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 * @ParameterizedTest 63 * @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