View Javadoc
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 org.junit.jupiter.api.Assertions.assertArrayEquals;
21  import static org.junit.jupiter.api.Assertions.assertNotNull;
22  import static org.junit.jupiter.api.Assertions.fail;
23  import static org.junit.jupiter.api.Assumptions.assumeTrue;
24  
25  import java.io.File;
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.Enumeration;
36  import java.util.jar.JarEntry;
37  import java.util.jar.JarFile;
38  
39  import org.apache.bcel.classfile.ClassParser;
40  import org.apache.bcel.classfile.Code;
41  import org.apache.bcel.classfile.JavaClass;
42  import org.apache.bcel.classfile.Method;
43  import org.apache.bcel.util.ModularRuntimeImage;
44  import org.apache.commons.lang3.JavaVersion;
45  import org.apache.commons.lang3.SystemUtils;
46  import org.junit.jupiter.api.condition.DisabledOnJre;
47  import org.junit.jupiter.api.condition.JRE;
48  import org.junit.jupiter.params.ParameterizedTest;
49  import org.junit.jupiter.params.provider.MethodSource;
50  
51  /**
52   * 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
53   * should be the same as the input.
54   * <p>
55   * Sets the property {@value JavaHome#EXTRA_JAVA_HOMES} to a {@link File#pathSeparator}-separated list of JRE/JDK paths for additional testing.
56   * </p>
57   * <p>
58   * For example:
59   * </p>
60   *
61   * <pre>
62   * mvn test -Dtest=JdkGenericDumpTestCase -DExtraJavaHomes="C:\Program Files\Java\openjdk\jdk-13;C:\Program Files\Java\openjdk\jdk-14"
63   * mvn test -Dtest=JdkGenericDumpTestCase -DExtraJavaRoot="C:\Program Files\Eclipse Adoptium"
64   * </pre>
65   * <p>
66   * Where "C:\Program Files\Eclipse Adoptium" contains JDK directories, for example:
67   * </p>
68   * <ul>
69   * <li>jdk-11.0.16.101-hotspot</li>
70   * <li>jdk-17.0.4.101-hotspot</li>
71   * <li>jdk-19.0.0.36-hotspot</li>
72   * <li>jdk-8.0.345.1-hotspot</li>
73   * </ul>
74   */
75  public class JdkGenericDumpTestCase {
76  
77      private static class ClassParserFilesVisitor extends SimpleFileVisitor<Path> {
78  
79          private final PathMatcher matcher;
80  
81          @SuppressWarnings("resource") // FileSystems.getDefault() returns a singleton
82          ClassParserFilesVisitor(final String pattern) {
83              matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);
84          }
85  
86          private void find(final Path path) throws IOException {
87              final Path name = path.getFileName();
88              if (name != null && matcher.matches(name)) {
89                  try (final InputStream inputStream = Files.newInputStream(path)) {
90                      final ClassParser classParser = new ClassParser(inputStream, name.toAbsolutePath().toString());
91                      assertNotNull(classParser.parse());
92                  }
93              }
94          }
95  
96          @Override
97          public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) throws IOException {
98              find(dir);
99              return FileVisitResult.CONTINUE;
100         }
101 
102         @Override
103         public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
104             find(file);
105             return FileVisitResult.CONTINUE;
106         }
107 
108         @Override
109         public FileVisitResult visitFileFailed(final Path file, final IOException e) {
110             System.err.println(e);
111             return FileVisitResult.CONTINUE;
112         }
113     }
114 
115     private static final char[] hexArray = "0123456789ABCDEF".toCharArray();
116 
117     private static String bytesToHex(final byte[] bytes) {
118         final char[] hexChars = new char[bytes.length * 3];
119         int i = 0;
120         for (final byte b : bytes) {
121             final int v = b & 0xFF;
122             hexChars[i++] = hexArray[v >>> 4];
123             hexChars[i++] = hexArray[v & 0x0F];
124             hexChars[i++] = ' ';
125         }
126         return new String(hexChars);
127     }
128 
129     private void compare(final String name, final Method method) {
130         // System.out.println("Method: " + m);
131         final Code code = method.getCode();
132         if (code == null) {
133             return; // e.g. abstract method
134         }
135         final byte[] src = code.getCode();
136         final InstructionList instructionList = new InstructionList(src);
137         final byte[] out = instructionList.getByteCode();
138         if (src.length == out.length) {
139             assertArrayEquals(src, out, () -> name + ": " + method.toString());
140         } else {
141             System.out.println(name + ": " + method.toString() + " " + src.length + " " + out.length);
142             System.out.println(bytesToHex(src));
143             System.out.println(bytesToHex(out));
144             for (final InstructionHandle instructionHandle : instructionList) {
145                 System.out.println(instructionHandle.toString(false));
146             }
147             fail("Array comparison failure");
148         }
149     }
150 
151     private void testJar(final Path file) throws Exception {
152         System.out.println(file);
153         try (JarFile jar = new JarFile(file.toFile())) {
154             final Enumeration<JarEntry> en = jar.entries();
155             while (en.hasMoreElements()) {
156                 final JarEntry jarEntry = en.nextElement();
157                 final String name = jarEntry.getName();
158                 if (name.endsWith(JavaClass.EXTENSION)) {
159                     // System.out.println("- " + name);
160                     try (InputStream inputStream = jar.getInputStream(jarEntry)) {
161                         final ClassParser classParser = new ClassParser(inputStream, name);
162                         final JavaClass javaClass = classParser.parse();
163                         for (final Method method : javaClass.getMethods()) {
164                             compare(name, method);
165                         }
166                     }
167                 }
168             }
169         }
170     }
171 
172     @ParameterizedTest
173     @MethodSource("org.apache.bcel.generic.JavaHome#streamJarPath")
174     public void testJdkJars(final Path jarPath) throws Exception {
175         testJar(jarPath);
176     }
177 
178     @ParameterizedTest
179     @MethodSource("org.apache.bcel.generic.JavaHome#streamModulePath")
180     @DisabledOnJre(value = JRE.JAVA_8)
181     public void testJdkModules(final Path jmodPath) throws Exception {
182         testJar(jmodPath);
183     }
184 
185     @ParameterizedTest
186     @MethodSource("org.apache.bcel.generic.JavaHome#streamJavaHome")
187     public void testJreModules(final JavaHome javaHome) throws Exception {
188         assumeTrue(SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_9));
189         try (final ModularRuntimeImage mri = javaHome.getModularRuntimeImage()) {
190             for (final Path path : mri.modules()) {
191                 Files.walkFileTree(path, new ClassParserFilesVisitor("*.class"));
192             }
193         }
194     }
195 
196 }