blob: 7287d471add5c1a58274918276a7b6c5fe41e0ce [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.bcel.generic;
import static com.sun.jna.platform.win32.WinReg.HKEY_LOCAL_MACHINE;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Stream;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.util.ModularRuntimeImage;
import org.apache.commons.lang3.JavaVersion;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import com.sun.jna.platform.win32.Advapi32Util;
/**
* Test that the generic dump() methods work on the JDK classes Reads each class into an instruction list and then dumps
* the instructions. The output bytes should be the same as the input.
* <p>
* Set the property {@value #EXTRA_JAVA_HOMES} to a {@link File#pathSeparator}-separated list of JRE/JDK paths for
* additional testing.
* </p>
* <p>
* For example:
* </p>
*
* <pre>
* mvn test -Dtest=JdkGenericDumpTestCase -DExtraJavaHomes="C:\Program Files\Java\openjdk\jdk-13;C:\Program Files\Java\openjdk\jdk-14"
* </pre>
*/
public class JdkGenericDumpTestCase {
private static class ClassParserFilesVisitor extends SimpleFileVisitor<Path> {
private final PathMatcher matcher;
@SuppressWarnings("resource") // FileSystems.getDefault() returns a singleton
ClassParserFilesVisitor(final String pattern) {
matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);
}
private void find(final Path path) throws IOException {
final Path name = path.getFileName();
if (name != null && matcher.matches(name)) {
try (final InputStream inputStream = Files.newInputStream(path)) {
final ClassParser classParser = new ClassParser(inputStream, name.toAbsolutePath().toString());
assertNotNull(classParser.parse());
}
}
}
@Override
public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) throws IOException {
find(dir);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
find(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(final Path file, final IOException e) {
System.err.println(e);
return FileVisitResult.CONTINUE;
}
}
private static final String EXTRA_JAVA_HOMES = "ExtraJavaHomes";
private static final char[] hexArray = "0123456789ABCDEF".toCharArray();
private static final String KEY_JDK = "SOFTWARE\\JavaSoft\\Java Development Kit";
private static final String KEY_JDK_9 = "SOFTWARE\\JavaSoft\\JDK";
private static final String KEY_JRE = "SOFTWARE\\JavaSoft\\Java Runtime Environment";
private static final String KEY_JRE_9 = "SOFTWARE\\JavaSoft\\JRE";
private static String bytesToHex(final byte[] bytes) {
final char[] hexChars = new char[bytes.length * 3];
int i = 0;
for (final byte b : bytes) {
final int v = b & 0xFF;
hexChars[i++] = hexArray[v >>> 4];
hexChars[i++] = hexArray[v & 0x0F];
hexChars[i++] = ' ';
}
return new String(hexChars);
}
public static Stream<String> findJavaHomes() {
if (SystemUtils.IS_OS_WINDOWS) {
final Stream<String> stream = findJavaHomesOnWindows();
if (stream.count() > 0) {
return findJavaHomesOnWindows();
}
// Falls back here on CI env like GitHub Actions.
}
return Stream.of(SystemUtils.JAVA_HOME);
}
private static Stream<String> findJavaHomesOnWindows() {
return Stream.concat(
Stream.of(KEY_JRE, KEY_JRE_9, KEY_JDK, KEY_JDK_9)
.flatMap(JdkGenericDumpTestCase::getAllJavaHomesOnWindows),
getAllJavaHomesFromKey(EXTRA_JAVA_HOMES)
).distinct();
}
private static Stream<String> findJavaHomesOnWindows(final String keyJavaHome, final String[] keys) {
final Set<String> javaHomes = new HashSet<>(keys.length);
for (final String key : keys) {
if (Advapi32Util.registryKeyExists(HKEY_LOCAL_MACHINE, keyJavaHome + "\\" + key)) {
final String javaHome = Advapi32Util.registryGetStringValue(HKEY_LOCAL_MACHINE,
keyJavaHome + "\\" + key, "JavaHome");
if (StringUtils.isNoneBlank(javaHome) && new File(javaHome).exists()) {
javaHomes.add(javaHome);
}
}
}
return javaHomes.stream();
}
private static Stream<String> getAllJavaHomesFromKey(final String extraJavaHomesKey) {
return Stream.concat(
getAllJavaHomesFromPath(System.getProperty(extraJavaHomesKey)),
getAllJavaHomesFromPath(System.getenv(extraJavaHomesKey)));
}
private static Stream<String> getAllJavaHomesFromPath(final String path) {
if (StringUtils.isEmpty(path)) {
return Stream.empty();
}
return Stream.of(path.split(File.pathSeparator));
}
private static Stream<String> getAllJavaHomesOnWindows(final String keyJre) {
if (Advapi32Util.registryKeyExists(HKEY_LOCAL_MACHINE, keyJre)) {
return findJavaHomesOnWindows(keyJre, Advapi32Util.registryGetKeys(HKEY_LOCAL_MACHINE, keyJre));
}
return Stream.empty();
}
private void compare(final String name, final Method method) {
// System.out.println("Method: " + m);
final Code code = method.getCode();
if (code == null) {
return; // e.g. abstract method
}
final byte[] src = code.getCode();
final InstructionList instructionList = new InstructionList(src);
final byte[] out = instructionList.getByteCode();
if (src.length == out.length) {
assertArrayEquals(src, out, () -> name + ": " + method.toString());
} else {
System.out.println(name + ": " + method.toString() + " " + src.length + " " + out.length);
System.out.println(bytesToHex(src));
System.out.println(bytesToHex(out));
for (final InstructionHandle instructionHandle : instructionList) {
System.out.println(instructionHandle.toString(false));
}
fail("Array comparison failure");
}
}
private File[] listJdkJars(final String javaHome) throws Exception {
final File javaLib = new File(javaHome, "lib");
return javaLib.listFiles(file -> file.getName().endsWith(".jar"));
}
private File[] listJdkModules(final String javaHome) throws Exception {
final File javaLib = new File(javaHome, "jmods");
return javaLib.listFiles(file -> file.getName().endsWith(".jmod"));
}
private void testJar(final File file) throws Exception {
System.out.println(file);
try (JarFile jar = new JarFile(file)) {
final Enumeration<JarEntry> en = jar.entries();
while (en.hasMoreElements()) {
final JarEntry jarEntry = en.nextElement();
final String name = jarEntry.getName();
if (name.endsWith(".class")) {
// System.out.println("- " + name);
try (InputStream inputStream = jar.getInputStream(jarEntry)) {
final ClassParser classParser = new ClassParser(inputStream, name);
final JavaClass javaClass = classParser.parse();
for (final Method method : javaClass.getMethods()) {
compare(name, method);
}
}
}
}
}
}
@ParameterizedTest
@MethodSource("findJavaHomes")
public void testJdkJars(final String javaHome) throws Exception {
final File[] jars = listJdkJars(javaHome);
if (jars != null) {
for (final File file : jars) {
testJar(file);
}
}
}
@ParameterizedTest
@MethodSource("findJavaHomes")
public void testJdkModules(final String javaHome) throws Exception {
final File[] jmods = listJdkModules(javaHome);
if (jmods != null) {
for (final File file : jmods) {
testJar(file);
}
}
}
@ParameterizedTest
@MethodSource("findJavaHomes")
public void testJreModules(final String javaHome) throws Exception {
assumeTrue(SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_9));
try (final ModularRuntimeImage mri = new ModularRuntimeImage(javaHome)) {
final List<Path> modules = mri.modules();
assertFalse(modules.isEmpty());
for (final Path path : modules) {
Files.walkFileTree(path, new ClassParserFilesVisitor("*.class"));
}
}
}
}