org.objectweb.asm.test.AsmTest Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of asm-test Show documentation
Show all versions of asm-test Show documentation
Utilities for testing ASM, a very small and fast Java bytecode manipulation framework
// ASM: a very small and fast Java bytecode manipulation framework
// Copyright (c) 2000-2011 INRIA, France Telecom
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// 3. Neither the name of the copyright holders nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
package org.objectweb.asm.test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.StringTokenizer;
import java.util.stream.Stream;
import org.junit.jupiter.params.provider.Arguments;
/**
* Base class for the ASM tests. ASM can be used to read, write or transform any Java class, ranging
* from very old (e.g. JDK 1.3) to very recent classes, containing all possible class file
* structures. ASM can also be used with different variants of its API (ASM4, ASM5, ASM6, etc). In
* order to test it thoroughly, it is therefore necessary to run read, write and transform tests,
* for each API version, and for each class in a set of classes containing all possible class file
* structures. The purpose of this class is to automate this process. For this it relies on:
*
*
* - a small set of hand-crafted classes designed to contain as much class file structures as
* possible (it is impossible to represent all possible bytecode sequences). These classes are
* called "precompiled classes" below, because they are not compiled as part of the build.
* Instead, they have been compiled beforehand with the appropriate JDKs (e.g. with the JDK
* 1.3, 1.5, etc).
*
- the JUnit framework for parameterized tests. Using the {@link #allClassesAndAllApis()}
* method, selected test methods can be instantiated for each possible (precompiled class, ASM
* API) tuple.
*
*
* For instance, to run a test on all the precompiled classes, with all the APIs, use a subclass
* such as the following:
*
*
* public class MyParameterizedTest extends AsmTest {
*
* @ParameterizedTest
* @MethodSource(ALL_CLASSES_AND_ALL_APIS)
* public void testSomeFeature(PrecompiledClass classParameter, Api apiParameter) {
* byte[] b = classParameter.getBytes();
* ClassWriter classWriter = new ClassWriter(apiParameter.value(), 0);
* ...
* }
* }
*
*
* @author Eric Bruneton
*/
public abstract class AsmTest {
/** The size of the temporary byte array used to read class input streams chunk by chunk. */
private static final int INPUT_STREAM_DATA_CHUNK_SIZE = 4096;
/** The name of JDK9 module classes. */
private static final String MODULE_INFO = "module-info";
/**
* MethodSource name to be used in parameterized tests that must be instantiated for all possible
* (precompiled class, api) pairs.
*/
public static final String ALL_CLASSES_AND_ALL_APIS = "allClassesAndAllApis";
/**
* MethodSource name to be used in parameterized tests that must be instantiated for all
* precompiled classes, with the latest api.
*/
public static final String ALL_CLASSES_AND_LATEST_API = "allClassesAndLatestApi";
/**
* A precompiled class, hand-crafted to contain some set of class file structures. These classes
* are not compiled as part of the build. Instead, they have been compiled beforehand, with the
* appropriate JDKs (including some now very hard to download and install).
*/
public enum PrecompiledClass {
DEFAULT_PACKAGE("DefaultPackage"),
JDK3_ALL_INSTRUCTIONS("jdk3.AllInstructions"),
JDK3_ALL_STRUCTURES("jdk3.AllStructures"),
JDK3_ANONYMOUS_INNER_CLASS("jdk3.AllStructures$1"),
JDK3_ARTIFICIAL_STRUCTURES("jdk3.ArtificialStructures"),
JDK3_INNER_CLASS("jdk3.AllStructures$InnerClass"),
JDK3_LARGE_METHOD("jdk3.LargeMethod"),
JDK5_ALL_INSTRUCTIONS("jdk5.AllInstructions"),
JDK5_ALL_STRUCTURES("jdk5.AllStructures"),
JDK5_ANNOTATION("jdk5.AllStructures$InvisibleAnnotation"),
JDK5_ENUM("jdk5.AllStructures$EnumClass"),
JDK5_LOCAL_CLASS("jdk5.AllStructures$1LocalClass"),
JDK8_ALL_FRAMES("jdk8.AllFrames"),
JDK8_ALL_INSTRUCTIONS("jdk8.AllInstructions"),
JDK8_ALL_STRUCTURES("jdk8.AllStructures"),
JDK8_ANONYMOUS_INNER_CLASS("jdk8.AllStructures$1"),
JDK8_ARTIFICIAL_STRUCTURES("jdk8.ArtificialStructures"),
JDK8_INNER_CLASS("jdk8.AllStructures$InnerClass"),
JDK8_LARGE_METHOD("jdk8.LargeMethod"),
JDK9_MODULE("jdk9.module-info"),
JDK11_ALL_INSTRUCTIONS("jdk11.AllInstructions"),
JDK11_ALL_STRUCTURES("jdk11.AllStructures"),
JDK11_ALL_STRUCTURES_NESTED("jdk11.AllStructures$Nested"),
JDK11_LAMBDA_CONDY("jdk11.LambdaCondy");
private final String name;
PrecompiledClass(final String name) {
this.name = name;
}
/** @return the fully qualified name of this class. */
public String getName() {
return name;
}
/** @return the internal name of this class. */
public String getInternalName() {
return name.endsWith(MODULE_INFO) ? MODULE_INFO : name.replace('.', '/');
}
/**
* Returns true if this class was compiled with a JDK which is more recent than the given ASM
* API. For instance, returns true for a class compiled with the JDK 1.8 if the ASM API version
* is ASM4.
*
* @param api an ASM API version.
* @return whether this class was compiled with a JDK which is more recent than api.
*/
public boolean isMoreRecentThan(final Api api) {
if (name.startsWith("jdk8.") && api.value() < Api.ASM5.value()) {
return true;
}
if (name.startsWith("jdk9.") && api.value() < Api.ASM6.value()) {
return true;
}
return name.startsWith("jdk11.") && api.value() < Api.ASM7.value();
}
/**
* Returns true if this class was compiled with a JDK which is more recent than the JDK used to
* run the tests.
*
* @return true if this class was compiled with the JDK9 and the current JDK version is strictly
* less than 9.
*/
public boolean isMoreRecentThanCurrentJdk() {
if (name.startsWith("jdk9.")) {
return getMajorJavaVersion() < 9;
}
if (name.startsWith("jdk11.")) {
return getMajorJavaVersion() < 11;
}
return false;
}
/** @return the content of this class. */
public byte[] getBytes() {
return AsmTest.getBytes(name);
}
@Override
public String toString() {
return name;
}
}
/**
* An invalid class, hand-crafted to contain some set of invalid class file structures. These
* classes are not compiled as part of the build. Instead, they have been compiled beforehand, and
* then manually edited to introduce errors.
*/
public enum InvalidClass {
INVALID_BYTECODE_OFFSET("invalid.InvalidBytecodeOffset"),
INVALID_CLASS_VERSION("invalid.InvalidClassVersion"),
INVALID_CONSTANT_POOL_INDEX("invalid.InvalidConstantPoolIndex"),
INVALID_CONSTANT_POOL_REFERENCE("invalid.InvalidConstantPoolReference"),
INVALID_CP_INFO_TAG("invalid.InvalidCpInfoTag"),
INVALID_ELEMENT_VALUE("invalid.InvalidElementValue"),
INVALID_INSN_TYPE_ANNOTATION_TARGET_TYPE("invalid.InvalidInsnTypeAnnotationTargetType"),
INVALID_OPCODE("invalid.InvalidOpcode"),
INVALID_STACK_MAP_FRAME_TYPE("invalid.InvalidStackMapFrameType"),
INVALID_TYPE_ANNOTATION_TARGET_TYPE("invalid.InvalidTypeAnnotationTargetType"),
INVALID_VERIFICATION_TYPE_INFO("invalid.InvalidVerificationTypeInfo"),
INVALID_WIDE_OPCODE("invalid.InvalidWideOpcode");
private final String name;
InvalidClass(final String name) {
this.name = name;
}
/** @return the content of this class. */
public byte[] getBytes() {
return AsmTest.getBytes(name);
}
@Override
public String toString() {
return name;
}
}
/** An ASM API version. */
public enum Api {
ASM4("ASM4", 4 << 16),
ASM5("ASM5", 5 << 16),
ASM6("ASM6", 6 << 16),
ASM7("ASM7", 1 << 24 | 7 << 16);
private final String name;
private final int value;
Api(final String name, final int value) {
this.name = name;
this.value = value;
}
/**
* Returns the int value of this version, as expected by ASM.
*
* @return one of the ASM4, ASM5, ASM6 or ASM7 constants from the ASM Opcodes interface.
*/
public int value() {
return value;
}
/**
* Returns a human readable symbol corresponding to this version.
*
* @return one of "ASM4", "ASM5", "ASM6" or "ASM7".
*/
@Override
public String toString() {
return name;
}
}
/**
* Builds a list of test arguments for a parameterized test. Parameterized test cases annotated
* with @MethodSource("allClassesAndAllApis") will be executed on all the possible
* (precompiledClass, api) pairs.
*
* @return all the possible (precompiledClass, api) pairs, for all the precompiled classes and all
* the given ASM API versions.
*/
public static Stream allClassesAndAllApis() {
return classesAndApis(Api.values());
}
/**
* Builds a list of test arguments for a parameterized test. Parameterized test cases annotated
* with @MethodSource("allClassesAndLatestApi") will be executed on all the precompiled
* classes, with the latest api.
*
* @return all the possible (precompiledClass, ASM7) pairs, for all the precompiled classes.
*/
public static Stream allClassesAndLatestApi() {
return classesAndApis(Api.ASM7);
}
private static Stream classesAndApis(final Api... apis) {
return Arrays.stream(PrecompiledClass.values())
.flatMap(
precompiledClass ->
Arrays.stream(apis).map(api -> Arguments.of(precompiledClass, api)));
}
private static int getMajorJavaVersion() {
String javaVersion = System.getProperty("java.version");
String javaMajorVersion = new StringTokenizer(javaVersion, "._").nextToken();
return Integer.parseInt(javaMajorVersion);
}
/**
* Starts an assertion about the given class content.
*
* @param classFile the content of a class.
* @return the {@link ClassSubject} to use to make actual assertions about the given class.
*/
public static ClassSubject assertThatClass(final byte[] classFile) {
return new ClassSubject(classFile);
}
/** Helper to make assertions about a class. */
public static class ClassSubject {
/** The content of the class to be tested. */
private final byte[] classFile;
ClassSubject(final byte[] classFile) {
this.classFile = classFile;
}
/**
* Asserts that a dump of the subject class into a string representation contains the given
* string.
*
* @param expectedString a string which should be contained in a dump of the subject class.
*/
public void contains(final String expectedString) {
try {
String dump = new ClassDump(classFile).toString();
assertTrue(dump.contains(expectedString));
} catch (IOException | IllegalArgumentException e) {
fail("Class can't be dumped", e);
}
}
/**
* Asserts that the subject class is equal to the given class, modulo some low level bytecode
* representation details (e.g. the order of the constants in the constant pool, the order of
* attributes and annotations, and low level details such as ldc vs ldc_w instructions).
*
* @param expectedClassFile a class file content which should be equal to the subject class.
*/
public void isEqualTo(final byte[] expectedClassFile) {
try {
String dump = new ClassDump(classFile).toString();
String expectedDump = new ClassDump(expectedClassFile).toString();
assertEquals(expectedDump, dump);
} catch (IOException | IllegalArgumentException e) {
fail("Class can't be dumped", e);
}
}
}
/**
* Loads the given class in a new class loader. Also tries to instantiate the loaded class (if it
* is not an abstract or enum class, or a module-info class), in order to check that it passes the
* bytecode verification step. Checks as well that the class can be dumped, to make sure that the
* class is well formed.
*
* @param className the name of the class to load.
* @param classContent the content of the class to load.
* @return whether the class was loaded successfully.
*/
public static boolean loadAndInstantiate(final String className, final byte[] classContent) {
try {
new ClassDump(classContent);
} catch (IOException | IllegalArgumentException e) {
fail("Class can't be dumped, probably invalid", e);
}
if (className.endsWith(MODULE_INFO)) {
if (getMajorJavaVersion() < 9) {
throw new UnsupportedClassVersionError();
} else {
return true;
}
} else {
return doLoadAndInstantiate(className, classContent);
}
}
/**
* Loads the given class in a new class loader. Also tries to instantiate the loaded class (if it
* is not an abstract or enum class), in order to check that it passes the bytecode verification
* step.
*
* @param className the name of the class to load.
* @param classContent the content of the class to load.
* @return whether the class was loaded successfully.
*/
static boolean doLoadAndInstantiate(final String className, final byte[] classContent) {
ByteClassLoader byteClassLoader = new ByteClassLoader(className, classContent);
try {
Class> clazz = byteClassLoader.loadClass(className);
if (!clazz.isEnum() && (clazz.getModifiers() & Modifier.ABSTRACT) == 0) {
Constructor> constructor = clazz.getDeclaredConstructors()[0];
ArrayList
© 2015 - 2025 Weber Informatics LLC | Privacy Policy