1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package org.apache.bcel.generic; 19 20 import static com.sun.jna.platform.win32.WinReg.HKEY_LOCAL_MACHINE; 21 import static org.junit.Assert.assertArrayEquals; 22 import static org.junit.Assert.fail; 23 24 import java.io.File; 25 import java.io.FileFilter; 26 import java.io.IOException; 27 import java.io.InputStream; 28 import java.nio.file.FileSystems; 29 import java.nio.file.FileVisitResult; 30 import java.nio.file.Files; 31 import java.nio.file.Path; 32 import java.nio.file.PathMatcher; 33 import java.nio.file.SimpleFileVisitor; 34 import java.nio.file.attribute.BasicFileAttributes; 35 import java.util.Arrays; 36 import java.util.Collection; 37 import java.util.Enumeration; 38 import java.util.HashSet; 39 import java.util.List; 40 import java.util.Set; 41 import java.util.jar.JarEntry; 42 import java.util.jar.JarFile; 43 44 import org.apache.bcel.classfile.ClassParser; 45 import org.apache.bcel.classfile.Code; 46 import org.apache.bcel.classfile.JavaClass; 47 import org.apache.bcel.classfile.Method; 48 import org.apache.bcel.util.ModularRuntimeImage; 49 import org.apache.commons.lang3.JavaVersion; 50 import org.apache.commons.lang3.StringUtils; 51 import org.apache.commons.lang3.SystemUtils; 52 import org.junit.Assert; 53 import org.junit.Assume; 54 import org.junit.Test; 55 import org.junit.runner.RunWith; 56 import org.junit.runners.Parameterized; 57 import org.junit.runners.Parameterized.Parameters; 58 59 import com.sun.jna.platform.win32.Advapi32Util; 60 61 /** 62 * Test that the generic dump() methods work on the JDK classes Reads each class into an instruction list and then dumps 63 * the instructions. The output bytes should be the same as the input. 64 */ 65 @RunWith(Parameterized.class) 66 public class JdkGenericDumpTestCase { 67 68 private static class ClassParserFilesVisitor extends SimpleFileVisitor<Path> { 69 70 private final PathMatcher matcher; 71 ClassParserFilesVisitor(final String pattern)72 ClassParserFilesVisitor(final String pattern) { 73 matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern); 74 } 75 find(final Path path)76 private void find(final Path path) throws IOException { 77 final Path name = path.getFileName(); 78 if (name != null && matcher.matches(name)) { 79 try (final InputStream inputStream = Files.newInputStream(path)) { 80 final ClassParser parser = new ClassParser(inputStream, name.toAbsolutePath().toString()); 81 final JavaClass jc = parser.parse(); 82 Assert.assertNotNull(jc); 83 } 84 85 } 86 } 87 88 @Override preVisitDirectory(final Path dir, final BasicFileAttributes attrs)89 public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) throws IOException { 90 find(dir); 91 return FileVisitResult.CONTINUE; 92 } 93 94 @Override visitFile(final Path file, final BasicFileAttributes attrs)95 public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { 96 find(file); 97 return FileVisitResult.CONTINUE; 98 } 99 100 @Override visitFileFailed(final Path file, final IOException e)101 public FileVisitResult visitFileFailed(final Path file, final IOException e) { 102 System.err.println(e); 103 return FileVisitResult.CONTINUE; 104 } 105 } 106 107 private static final char[] hexArray = "0123456789ABCDEF".toCharArray(); 108 109 private static final String KEY_JDK = "SOFTWARE\\JavaSoft\\Java Development Kit"; 110 111 private static final String KEY_JDK_9 = "SOFTWARE\\JavaSoft\\JDK"; 112 113 private static final String KEY_JRE = "SOFTWARE\\JavaSoft\\Java Runtime Environment"; 114 115 private static final String KEY_JRE_9 = "SOFTWARE\\JavaSoft\\JRE"; 116 addAllJavaHomesOnWindows(final String keyJre, final Set<String> javaHomes)117 private static void addAllJavaHomesOnWindows(final String keyJre, final Set<String> javaHomes) { 118 if (Advapi32Util.registryKeyExists(HKEY_LOCAL_MACHINE, keyJre)) { 119 javaHomes.addAll(findJavaHomesOnWindows(keyJre, Advapi32Util.registryGetKeys(HKEY_LOCAL_MACHINE, keyJre))); 120 } 121 } 122 bytesToHex(final byte[] bytes)123 private static String bytesToHex(final byte[] bytes) { 124 final char[] hexChars = new char[bytes.length * 3]; 125 int i = 0; 126 for (final byte b : bytes) { 127 final int v = b & 0xFF; 128 hexChars[i++] = hexArray[v >>> 4]; 129 hexChars[i++] = hexArray[v & 0x0F]; 130 hexChars[i++] = ' '; 131 } 132 return new String(hexChars); 133 } 134 135 @Parameters(name = "{0}") data()136 public static Collection<String> data() { 137 return findJavaHomes(); 138 } 139 findJavaHomes()140 private static Set<String> findJavaHomes() { 141 if (SystemUtils.IS_OS_WINDOWS) { 142 return findJavaHomesOnWindows(); 143 } 144 final Set<String> javaHomes = new HashSet<>(1); 145 javaHomes.add(SystemUtils.JAVA_HOME); 146 return javaHomes; 147 } 148 findJavaHomesOnWindows()149 private static Set<String> findJavaHomesOnWindows() { 150 final Set<String> javaHomes = new HashSet<>(); 151 addAllJavaHomesOnWindows(KEY_JRE, javaHomes); 152 addAllJavaHomesOnWindows(KEY_JRE_9, javaHomes); 153 addAllJavaHomesOnWindows(KEY_JDK, javaHomes); 154 addAllJavaHomesOnWindows(KEY_JDK_9, javaHomes); 155 addAllJavaHomes("ExtraJavaHomes", javaHomes); 156 return javaHomes; 157 } 158 addAllJavaHomes(String extraJavaHomesProp, Set<String> javaHomes)159 private static void addAllJavaHomes(String extraJavaHomesProp, Set<String> javaHomes) { 160 String path = System.getProperty(extraJavaHomesProp); 161 if (StringUtils.isEmpty(path)) { 162 return; 163 } 164 String[] paths = path.split(File.pathSeparator); 165 javaHomes.addAll(Arrays.asList(paths)); 166 167 } 168 findJavaHomesOnWindows(final String keyJavaHome, final String[] keys)169 private static Set<String> findJavaHomesOnWindows(final String keyJavaHome, final String[] keys) { 170 final Set<String> javaHomes = new HashSet<>(keys.length); 171 for (final String key : keys) { 172 if (Advapi32Util.registryKeyExists(HKEY_LOCAL_MACHINE, keyJavaHome + "\\" + key)) { 173 final String javaHome = Advapi32Util.registryGetStringValue(HKEY_LOCAL_MACHINE, 174 keyJavaHome + "\\" + key, "JavaHome"); 175 if (StringUtils.isNoneBlank(javaHome)) { 176 if (new File(javaHome).exists()) { 177 javaHomes.add(javaHome); 178 } 179 } 180 } 181 } 182 return javaHomes; 183 } 184 185 private final String javaHome; 186 JdkGenericDumpTestCase(final String javaHome)187 public JdkGenericDumpTestCase(final String javaHome) { 188 this.javaHome = javaHome; 189 } 190 compare(final String name, final Method m)191 private void compare(final String name, final Method m) { 192 // System.out.println("Method: " + m); 193 final Code c = m.getCode(); 194 if (c == null) { 195 return; // e.g. abstract method 196 } 197 final byte[] src = c.getCode(); 198 final InstructionList il = new InstructionList(src); 199 final byte[] out = il.getByteCode(); 200 if (src.length == out.length) { 201 assertArrayEquals(name + ": " + m.toString(), src, out); 202 } else { 203 System.out.println(name + ": " + m.toString() + " " + src.length + " " + out.length); 204 System.out.println(bytesToHex(src)); 205 System.out.println(bytesToHex(out)); 206 for (final InstructionHandle ih : il) { 207 System.out.println(ih.toString(false)); 208 } 209 fail("Array comparison failure"); 210 } 211 } 212 listJdkJars()213 private File[] listJdkJars() throws Exception { 214 final File javaLib = new File(javaHome, "lib"); 215 return javaLib.listFiles(new FileFilter() { 216 @Override 217 public boolean accept(final File file) { 218 return file.getName().endsWith(".jar"); 219 } 220 }); 221 } 222 223 private File[] listJdkModules() throws Exception { 224 final File javaLib = new File(javaHome, "jmods"); 225 return javaLib.listFiles(new FileFilter() { 226 @Override 227 public boolean accept(final File file) { 228 return file.getName().endsWith(".jmod"); 229 } 230 }); 231 } 232 233 private void testJar(final File file) throws Exception { 234 System.out.println(file); 235 try (JarFile jar = new JarFile(file)) { 236 final Enumeration<JarEntry> en = jar.entries(); 237 while (en.hasMoreElements()) { 238 final JarEntry e = en.nextElement(); 239 final String name = e.getName(); 240 if (name.endsWith(".class")) { 241 // System.out.println("- " + name); 242 try (InputStream in = jar.getInputStream(e)) { 243 final ClassParser parser = new ClassParser(in, name); 244 final JavaClass jc = parser.parse(); 245 for (final Method m : jc.getMethods()) { 246 compare(name, m); 247 } 248 } 249 } 250 } 251 } 252 } 253 254 @Test 255 public void testJdkJars() throws Exception { 256 final File[] jars = listJdkJars(); 257 if (jars != null) { 258 for (final File file : jars) { 259 testJar(file); 260 } 261 } 262 } 263 264 @Test 265 public void testJdkModules() throws Exception { 266 final File[] jmods = listJdkModules(); 267 if (jmods != null) { 268 for (final File file : jmods) { 269 testJar(file); 270 } 271 } 272 } 273 274 @Test 275 public void testJreModules() throws Exception { 276 Assume.assumeTrue(SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_9)); 277 try (final ModularRuntimeImage mri = new ModularRuntimeImage(javaHome)) { 278 final List<Path> modules = mri.modules(); 279 Assert.assertFalse(modules.isEmpty()); 280 for (final Path path : modules) { 281 Files.walkFileTree(path, new ClassParserFilesVisitor("*.class")); 282 } 283 } 284 } 285 286 } 287