Upgrade to ASM 7.3

Closes gh-24438
This commit is contained in:
Juergen Hoeller 2020-01-27 18:18:16 +01:00
parent c1218615df
commit 75abd9fc7e
15 changed files with 870 additions and 22 deletions

View File

@ -67,10 +67,18 @@ public abstract class AnnotationVisitor {
* @param annotationVisitor the annotation visitor to which this visitor must delegate method
* calls. May be {@literal null}.
*/
@SuppressWarnings("deprecation")
public AnnotationVisitor(final int api, final AnnotationVisitor annotationVisitor) {
if (api != Opcodes.ASM7 && api != Opcodes.ASM6 && api != Opcodes.ASM5 && api != Opcodes.ASM4) {
if (api != Opcodes.ASM7
&& api != Opcodes.ASM6
&& api != Opcodes.ASM5
&& api != Opcodes.ASM4
&& api != Opcodes.ASM8_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
if (api == Opcodes.ASM8_EXPERIMENTAL) {
Constants.checkAsm8Experimental(this);
}
this.api = api;
this.av = annotationVisitor;
}

View File

@ -112,7 +112,7 @@ final class AnnotationWriter extends AnnotationVisitor {
final boolean useNamedValues,
final ByteVector annotation,
final AnnotationWriter previousAnnotation) {
super(Opcodes.ASM7);
super(/* latest api = */ Opcodes.ASM7);
this.symbolTable = symbolTable;
this.useNamedValues = useNamedValues;
this.annotation = annotation;

View File

@ -50,10 +50,11 @@ public class ClassReader {
public static final int SKIP_CODE = 1;
/**
* A flag to skip the SourceFile, SourceDebugExtension, LocalVariableTable, LocalVariableTypeTable
* and LineNumberTable attributes. If this flag is set these attributes are neither parsed nor
* visited (i.e. {@link ClassVisitor#visitSource}, {@link MethodVisitor#visitLocalVariable} and
* {@link MethodVisitor#visitLineNumber} are not called).
* A flag to skip the SourceFile, SourceDebugExtension, LocalVariableTable,
* LocalVariableTypeTable, LineNumberTable and MethodParameters attributes. If this flag is set
* these attributes are neither parsed nor visited (i.e. {@link ClassVisitor#visitSource}, {@link
* MethodVisitor#visitLocalVariable}, {@link MethodVisitor#visitLineNumber} and {@link
* MethodVisitor#visitParameter} are not called).
*/
public static final int SKIP_DEBUG = 2;
@ -190,7 +191,7 @@ public class ClassReader {
this.b = classFileBuffer;
// Check the class' major_version. This field is after the magic and minor_version fields, which
// use 4 and 2 bytes respectively.
if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V14) {
if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V15) {
throw new IllegalArgumentException(
"Unsupported class file major version " + readShort(classFileOffset + 6));
}
@ -414,6 +415,7 @@ public class ClassReader {
* @param parsingOptions the options to use to parse this class. One or more of {@link
* #SKIP_CODE}, {@link #SKIP_DEBUG}, {@link #SKIP_FRAMES} or {@link #EXPAND_FRAMES}.
*/
@SuppressWarnings("deprecation")
public void accept(
final ClassVisitor classVisitor,
final Attribute[] attributePrototypes,
@ -466,6 +468,10 @@ public class ClassReader {
String nestHostClass = null;
// - The offset of the NestMembers attribute, or 0.
int nestMembersOffset = 0;
// - The offset of the PermittedSubtypes attribute, or 0
int permittedSubtypesOffset = 0;
// - The offset of the Record attribute, or 0.
int recordOffset = 0;
// - The non standard attributes (linked with their {@link Attribute#nextAttribute} field).
// This list in the <i>reverse order</i> or their order in the ClassFile structure.
Attribute attributes = null;
@ -488,6 +494,8 @@ public class ClassReader {
nestHostClass = readClass(currentAttributeOffset, charBuffer);
} else if (Constants.NEST_MEMBERS.equals(attributeName)) {
nestMembersOffset = currentAttributeOffset;
} else if (Constants.PERMITTED_SUBTYPES.equals(attributeName)) {
permittedSubtypesOffset = currentAttributeOffset;
} else if (Constants.SIGNATURE.equals(attributeName)) {
signature = readUTF8(currentAttributeOffset, charBuffer);
} else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) {
@ -505,6 +513,8 @@ public class ClassReader {
runtimeInvisibleAnnotationsOffset = currentAttributeOffset;
} else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
runtimeInvisibleTypeAnnotationsOffset = currentAttributeOffset;
} else if (Constants.RECORD.equals(attributeName)) {
recordOffset = currentAttributeOffset;
} else if (Constants.MODULE.equals(attributeName)) {
moduleOffset = currentAttributeOffset;
} else if (Constants.MODULE_MAIN_CLASS.equals(attributeName)) {
@ -662,6 +672,17 @@ public class ClassReader {
}
}
// Visit the PermittedSubtypes attribute.
if (permittedSubtypesOffset != 0) {
int numberOfPermittedSubtypes = readUnsignedShort(permittedSubtypesOffset);
int currentPermittedSubtypeOffset = permittedSubtypesOffset + 2;
while (numberOfPermittedSubtypes-- > 0) {
classVisitor.visitPermittedSubtypeExperimental(
readClass(currentPermittedSubtypeOffset, charBuffer));
currentPermittedSubtypeOffset += 2;
}
}
// Visit the InnerClasses attribute.
if (innerClassesOffset != 0) {
int numberOfClasses = readUnsignedShort(innerClassesOffset);
@ -676,6 +697,15 @@ public class ClassReader {
}
}
// Visit Record components.
if (recordOffset != 0) {
int recordComponentsCount = readUnsignedShort(recordOffset);
recordOffset += 2;
while (recordComponentsCount-- > 0) {
recordOffset = readRecordComponent(classVisitor, context, recordOffset);
}
}
// Visit the fields and methods.
int fieldsCount = readUnsignedShort(currentOffset);
currentOffset += 2;
@ -823,6 +853,186 @@ public class ClassReader {
moduleVisitor.visitEnd();
}
/**
* Reads a record component and visit it.
*
* @param classVisitor the current class visitor
* @param context information about the class being parsed.
* @param recordComponentOffset the offset of the current record component.
* @return the offset of the first byte following the record component.
*/
@SuppressWarnings("deprecation")
private int readRecordComponent(
final ClassVisitor classVisitor, final Context context, final int recordComponentOffset) {
char[] charBuffer = context.charBuffer;
int currentOffset = recordComponentOffset;
String name = readUTF8(currentOffset, charBuffer);
String descriptor = readUTF8(currentOffset + 2, charBuffer);
currentOffset += 4;
// Read the record component attributes (the variables are ordered as in Section 4.7 of the
// JVMS).
int accessFlags = 0;
// Attribute offsets exclude the attribute_name_index and attribute_length fields.
// - The string corresponding to the Signature attribute, or null.
String signature = null;
// - The offset of the RuntimeVisibleAnnotations attribute, or 0.
int runtimeVisibleAnnotationsOffset = 0;
// - The offset of the RuntimeInvisibleAnnotations attribute, or 0.
int runtimeInvisibleAnnotationsOffset = 0;
// - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0.
int runtimeVisibleTypeAnnotationsOffset = 0;
// - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0.
int runtimeInvisibleTypeAnnotationsOffset = 0;
// - The non standard attributes (linked with their {@link Attribute#nextAttribute} field).
// This list in the <i>reverse order</i> or their order in the ClassFile structure.
Attribute attributes = null;
int attributesCount = readUnsignedShort(currentOffset);
currentOffset += 2;
while (attributesCount-- > 0) {
// Read the attribute_info's attribute_name and attribute_length fields.
String attributeName = readUTF8(currentOffset, charBuffer);
int attributeLength = readInt(currentOffset + 2);
currentOffset += 6;
// The tests are sorted in decreasing frequency order (based on frequencies observed on
// typical classes).
if (Constants.SIGNATURE.equals(attributeName)) {
signature = readUTF8(currentOffset, charBuffer);
} else if (Constants.DEPRECATED.equals(attributeName)) {
accessFlags |= Opcodes.ACC_DEPRECATED;
} else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) {
runtimeVisibleAnnotationsOffset = currentOffset;
} else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
runtimeVisibleTypeAnnotationsOffset = currentOffset;
} else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) {
runtimeInvisibleAnnotationsOffset = currentOffset;
} else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
runtimeInvisibleTypeAnnotationsOffset = currentOffset;
} else {
Attribute attribute =
readAttribute(
context.attributePrototypes,
attributeName,
currentOffset,
attributeLength,
charBuffer,
-1,
null);
attribute.nextAttribute = attributes;
attributes = attribute;
}
currentOffset += attributeLength;
}
RecordComponentVisitor recordComponentVisitor =
classVisitor.visitRecordComponentExperimental(accessFlags, name, descriptor, signature);
if (recordComponentVisitor == null) {
return currentOffset;
}
// Visit the RuntimeVisibleAnnotations attribute.
if (runtimeVisibleAnnotationsOffset != 0) {
int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset);
int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2;
while (numAnnotations-- > 0) {
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
currentAnnotationOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
currentAnnotationOffset =
readElementValues(
recordComponentVisitor.visitAnnotationExperimental(
annotationDescriptor, /* visible = */ true),
currentAnnotationOffset,
/* named = */ true,
charBuffer);
}
}
// Visit the RuntimeInvisibleAnnotations attribute.
if (runtimeInvisibleAnnotationsOffset != 0) {
int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset);
int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2;
while (numAnnotations-- > 0) {
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
currentAnnotationOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
currentAnnotationOffset =
readElementValues(
recordComponentVisitor.visitAnnotationExperimental(
annotationDescriptor, /* visible = */ false),
currentAnnotationOffset,
/* named = */ true,
charBuffer);
}
}
// Visit the RuntimeVisibleTypeAnnotations attribute.
if (runtimeVisibleTypeAnnotationsOffset != 0) {
int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset);
int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2;
while (numAnnotations-- > 0) {
// Parse the target_type, target_info and target_path fields.
currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
currentAnnotationOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
currentAnnotationOffset =
readElementValues(
recordComponentVisitor.visitTypeAnnotationExperimental(
context.currentTypeAnnotationTarget,
context.currentTypeAnnotationTargetPath,
annotationDescriptor,
/* visible = */ true),
currentAnnotationOffset,
/* named = */ true,
charBuffer);
}
}
// Visit the RuntimeInvisibleTypeAnnotations attribute.
if (runtimeInvisibleTypeAnnotationsOffset != 0) {
int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset);
int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2;
while (numAnnotations-- > 0) {
// Parse the target_type, target_info and target_path fields.
currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
// Parse the type_index field.
String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
currentAnnotationOffset += 2;
// Parse num_element_value_pairs and element_value_pairs and visit these values.
currentAnnotationOffset =
readElementValues(
recordComponentVisitor.visitTypeAnnotationExperimental(
context.currentTypeAnnotationTarget,
context.currentTypeAnnotationTargetPath,
annotationDescriptor,
/* visible = */ false),
currentAnnotationOffset,
/* named = */ true,
charBuffer);
}
}
// Visit the non standard attributes.
while (attributes != null) {
// Copy and reset the nextAttribute field so that it can also be used in FieldWriter.
Attribute nextAttribute = attributes.nextAttribute;
attributes.nextAttribute = null;
recordComponentVisitor.visitAttributeExperimental(attributes);
attributes = nextAttribute;
}
// Visit the end of the field.
recordComponentVisitor.visitEndExperimental();
return currentOffset;
}
/**
* Reads a JVMS field_info structure and makes the given visitor visit it.
*
@ -1149,7 +1359,7 @@ public class ClassReader {
}
// Visit the MethodParameters attribute.
if (methodParametersOffset != 0) {
if (methodParametersOffset != 0 && (context.parsingOptions & SKIP_DEBUG) == 0) {
int parametersCount = readByte(methodParametersOffset);
int currentParameterOffset = methodParametersOffset + 1;
while (parametersCount-- > 0) {

View File

@ -30,9 +30,9 @@ package org.springframework.asm;
/**
* A visitor to visit a Java class. The methods of this class must be called in the following order:
* {@code visit} [ {@code visitSource} ] [ {@code visitModule} ][ {@code visitNestHost} ][ {@code
* visitOuterClass} ] ( {@code visitAnnotation} | {@code visitTypeAnnotation} | {@code
* visitAttribute} )* ( {@code visitNestMember} | {@code visitInnerClass} | {@code visitField} |
* {@code visitMethod} )* {@code visitEnd}.
* visitPermittedSubtype} ][ {@code visitOuterClass} ] ( {@code visitAnnotation} | {@code
* visitTypeAnnotation} | {@code visitAttribute} )* ( {@code visitNestMember} | {@code
* visitInnerClass} | {@code visitField} | {@code visitMethod} )* {@code visitEnd}.
*
* @author Eric Bruneton
*/
@ -65,10 +65,18 @@ public abstract class ClassVisitor {
* @param classVisitor the class visitor to which this visitor must delegate method calls. May be
* null.
*/
@SuppressWarnings("deprecation")
public ClassVisitor(final int api, final ClassVisitor classVisitor) {
if (api != Opcodes.ASM7 && api != Opcodes.ASM6 && api != Opcodes.ASM5 && api != Opcodes.ASM4) {
if (api != Opcodes.ASM7
&& api != Opcodes.ASM6
&& api != Opcodes.ASM5
&& api != Opcodes.ASM4
&& api != Opcodes.ASM8_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
if (api == Opcodes.ASM8_EXPERIMENTAL) {
Constants.checkAsm8Experimental(this);
}
this.api = api;
this.cv = classVisitor;
}
@ -240,6 +248,24 @@ public abstract class ClassVisitor {
}
}
/**
* <b>Experimental, use at your own risk. This method will be renamed when it becomes stable, this
* will break existing code using it</b>. Visits a permitted subtypes. A permitted subtypes is one
* of the allowed subtypes of the current class.
*
* @param permittedSubtype the internal name of a permitted subtype.
* @deprecated this API is experimental.
*/
@Deprecated
public void visitPermittedSubtypeExperimental(final String permittedSubtype) {
if (api != Opcodes.ASM8_EXPERIMENTAL) {
throw new UnsupportedOperationException("This feature requires ASM8_EXPERIMENTAL");
}
if (cv != null) {
cv.visitPermittedSubtypeExperimental(permittedSubtype);
}
}
/**
* Visits information about an inner class. This inner class is not necessarily a member of the
* class being visited.
@ -259,6 +285,31 @@ public abstract class ClassVisitor {
}
}
/**
* Visits a record component of the class.
*
* @param access the record component access flags, the only possible value is {@link
* Opcodes#ACC_DEPRECATED}.
* @param name the record component name.
* @param descriptor the record component descriptor (see {@link Type}).
* @param signature the record component signature. May be {@literal null} if the record component
* type does not use generic types.
* @return a visitor to visit this record component annotations and attributes, or {@literal null}
* if this class visitor is not interested in visiting these annotations and attributes.
* @deprecated this API is experimental.
*/
@Deprecated
public RecordComponentVisitor visitRecordComponentExperimental(
final int access, final String name, final String descriptor, final String signature) {
if (api < Opcodes.ASM8_EXPERIMENTAL) {
throw new UnsupportedOperationException("This feature requires ASM8_EXPERIMENTAL");
}
if (cv != null) {
return cv.visitRecordComponentExperimental(access, name, descriptor, signature);
}
return null;
}
/**
* Visits a field of the class.
*

View File

@ -177,6 +177,26 @@ public class ClassWriter extends ClassVisitor {
/** The 'classes' array of the NestMembers attribute, or {@literal null}. */
private ByteVector nestMemberClasses;
/** The number_of_classes field of the PermittedSubtypes attribute, or 0. */
private int numberOfPermittedSubtypeClasses;
/** The 'classes' array of the PermittedSubtypes attribute, or {@literal null}. */
private ByteVector permittedSubtypeClasses;
/**
* The record components of this class, stored in a linked list of {@link RecordComponentWriter}
* linked via their {@link RecordComponentWriter#delegate} field. This field stores the first
* element of this list.
*/
private RecordComponentWriter firstRecordComponent;
/**
* The record components of this class, stored in a linked list of {@link RecordComponentWriter}
* linked via their {@link RecordComponentWriter#delegate} field. This field stores the last
* element of this list.
*/
private RecordComponentWriter lastRecordComponent;
/**
* The first non standard attribute of this class. The next ones can be accessed with the {@link
* Attribute#nextAttribute} field. May be {@literal null}.
@ -234,7 +254,7 @@ public class ClassWriter extends ClassVisitor {
* maximum stack size nor the stack frames will be computed for these methods</i>.
*/
public ClassWriter(final ClassReader classReader, final int flags) {
super(Opcodes.ASM7);
super(/* latest api = */ Opcodes.ASM7);
symbolTable = classReader == null ? new SymbolTable(this) : new SymbolTable(this, classReader);
if ((flags & COMPUTE_FRAMES) != 0) {
this.compute = MethodWriter.COMPUTE_ALL_FRAMES;
@ -352,6 +372,16 @@ public class ClassWriter extends ClassVisitor {
nestMemberClasses.putShort(symbolTable.addConstantClass(nestMember).index);
}
@Override
@SuppressWarnings("deprecation")
public final void visitPermittedSubtypeExperimental(final String permittedSubtype) {
if (permittedSubtypeClasses == null) {
permittedSubtypeClasses = new ByteVector();
}
++numberOfPermittedSubtypeClasses;
permittedSubtypeClasses.putShort(symbolTable.addConstantClass(permittedSubtype).index);
}
@Override
public final void visitInnerClass(
final String name, final String outerName, final String innerName, final int access) {
@ -377,6 +407,20 @@ public class ClassWriter extends ClassVisitor {
// and throw an exception if there is a difference?
}
@Override
@SuppressWarnings("deprecation")
public final RecordComponentVisitor visitRecordComponentExperimental(
final int access, final String name, final String descriptor, final String signature) {
RecordComponentWriter recordComponentWriter =
new RecordComponentWriter(symbolTable, access, name, descriptor, signature);
if (firstRecordComponent == null) {
firstRecordComponent = recordComponentWriter;
} else {
lastRecordComponent.delegate = recordComponentWriter;
}
return lastRecordComponent = recordComponentWriter;
}
@Override
public final FieldVisitor visitField(
final int access,
@ -447,6 +491,7 @@ public class ClassWriter extends ClassVisitor {
size += methodWriter.computeMethodInfoSize();
methodWriter = (MethodWriter) methodWriter.mv;
}
// For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
int attributesCount = 0;
if (innerClasses != null) {
@ -526,6 +571,24 @@ public class ClassWriter extends ClassVisitor {
size += 8 + nestMemberClasses.length;
symbolTable.addConstantUtf8(Constants.NEST_MEMBERS);
}
if (permittedSubtypeClasses != null) {
++attributesCount;
size += 8 + permittedSubtypeClasses.length;
symbolTable.addConstantUtf8(Constants.PERMITTED_SUBTYPES);
}
int recordComponentCount = 0;
int recordSize = 0;
if (firstRecordComponent != null) {
RecordComponentWriter recordComponentWriter = firstRecordComponent;
while (recordComponentWriter != null) {
++recordComponentCount;
recordSize += recordComponentWriter.computeRecordComponentInfoSize();
recordComponentWriter = (RecordComponentWriter) recordComponentWriter.delegate;
}
++attributesCount;
size += 8 + recordSize;
symbolTable.addConstantUtf8(Constants.RECORD);
}
if (firstAttribute != null) {
attributesCount += firstAttribute.getAttributeCount();
size += firstAttribute.computeAttributesSize(symbolTable);
@ -630,6 +693,24 @@ public class ClassWriter extends ClassVisitor {
.putShort(numberOfNestMemberClasses)
.putByteArray(nestMemberClasses.data, 0, nestMemberClasses.length);
}
if (permittedSubtypeClasses != null) {
result
.putShort(symbolTable.addConstantUtf8(Constants.PERMITTED_SUBTYPES))
.putInt(permittedSubtypeClasses.length + 2)
.putShort(numberOfPermittedSubtypeClasses)
.putByteArray(permittedSubtypeClasses.data, 0, permittedSubtypeClasses.length);
}
if (firstRecordComponent != null) {
result
.putShort(symbolTable.addConstantUtf8(Constants.RECORD))
.putInt(recordSize + 2)
.putShort(recordComponentCount);
RecordComponentWriter recordComponentWriter = firstRecordComponent;
while (recordComponentWriter != null) {
recordComponentWriter.putRecordComponentInfo(result);
recordComponentWriter = (RecordComponentWriter) recordComponentWriter.delegate;
}
}
if (firstAttribute != null) {
firstAttribute.putAttributes(symbolTable, result);
}
@ -666,6 +747,10 @@ public class ClassWriter extends ClassVisitor {
nestHostClassIndex = 0;
numberOfNestMemberClasses = 0;
nestMemberClasses = null;
numberOfPermittedSubtypeClasses = 0;
permittedSubtypeClasses = null;
firstRecordComponent = null;
lastRecordComponent = null;
firstAttribute = null;
compute = hasFrames ? MethodWriter.COMPUTE_INSERTED_FRAMES : MethodWriter.COMPUTE_NOTHING;
new ClassReader(classFile, 0, /* checkClassVersion = */ false)
@ -694,6 +779,11 @@ public class ClassWriter extends ClassVisitor {
methodWriter.collectAttributePrototypes(attributePrototypes);
methodWriter = (MethodWriter) methodWriter.mv;
}
RecordComponentWriter recordComponentWriter = firstRecordComponent;
while (recordComponentWriter != null) {
recordComponentWriter.collectAttributePrototypes(attributePrototypes);
recordComponentWriter = (RecordComponentWriter) recordComponentWriter.delegate;
}
return attributePrototypes.toArray();
}

View File

@ -27,6 +27,11 @@
// THE POSSIBILITY OF SUCH DAMAGE.
package org.springframework.asm;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.regex.Pattern;
/**
* Defines additional JVM opcodes, access flags and constants which are not part of the ASM public
* API.
@ -56,7 +61,8 @@ final class Constants implements Opcodes {
static final String RUNTIME_VISIBLE_ANNOTATIONS = "RuntimeVisibleAnnotations";
static final String RUNTIME_INVISIBLE_ANNOTATIONS = "RuntimeInvisibleAnnotations";
static final String RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS = "RuntimeVisibleParameterAnnotations";
static final String RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS = "RuntimeInvisibleParameterAnnotations";
static final String RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS =
"RuntimeInvisibleParameterAnnotations";
static final String RUNTIME_VISIBLE_TYPE_ANNOTATIONS = "RuntimeVisibleTypeAnnotations";
static final String RUNTIME_INVISIBLE_TYPE_ANNOTATIONS = "RuntimeInvisibleTypeAnnotations";
static final String ANNOTATION_DEFAULT = "AnnotationDefault";
@ -67,6 +73,8 @@ final class Constants implements Opcodes {
static final String MODULE_MAIN_CLASS = "ModuleMainClass";
static final String NEST_HOST = "NestHost";
static final String NEST_MEMBERS = "NestMembers";
static final String PERMITTED_SUBTYPES = "PermittedSubtypes";
static final String RECORD = "Record";
// ASM specific access flags.
// WARNING: the 16 least significant bits must NOT be used, to avoid conflicts with standard
@ -173,4 +181,41 @@ final class Constants implements Opcodes {
static final int ASM_GOTO_W = 220;
private Constants() {}
static void checkAsm8Experimental(final Object caller) {
Class<?> callerClass = caller.getClass();
String internalName = callerClass.getName().replace('.', '/');
if (!isWhitelisted(internalName)) {
checkIsPreview(callerClass.getClassLoader().getResourceAsStream(internalName + ".class"));
}
}
static boolean isWhitelisted(final String internalName) {
if (!internalName.startsWith("org/objectweb/asm/")) {
return false;
}
String member = "(Annotation|Class|Field|Method|Module|RecordComponent|Signature)";
return internalName.contains("Test$")
|| Pattern.matches(
"org/objectweb/asm/util/Trace" + member + "Visitor(\\$.*)?", internalName)
|| Pattern.matches(
"org/objectweb/asm/util/Check" + member + "Adapter(\\$.*)?", internalName);
}
static void checkIsPreview(final InputStream classInputStream) {
if (classInputStream == null) {
throw new IllegalStateException("Bytecode not available, can't check class version");
}
int minorVersion;
try (DataInputStream callerClassStream = new DataInputStream(classInputStream); ) {
callerClassStream.readInt();
minorVersion = callerClassStream.readUnsignedShort();
} catch (IOException ioe) {
throw new IllegalStateException("I/O error, can't check class version", ioe);
}
if (minorVersion != 0xFFFF) {
throw new IllegalStateException(
"ASM8_EXPERIMENTAL can only be used by classes compiled with --enable-preview");
}
}
}

View File

@ -63,10 +63,18 @@ public abstract class FieldVisitor {
* @param fieldVisitor the field visitor to which this visitor must delegate method calls. May be
* null.
*/
@SuppressWarnings("deprecation")
public FieldVisitor(final int api, final FieldVisitor fieldVisitor) {
if (api != Opcodes.ASM7 && api != Opcodes.ASM6 && api != Opcodes.ASM5 && api != Opcodes.ASM4) {
if (api != Opcodes.ASM7
&& api != Opcodes.ASM6
&& api != Opcodes.ASM5
&& api != Opcodes.ASM4
&& api != Opcodes.ASM8_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
if (api == Opcodes.ASM8_EXPERIMENTAL) {
Constants.checkAsm8Experimental(this);
}
this.api = api;
this.fv = fieldVisitor;
}

View File

@ -124,7 +124,7 @@ final class FieldWriter extends FieldVisitor {
final String descriptor,
final String signature,
final Object constantValue) {
super(Opcodes.ASM7);
super(/* latest api = */ Opcodes.ASM7);
this.symbolTable = symbolTable;
this.accessFlags = access;
this.nameIndex = symbolTable.addConstantUtf8(name);

View File

@ -79,10 +79,18 @@ public abstract class MethodVisitor {
* @param methodVisitor the method visitor to which this visitor must delegate method calls. May
* be null.
*/
@SuppressWarnings("deprecation")
public MethodVisitor(final int api, final MethodVisitor methodVisitor) {
if (api != Opcodes.ASM7 && api != Opcodes.ASM6 && api != Opcodes.ASM5 && api != Opcodes.ASM4) {
if (api != Opcodes.ASM7
&& api != Opcodes.ASM6
&& api != Opcodes.ASM5
&& api != Opcodes.ASM4
&& api != Opcodes.ASM8_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
if (api == Opcodes.ASM8_EXPERIMENTAL) {
Constants.checkAsm8Experimental(this);
}
this.api = api;
this.mv = methodVisitor;
}
@ -534,7 +542,7 @@ public abstract class MethodVisitor {
|| (value instanceof Type && ((Type) value).getSort() == Type.METHOD))) {
throw new UnsupportedOperationException(REQUIRES_ASM5);
}
if (api != Opcodes.ASM7 && value instanceof ConstantDynamic) {
if (api < Opcodes.ASM7 && value instanceof ConstantDynamic) {
throw new UnsupportedOperationException("This feature requires ASM7");
}
if (mv != null) {

View File

@ -592,7 +592,7 @@ final class MethodWriter extends MethodVisitor {
final String signature,
final String[] exceptions,
final int compute) {
super(Opcodes.ASM7);
super(/* latest api = */ Opcodes.ASM7);
this.symbolTable = symbolTable;
this.accessFlags = "<init>".equals(name) ? access | Constants.ACC_CONSTRUCTOR : access;
this.nameIndex = symbolTable.addConstantUtf8(name);

View File

@ -65,10 +65,18 @@ public abstract class ModuleVisitor {
* @param moduleVisitor the module visitor to which this visitor must delegate method calls. May
* be null.
*/
@SuppressWarnings("deprecation")
public ModuleVisitor(final int api, final ModuleVisitor moduleVisitor) {
if (api != Opcodes.ASM7 && api != Opcodes.ASM6) {
if (api != Opcodes.ASM7
&& api != Opcodes.ASM6
&& api != Opcodes.ASM5
&& api != Opcodes.ASM4
&& api != Opcodes.ASM8_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
if (api == Opcodes.ASM8_EXPERIMENTAL) {
Constants.checkAsm8Experimental(this);
}
this.api = api;
this.mv = moduleVisitor;
}

View File

@ -94,7 +94,7 @@ final class ModuleWriter extends ModuleVisitor {
private int mainClassIndex;
ModuleWriter(final SymbolTable symbolTable, final int name, final int access, final int version) {
super(Opcodes.ASM7);
super(/* latest api = */ Opcodes.ASM7);
this.symbolTable = symbolTable;
this.moduleNameIndex = name;
this.moduleFlags = access;

View File

@ -48,6 +48,14 @@ public interface Opcodes {
int ASM6 = 6 << 16 | 0 << 8;
int ASM7 = 7 << 16 | 0 << 8;
/**
* <i>Experimental, use at your own risk. This field will be renamed when it becomes stable, this
* will break existing code using it. Only code compiled with --enable-preview can use this.</i>
*
* @deprecated This API is experimental.
*/
@Deprecated int ASM8_EXPERIMENTAL = 1 << 24 | 8 << 16 | 0 << 8;
/*
* Internal flags used to redirect calls to deprecated methods. For instance, if a visitOldStuff
* method in API_OLD is deprecated and replaced with visitNewStuff in API_NEW, then the
@ -270,6 +278,7 @@ public interface Opcodes {
int V12 = 0 << 16 | 56;
int V13 = 0 << 16 | 57;
int V14 = 0 << 16 | 58;
int V15 = 0 << 16 | 59;
/**
* Version flag indicating that the class is using 'preview' features.
@ -306,7 +315,7 @@ public interface Opcodes {
int ACC_SYNTHETIC = 0x1000; // class, field, method, parameter, module *
int ACC_ANNOTATION = 0x2000; // class
int ACC_ENUM = 0x4000; // class(?) field inner
int ACC_MANDATED = 0x8000; // parameter, module, module *
int ACC_MANDATED = 0x8000; // field, method, parameter, module, module *
int ACC_MODULE = 0x8000; // class
// ASM specific access flags.

View File

@ -0,0 +1,169 @@
// 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.springframework.asm;
/**
* A visitor to visit a record component. The methods of this class must be called in the following
* order: ( {@code visitAnnotation} | {@code visitTypeAnnotation} | {@code visitAttribute} )* {@code
* visitEnd}.
*
* @author Remi Forax
* @author Eric Bruneton
* @deprecated this API is experimental.
*/
@Deprecated
public abstract class RecordComponentVisitor {
/**
* The ASM API version implemented by this visitor. The value of this field must be {@link
* Opcodes#ASM8_EXPERIMENTAL}.
*/
protected final int api;
/**
* The record visitor to which this visitor must delegate method calls. May be {@literal null}.
*/
/*package-private*/ RecordComponentVisitor delegate;
/**
* Constructs a new {@link RecordComponentVisitor}.
*
* @param api the ASM API version implemented by this visitor. Must be {@link
* Opcodes#ASM8_EXPERIMENTAL}.
* @deprecated this API is experimental.
*/
@Deprecated
public RecordComponentVisitor(final int api) {
this(api, null);
}
/**
* Constructs a new {@link RecordComponentVisitor}.
*
* @param api the ASM API version implemented by this visitor. Must be {@link
* Opcodes#ASM8_EXPERIMENTAL}.
* @param recordComponentVisitor the record component visitor to which this visitor must delegate
* method calls. May be null.
* @deprecated this API is experimental.
*/
@Deprecated
public RecordComponentVisitor(
final int api, final RecordComponentVisitor recordComponentVisitor) {
if (api != Opcodes.ASM7
&& api != Opcodes.ASM6
&& api != Opcodes.ASM5
&& api != Opcodes.ASM4
&& api != Opcodes.ASM8_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
if (api == Opcodes.ASM8_EXPERIMENTAL) {
Constants.checkAsm8Experimental(this);
}
this.api = api;
this.delegate = recordComponentVisitor;
}
/**
* The record visitor to which this visitor must delegate method calls. May be {@literal null}.
*
* @return the record visitor to which this visitor must delegate method calls or {@literal null}.
* @deprecated this API is experimental.
*/
@Deprecated
public RecordComponentVisitor getDelegateExperimental() {
return delegate;
}
/**
* Visits an annotation of the record component.
*
* @param descriptor the class descriptor of the annotation class.
* @param visible {@literal true} if the annotation is visible at runtime.
* @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
* interested in visiting this annotation.
* @deprecated this API is experimental.
*/
@Deprecated
public AnnotationVisitor visitAnnotationExperimental(
final String descriptor, final boolean visible) {
if (delegate != null) {
return delegate.visitAnnotationExperimental(descriptor, visible);
}
return null;
}
/**
* Visits an annotation on a type in the record component signature.
*
* @param typeRef a reference to the annotated type. The sort of this type reference must be
* {@link TypeReference#CLASS_TYPE_PARAMETER}, {@link
* TypeReference#CLASS_TYPE_PARAMETER_BOUND} or {@link TypeReference#CLASS_EXTENDS}. See
* {@link TypeReference}.
* @param typePath the path to the annotated type argument, wildcard bound, array element type, or
* static inner type within 'typeRef'. May be {@literal null} if the annotation targets
* 'typeRef' as a whole.
* @param descriptor the class descriptor of the annotation class.
* @param visible {@literal true} if the annotation is visible at runtime.
* @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
* interested in visiting this annotation.
* @deprecated this API is experimental.
*/
@Deprecated
public AnnotationVisitor visitTypeAnnotationExperimental(
final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
if (delegate != null) {
return delegate.visitTypeAnnotationExperimental(typeRef, typePath, descriptor, visible);
}
return null;
}
/**
* Visits a non standard attribute of the record component.
*
* @param attribute an attribute.
* @deprecated this API is experimental.
*/
@Deprecated
public void visitAttributeExperimental(final Attribute attribute) {
if (delegate != null) {
delegate.visitAttributeExperimental(attribute);
}
}
/**
* Visits the end of the record component. This method, which is the last one to be called, is
* used to inform the visitor that everything have been visited.
*
* @deprecated this API is experimental.
*/
@Deprecated
public void visitEndExperimental() {
if (delegate != null) {
delegate.visitEndExperimental();
}
}
}

View File

@ -0,0 +1,242 @@
// 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.springframework.asm;
@SuppressWarnings("deprecation")
final class RecordComponentWriter extends RecordComponentVisitor {
/** Where the constants used in this RecordComponentWriter must be stored. */
private final SymbolTable symbolTable;
// Note: fields are ordered as in the component_info structure, and those related to attributes
// are ordered as in Section TODO of the JVMS.
// The field accessFlag doesn't exist in the component_info structure but is used to carry
// ACC_DEPRECATED which is represented by an attribute in the structure and as an access flag by
// ASM.
/** The access_flags field can only be {@link Opcodes#ACC_DEPRECATED}. */
private final int accessFlags;
/** The name_index field of the Record attribute. */
private final int nameIndex;
/** The descriptor_index field of the the Record attribute. */
private final int descriptorIndex;
/**
* The signature_index field of the Signature attribute of this record component, or 0 if there is
* no Signature attribute.
*/
private int signatureIndex;
/**
* The last runtime visible annotation of this record component. The previous ones can be accessed
* with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
*/
private AnnotationWriter lastRuntimeVisibleAnnotation;
/**
* The last runtime invisible annotation of this record component. The previous ones can be
* accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
*/
private AnnotationWriter lastRuntimeInvisibleAnnotation;
/**
* The last runtime visible type annotation of this record component. The previous ones can be
* accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
*/
private AnnotationWriter lastRuntimeVisibleTypeAnnotation;
/**
* The last runtime invisible type annotation of this record component. The previous ones can be
* accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
*/
private AnnotationWriter lastRuntimeInvisibleTypeAnnotation;
/**
* The first non standard attribute of this record component. The next ones can be accessed with
* the {@link Attribute#nextAttribute} field. May be {@literal null}.
*
* <p><b>WARNING</b>: this list stores the attributes in the <i>reverse</i> order of their visit.
* firstAttribute is actually the last attribute visited in {@link
* #visitAttributeExperimental(Attribute)}. The {@link #putRecordComponentInfo(ByteVector)} method
* writes the attributes in the order defined by this list, i.e. in the reverse order specified by
* the user.
*/
private Attribute firstAttribute;
/**
* Constructs a new {@link RecordComponentWriter}.
*
* @param symbolTable where the constants used in this RecordComponentWriter must be stored.
* @param accessFlags the record component access flags, only synthetic and/or deprecated.
* @param name the record component name.
* @param descriptor the record component descriptor (see {@link Type}).
* @param signature the record component signature. May be {@literal null}.
*/
RecordComponentWriter(
final SymbolTable symbolTable,
final int accessFlags,
final String name,
final String descriptor,
final String signature) {
super(/* latest api = */ Opcodes.ASM7);
this.symbolTable = symbolTable;
this.accessFlags = accessFlags;
this.nameIndex = symbolTable.addConstantUtf8(name);
this.descriptorIndex = symbolTable.addConstantUtf8(descriptor);
if (signature != null) {
this.signatureIndex = symbolTable.addConstantUtf8(signature);
}
}
// -----------------------------------------------------------------------------------------------
// Implementation of the FieldVisitor abstract class
// -----------------------------------------------------------------------------------------------
@Override
public AnnotationVisitor visitAnnotationExperimental(
final String descriptor, final boolean visible) {
if (visible) {
return lastRuntimeVisibleAnnotation =
AnnotationWriter.create(symbolTable, descriptor, lastRuntimeVisibleAnnotation);
} else {
return lastRuntimeInvisibleAnnotation =
AnnotationWriter.create(symbolTable, descriptor, lastRuntimeInvisibleAnnotation);
}
}
@Override
public AnnotationVisitor visitTypeAnnotationExperimental(
final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
if (visible) {
return lastRuntimeVisibleTypeAnnotation =
AnnotationWriter.create(
symbolTable, typeRef, typePath, descriptor, lastRuntimeVisibleTypeAnnotation);
} else {
return lastRuntimeInvisibleTypeAnnotation =
AnnotationWriter.create(
symbolTable, typeRef, typePath, descriptor, lastRuntimeInvisibleTypeAnnotation);
}
}
@Override
public void visitAttributeExperimental(final Attribute attribute) {
// Store the attributes in the <i>reverse</i> order of their visit by this method.
attribute.nextAttribute = firstAttribute;
firstAttribute = attribute;
}
@Override
public void visitEndExperimental() {
// Nothing to do.
}
// -----------------------------------------------------------------------------------------------
// Utility methods
// -----------------------------------------------------------------------------------------------
/**
* Returns the size of the record component JVMS structure generated by this
* RecordComponentWriter. Also adds the names of the attributes of this record component in the
* constant pool.
*
* @return the size in bytes of the record_component_info of the Record attribute.
*/
int computeRecordComponentInfoSize() {
// name_index, descriptor_index and attributes_count fields use 6 bytes.
int size = 6;
size +=
Attribute.computeAttributesSize(
symbolTable, accessFlags & Opcodes.ACC_DEPRECATED, signatureIndex);
size +=
AnnotationWriter.computeAnnotationsSize(
lastRuntimeVisibleAnnotation,
lastRuntimeInvisibleAnnotation,
lastRuntimeVisibleTypeAnnotation,
lastRuntimeInvisibleTypeAnnotation);
if (firstAttribute != null) {
size += firstAttribute.computeAttributesSize(symbolTable);
}
return size;
}
/**
* Puts the content of the record component generated by this RecordComponentWriter into the given
* ByteVector.
*
* @param output where the record_component_info structure must be put.
*/
void putRecordComponentInfo(final ByteVector output) {
output.putShort(nameIndex).putShort(descriptorIndex);
// Compute and put the attributes_count field.
// For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
int attributesCount = 0;
if (signatureIndex != 0) {
++attributesCount;
}
if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) {
++attributesCount;
}
if (lastRuntimeVisibleAnnotation != null) {
++attributesCount;
}
if (lastRuntimeInvisibleAnnotation != null) {
++attributesCount;
}
if (lastRuntimeVisibleTypeAnnotation != null) {
++attributesCount;
}
if (lastRuntimeInvisibleTypeAnnotation != null) {
++attributesCount;
}
if (firstAttribute != null) {
attributesCount += firstAttribute.getAttributeCount();
}
output.putShort(attributesCount);
Attribute.putAttributes(symbolTable, accessFlags, signatureIndex, output);
AnnotationWriter.putAnnotations(
symbolTable,
lastRuntimeVisibleAnnotation,
lastRuntimeInvisibleAnnotation,
lastRuntimeVisibleTypeAnnotation,
lastRuntimeInvisibleTypeAnnotation,
output);
if (firstAttribute != null) {
firstAttribute.putAttributes(symbolTable, output);
}
}
/**
* Collects the attributes of this record component into the given set of attribute prototypes.
*
* @param attributePrototypes a set of attribute prototypes.
*/
final void collectAttributePrototypes(final Attribute.Set attributePrototypes) {
attributePrototypes.addAttributes(firstAttribute);
}
}