diff --git a/spring-core/src/main/java/org/springframework/asm/AnnotationVisitor.java b/spring-core/src/main/java/org/springframework/asm/AnnotationVisitor.java index 31fcb619139..cb99202b72b 100644 --- a/spring-core/src/main/java/org/springframework/asm/AnnotationVisitor.java +++ b/spring-core/src/main/java/org/springframework/asm/AnnotationVisitor.java @@ -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; } diff --git a/spring-core/src/main/java/org/springframework/asm/AnnotationWriter.java b/spring-core/src/main/java/org/springframework/asm/AnnotationWriter.java index b81e1917a20..0b29d380582 100644 --- a/spring-core/src/main/java/org/springframework/asm/AnnotationWriter.java +++ b/spring-core/src/main/java/org/springframework/asm/AnnotationWriter.java @@ -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; diff --git a/spring-core/src/main/java/org/springframework/asm/ClassReader.java b/spring-core/src/main/java/org/springframework/asm/ClassReader.java index f656d179a48..b2591c7fbb9 100644 --- a/spring-core/src/main/java/org/springframework/asm/ClassReader.java +++ b/spring-core/src/main/java/org/springframework/asm/ClassReader.java @@ -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 reverse order 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 reverse order 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) { diff --git a/spring-core/src/main/java/org/springframework/asm/ClassVisitor.java b/spring-core/src/main/java/org/springframework/asm/ClassVisitor.java index 463499cacce..856b77f0495 100644 --- a/spring-core/src/main/java/org/springframework/asm/ClassVisitor.java +++ b/spring-core/src/main/java/org/springframework/asm/ClassVisitor.java @@ -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 { } } + /** + * Experimental, use at your own risk. This method will be renamed when it becomes stable, this + * will break existing code using it. 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. * diff --git a/spring-core/src/main/java/org/springframework/asm/ClassWriter.java b/spring-core/src/main/java/org/springframework/asm/ClassWriter.java index e3d2dfd9464..60074927a55 100644 --- a/spring-core/src/main/java/org/springframework/asm/ClassWriter.java +++ b/spring-core/src/main/java/org/springframework/asm/ClassWriter.java @@ -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. */ 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(); } diff --git a/spring-core/src/main/java/org/springframework/asm/Constants.java b/spring-core/src/main/java/org/springframework/asm/Constants.java index 4713f09e5fe..f94f1197575 100644 --- a/spring-core/src/main/java/org/springframework/asm/Constants.java +++ b/spring-core/src/main/java/org/springframework/asm/Constants.java @@ -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"); + } + } } diff --git a/spring-core/src/main/java/org/springframework/asm/FieldVisitor.java b/spring-core/src/main/java/org/springframework/asm/FieldVisitor.java index 04d3d2e9222..bd17f86796d 100644 --- a/spring-core/src/main/java/org/springframework/asm/FieldVisitor.java +++ b/spring-core/src/main/java/org/springframework/asm/FieldVisitor.java @@ -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; } diff --git a/spring-core/src/main/java/org/springframework/asm/FieldWriter.java b/spring-core/src/main/java/org/springframework/asm/FieldWriter.java index 0b4ca89d7e3..ab8ad32ee59 100644 --- a/spring-core/src/main/java/org/springframework/asm/FieldWriter.java +++ b/spring-core/src/main/java/org/springframework/asm/FieldWriter.java @@ -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); diff --git a/spring-core/src/main/java/org/springframework/asm/MethodVisitor.java b/spring-core/src/main/java/org/springframework/asm/MethodVisitor.java index 0f987f07291..ab905e8084c 100644 --- a/spring-core/src/main/java/org/springframework/asm/MethodVisitor.java +++ b/spring-core/src/main/java/org/springframework/asm/MethodVisitor.java @@ -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) { diff --git a/spring-core/src/main/java/org/springframework/asm/MethodWriter.java b/spring-core/src/main/java/org/springframework/asm/MethodWriter.java index 39ab255be60..4cc0552dc85 100644 --- a/spring-core/src/main/java/org/springframework/asm/MethodWriter.java +++ b/spring-core/src/main/java/org/springframework/asm/MethodWriter.java @@ -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 = "".equals(name) ? access | Constants.ACC_CONSTRUCTOR : access; this.nameIndex = symbolTable.addConstantUtf8(name); diff --git a/spring-core/src/main/java/org/springframework/asm/ModuleVisitor.java b/spring-core/src/main/java/org/springframework/asm/ModuleVisitor.java index e8c2b062865..afdb37279f2 100644 --- a/spring-core/src/main/java/org/springframework/asm/ModuleVisitor.java +++ b/spring-core/src/main/java/org/springframework/asm/ModuleVisitor.java @@ -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; } diff --git a/spring-core/src/main/java/org/springframework/asm/ModuleWriter.java b/spring-core/src/main/java/org/springframework/asm/ModuleWriter.java index 8a54e652df7..e23e28cac47 100644 --- a/spring-core/src/main/java/org/springframework/asm/ModuleWriter.java +++ b/spring-core/src/main/java/org/springframework/asm/ModuleWriter.java @@ -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; diff --git a/spring-core/src/main/java/org/springframework/asm/Opcodes.java b/spring-core/src/main/java/org/springframework/asm/Opcodes.java index 90bd930fcc3..21fa7287b21 100644 --- a/spring-core/src/main/java/org/springframework/asm/Opcodes.java +++ b/spring-core/src/main/java/org/springframework/asm/Opcodes.java @@ -48,6 +48,14 @@ public interface Opcodes { int ASM6 = 6 << 16 | 0 << 8; int ASM7 = 7 << 16 | 0 << 8; + /** + * 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. + * + * @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. diff --git a/spring-core/src/main/java/org/springframework/asm/RecordComponentVisitor.java b/spring-core/src/main/java/org/springframework/asm/RecordComponentVisitor.java new file mode 100644 index 00000000000..4dfab5d1264 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/asm/RecordComponentVisitor.java @@ -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(); + } + } +} diff --git a/spring-core/src/main/java/org/springframework/asm/RecordComponentWriter.java b/spring-core/src/main/java/org/springframework/asm/RecordComponentWriter.java new file mode 100644 index 00000000000..c775fc4bd8f --- /dev/null +++ b/spring-core/src/main/java/org/springframework/asm/RecordComponentWriter.java @@ -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}. + * + *

WARNING: this list stores the attributes in the reverse 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 reverse 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); + } +}