xref: /aosp_15_r20/external/ow2-asm/asm-test/src/main/java/org/objectweb/asm/test/AsmTest.java (revision 2835e6bb194a25e32dae2cc0628d8f988b82bfc0)
1 // ASM: a very small and fast Java bytecode manipulation framework
2 // Copyright (c) 2000-2011 INRIA, France Telecom
3 // All rights reserved.
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions
7 // are met:
8 // 1. Redistributions of source code must retain the above copyright
9 //    notice, this list of conditions and the following disclaimer.
10 // 2. Redistributions in binary form must reproduce the above copyright
11 //    notice, this list of conditions and the following disclaimer in the
12 //    documentation and/or other materials provided with the distribution.
13 // 3. Neither the name of the copyright holders nor the names of its
14 //    contributors may be used to endorse or promote products derived from
15 //    this software without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27 // THE POSSIBILITY OF SUCH DAMAGE.
28 package org.objectweb.asm.test;
29 
30 import java.io.ByteArrayOutputStream;
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.util.Arrays;
34 import java.util.stream.Stream;
35 import org.junit.jupiter.params.provider.Arguments;
36 
37 /**
38  * Base class for the ASM tests. ASM can be used to read, write or transform any Java class, ranging
39  * from very old (e.g. JDK 1.3) to very recent classes, containing all possible class file
40  * structures. ASM can also be used with different variants of its API (ASM4, ASM5, ASM6, etc). In
41  * order to test it thoroughly, it is therefore necessary to run read, write and transform tests,
42  * for each API version, and for each class in a set of classes containing all possible class file
43  * structures. The purpose of this class is to automate this process. For this it relies on:
44  *
45  * <ul>
46  *   <li>a small set of hand-crafted classes designed to contain as much class file structures as
47  *       possible (it is impossible to represent all possible bytecode sequences). These classes are
48  *       called "precompiled classes" below, because they are not compiled as part of the build.
49  *       Instead, they have been compiled beforehand with the appropriate JDKs (e.g. with the JDK
50  *       1.3, 1.5, etc).
51  *   <li>the JUnit framework for parameterized tests. Using the {@link #allClassesAndAllApis()}
52  *       method, selected test methods can be instantiated for each possible (precompiled class, ASM
53  *       API) tuple.
54  * </ul>
55  *
56  * <p>For instance, to run a test on all the precompiled classes, with all the APIs, use a subclass
57  * such as the following:
58  *
59  * <pre>
60  * public class MyParameterizedTest extends AsmTest {
61  *
62  *   &#64;ParameterizedTest
63  *   &#64;MethodSource(ALL_CLASSES_AND_ALL_APIS)
64  *   public void testSomeFeature(PrecompiledClass classParameter, Api apiParameter) {
65  *     byte[] b = classParameter.getBytes();
66  *     ClassWriter classWriter = new ClassWriter(apiParameter.value(), 0);
67  *     ...
68  *   }
69  * }
70  * </pre>
71  *
72  * @author Eric Bruneton
73  */
74 public abstract class AsmTest {
75 
76   /** The size of the temporary byte array used to read class input streams chunk by chunk. */
77   private static final int INPUT_STREAM_DATA_CHUNK_SIZE = 4096;
78 
79   /**
80    * MethodSource name to be used in parameterized tests that must be instantiated for all possible
81    * (precompiled class, api) pairs.
82    */
83   public static final String ALL_CLASSES_AND_ALL_APIS = "allClassesAndAllApis";
84 
85   /**
86    * MethodSource name to be used in parameterized tests that must be instantiated for all
87    * precompiled classes, with the latest api.
88    */
89   public static final String ALL_CLASSES_AND_LATEST_API = "allClassesAndLatestApi";
90 
91   /**
92    * The expected pattern (i.e. regular expression) that ASM's UnsupportedOperationException
93    * messages are supposed to match.
94    */
95   public static final String UNSUPPORTED_OPERATION_MESSAGE_PATTERN = ".* requires ASM[56789].*";
96 
97   /** JDK version with the corresponding ASM version. */
98   enum JdkVersion {
99     JDK7(7, Api.ASM4),
100     JDK8(8, Api.ASM5),
101     JDK9(9, Api.ASM6),
102     JDK11(11, Api.ASM7),
103     JDK14(14, Api.ASM8),
104     JDK15(15, Api.ASM9);
105 
106     private final int majorVersion;
107     private final Api minimumApi;
108 
JdkVersion(final int majorVersion, final Api minimumApi)109     JdkVersion(final int majorVersion, final Api minimumApi) {
110       this.majorVersion = majorVersion;
111       this.minimumApi = minimumApi;
112     }
113 
114     /**
115      * Returns the major version of the current JDK version.
116      *
117      * @return the major version of the current JDK version.
118      */
majorVersion()119     public int majorVersion() {
120       return majorVersion;
121     }
122 
123     /**
124      * Returns the minimum ASM Api version supporting the current JDK version.
125      *
126      * @return the minimum ASM Api version supporting the current JDK version.
127      */
minimumApi()128     public Api minimumApi() {
129       return minimumApi;
130     }
131   }
132 
133   /**
134    * A precompiled class, hand-crafted to contain some set of class file structures. These classes
135    * are not compiled as part of the build. Instead, they have been compiled beforehand, with the
136    * appropriate JDKs (including some now very hard to download and install).
137    */
138   public enum PrecompiledClass {
139     DEFAULT_PACKAGE("DefaultPackage"),
140     JDK3_ALL_INSTRUCTIONS("jdk3.AllInstructions"),
141     JDK3_ALL_STRUCTURES("jdk3.AllStructures"),
142     JDK3_ANONYMOUS_INNER_CLASS("jdk3.AllStructures$1"),
143     JDK3_ARTIFICIAL_STRUCTURES("jdk3.ArtificialStructures"),
144     JDK3_INNER_CLASS("jdk3.AllStructures$InnerClass"),
145     JDK3_LARGE_METHOD("jdk3.LargeMethod"),
146     JDK3_SUB_OPTIMAL_MAX_STACK_AND_LOCALS("jdk3.SubOptimalMaxStackAndLocals"),
147     JDK5_ALL_INSTRUCTIONS("jdk5.AllInstructions"),
148     JDK5_ALL_STRUCTURES("jdk5.AllStructures"),
149     JDK5_ANNOTATION("jdk5.AllStructures$InvisibleAnnotation"),
150     JDK5_ENUM("jdk5.AllStructures$EnumClass"),
151     JDK5_LOCAL_CLASS("jdk5.AllStructures$1LocalClass"),
152     JDK8_ALL_FRAMES("jdk8.AllFrames", JdkVersion.JDK8),
153     JDK8_ALL_INSTRUCTIONS("jdk8.AllInstructions", JdkVersion.JDK8),
154     JDK8_ALL_STRUCTURES("jdk8.AllStructures", JdkVersion.JDK8),
155     JDK8_ANONYMOUS_INNER_CLASS("jdk8.AllStructures$1", JdkVersion.JDK8),
156     JDK8_ARTIFICIAL_STRUCTURES("jdk8.Artificial$()$Structures", JdkVersion.JDK8),
157     JDK8_INNER_CLASS("jdk8.AllStructures$InnerClass", JdkVersion.JDK8),
158     JDK8_LARGE_METHOD("jdk8.LargeMethod", JdkVersion.JDK8),
159     JDK9_MODULE("jdk9.module-info", JdkVersion.JDK9),
160     JDK11_ALL_INSTRUCTIONS("jdk11.AllInstructions", JdkVersion.JDK11),
161     JDK11_ALL_STRUCTURES("jdk11.AllStructures", JdkVersion.JDK11),
162     JDK11_ALL_STRUCTURES_NESTED("jdk11.AllStructures$Nested", JdkVersion.JDK11),
163     JDK14_ALL_STRUCTURES_RECORD("jdk14.AllStructures$RecordSubType", JdkVersion.JDK14, true),
164     JDK14_ALL_STRUCTURES_EMPTY_RECORD("jdk14.AllStructures$EmptyRecord", JdkVersion.JDK14, true),
165     JDK15_ALL_STRUCTURES("jdk15.AllStructures", JdkVersion.JDK15, true);
166 
167     private final String name;
168     private final JdkVersion jdkVersion;
169     private final boolean preview;
170     private byte[] bytes;
171 
PrecompiledClass(final String name, final JdkVersion jdkVersion, final boolean preview)172     PrecompiledClass(final String name, final JdkVersion jdkVersion, final boolean preview) {
173       this.name = name;
174       this.jdkVersion = jdkVersion;
175       this.preview = preview;
176     }
177 
PrecompiledClass(final String name, final JdkVersion jdkVersion)178     PrecompiledClass(final String name, final JdkVersion jdkVersion) {
179       this(name, jdkVersion, false);
180     }
181 
PrecompiledClass(final String name)182     PrecompiledClass(final String name) {
183       this(name, JdkVersion.JDK7, false);
184     }
185 
186     /**
187      * Returns the fully qualified name of this class.
188      *
189      * @return the fully qualified name of this class.
190      */
getName()191     public String getName() {
192       return name;
193     }
194 
195     /**
196      * Returns the internal name of this class.
197      *
198      * @return the internal name of this class.
199      */
getInternalName()200     public String getInternalName() {
201       return name.endsWith(ClassFile.MODULE_INFO) ? ClassFile.MODULE_INFO : name.replace('.', '/');
202     }
203 
204     /**
205      * Returns true if this class was compiled with a JDK which is more recent than the given ASM
206      * API. For instance, returns true for a class compiled with the JDK 1.8 if the ASM API version
207      * is ASM4.
208      *
209      * @param api an ASM API version.
210      * @return whether this class was compiled with a JDK which is more recent than api.
211      */
isMoreRecentThan(final Api api)212     public boolean isMoreRecentThan(final Api api) {
213       return api.value() < jdkVersion.minimumApi().value();
214     }
215 
216     /**
217      * Returns true if this class was compiled with a JDK which is not compatible with the JDK used
218      * to run the tests.
219      *
220      * @return true if this class was compiled with a JDK which is not compatible with the JDK used
221      *     to run the tests.
222      */
isNotCompatibleWithCurrentJdk()223     public boolean isNotCompatibleWithCurrentJdk() {
224       if (preview) {
225         if (!Util.previewFeatureEnabled()) {
226           return true;
227         }
228         return Util.getMajorJavaVersion() != jdkVersion.majorVersion();
229       }
230       return Util.getMajorJavaVersion() < jdkVersion.majorVersion();
231     }
232 
233     /**
234      * Returns the content of this class.
235      *
236      * @return the content of this class.
237      */
getBytes()238     public byte[] getBytes() {
239       if (bytes == null) {
240         bytes = AsmTest.getBytes(name, ".class");
241       }
242       return bytes.clone();
243     }
244 
245     @Override
toString()246     public String toString() {
247       return name;
248     }
249   }
250 
251   /**
252    * An invalid class, hand-crafted to contain some set of invalid class file structures. These
253    * classes are not compiled as part of the build. Instead, they have been compiled beforehand, and
254    * then manually edited to introduce errors.
255    */
256   public enum InvalidClass {
257     INVALID_BYTECODE_OFFSET("invalid.InvalidBytecodeOffset"),
258     INVALID_CLASS_VERSION("invalid.InvalidClassVersion"),
259     INVALID_CODE_LENGTH("invalid.InvalidCodeLength"),
260     INVALID_CONSTANT_POOL_INDEX("invalid.InvalidConstantPoolIndex"),
261     INVALID_CONSTANT_POOL_REFERENCE("invalid.InvalidConstantPoolReference"),
262     INVALID_CP_INFO_TAG("invalid.InvalidCpInfoTag"),
263     INVALID_ELEMENT_VALUE("invalid.InvalidElementValue"),
264     INVALID_INSN_TYPE_ANNOTATION_TARGET_TYPE("invalid.InvalidInsnTypeAnnotationTargetType"),
265     INVALID_OPCODE("invalid.InvalidOpcode"),
266     INVALID_SOURCE_DEBUG_EXTENSION("invalid.InvalidSourceDebugExtension"),
267     INVALID_STACK_MAP_FRAME_TYPE("invalid.InvalidStackMapFrameType"),
268     INVALID_TYPE_ANNOTATION_TARGET_TYPE("invalid.InvalidTypeAnnotationTargetType"),
269     INVALID_VERIFICATION_TYPE_INFO("invalid.InvalidVerificationTypeInfo"),
270     INVALID_WIDE_OPCODE("invalid.InvalidWideOpcode");
271 
272     private final String name;
273 
InvalidClass(final String name)274     InvalidClass(final String name) {
275       this.name = name;
276     }
277 
278     /**
279      * Returns the fully qualified name of this class.
280      *
281      * @return the fully qualified name of this class.
282      */
getName()283     public String getName() {
284       return name;
285     }
286 
287     /**
288      * Returns the content of this class.
289      *
290      * @return the content of this class.
291      */
getBytes()292     public byte[] getBytes() {
293       return AsmTest.getBytes(name, ".clazz");
294     }
295 
296     @Override
toString()297     public String toString() {
298       return name;
299     }
300   }
301 
302   /** An ASM API version. */
303   public enum Api {
304     ASM4("ASM4", 4 << 16),
305     ASM5("ASM5", 5 << 16),
306     ASM6("ASM6", 6 << 16),
307     ASM7("ASM7", 7 << 16),
308     ASM8("ASM8", 8 << 16),
309     ASM9("ASM9", 9 << 16),
310     ;
311 
312     private final String name;
313     private final int value;
314 
Api(final String name, final int value)315     Api(final String name, final int value) {
316       this.name = name;
317       this.value = value;
318     }
319 
320     /**
321      * Returns the int value of this version, as expected by ASM.
322      *
323      * @return one of the ASM4, ASM5, ASM6, ASM7, ASM8 or ASM9 constants from the ASM Opcodes
324      *     interface.
325      */
value()326     public int value() {
327       return value;
328     }
329 
330     /**
331      * Returns a human readable symbol corresponding to this version.
332      *
333      * @return one of "ASM4", "ASM5", "ASM6" "ASM7", "ASM8" or "ASM9".
334      */
335     @Override
toString()336     public String toString() {
337       return name;
338     }
339   }
340 
341   /**
342    * Builds a list of test arguments for a parameterized test. Parameterized test cases annotated
343    * with {@code @MethodSource("allClassesAndAllApis")} will be executed on all the possible
344    * (precompiledClass, api) pairs.
345    *
346    * @return all the possible (precompiledClass, api) pairs, for all the precompiled classes and all
347    *     the given ASM API versions.
348    */
allClassesAndAllApis()349   public static Stream<Arguments> allClassesAndAllApis() {
350     return classesAndApis(Api.values());
351   }
352 
353   /**
354    * Builds a list of test arguments for a parameterized test. Parameterized test cases annotated
355    * with {@code @MethodSource("allClassesAndLatestApi")} will be executed on all the precompiled
356    * classes, with the latest api.
357    *
358    * @return all the possible (precompiledClass, ASM9) pairs, for all the precompiled classes.
359    */
allClassesAndLatestApi()360   public static Stream<Arguments> allClassesAndLatestApi() {
361     return classesAndApis(Api.ASM9);
362   }
363 
classesAndApis(final Api... apis)364   private static Stream<Arguments> classesAndApis(final Api... apis) {
365     return Arrays.stream(PrecompiledClass.values())
366         .flatMap(
367             precompiledClass ->
368                 Arrays.stream(apis).map(api -> Arguments.of(precompiledClass, api)));
369   }
370 
getBytes(final String name, final String extension)371   private static byte[] getBytes(final String name, final String extension) {
372     String resourceName = name.replace('.', '/') + extension;
373     try (InputStream inputStream = ClassLoader.getSystemResourceAsStream(resourceName)) {
374       if (inputStream == null) {
375         throw new IllegalArgumentException("Class not found " + name);
376       }
377       ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
378       byte[] data = new byte[INPUT_STREAM_DATA_CHUNK_SIZE];
379       int bytesRead;
380       while ((bytesRead = inputStream.read(data, 0, data.length)) != -1) {
381         outputStream.write(data, 0, bytesRead);
382       }
383       outputStream.flush();
384       return outputStream.toByteArray();
385     } catch (IOException e) {
386       throw new ClassFormatException("Can't read " + name, e);
387     }
388   }
389 }
390