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 e9111fce4059..31fcb619139a 100644 --- a/spring-core/src/main/java/org/springframework/asm/AnnotationVisitor.java +++ b/spring-core/src/main/java/org/springframework/asm/AnnotationVisitor.java @@ -43,7 +43,10 @@ public abstract class AnnotationVisitor { */ protected final int api; - /** The annotation visitor to which this visitor must delegate method calls. May be null. */ + /** + * The annotation visitor to which this visitor must delegate method calls. May be {@literal + * null}. + */ protected AnnotationVisitor av; /** @@ -62,11 +65,11 @@ public AnnotationVisitor(final int api) { * @param api the ASM API version implemented by this visitor. Must be one of {@link * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}. * @param annotationVisitor the annotation visitor to which this visitor must delegate method - * calls. May be null. + * calls. May be {@literal null}. */ public AnnotationVisitor(final int api, final AnnotationVisitor annotationVisitor) { - if (api != Opcodes.ASM6 && api != Opcodes.ASM5 && api != Opcodes.ASM4 && api != Opcodes.ASM7) { - throw new IllegalArgumentException(); + if (api != Opcodes.ASM7 && api != Opcodes.ASM6 && api != Opcodes.ASM5 && api != Opcodes.ASM4) { + throw new IllegalArgumentException("Unsupported api " + api); } 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 b85e792a466d..b81e1917a20a 100644 --- a/spring-core/src/main/java/org/springframework/asm/AnnotationWriter.java +++ b/spring-core/src/main/java/org/springframework/asm/AnnotationWriter.java @@ -91,7 +91,7 @@ final class AnnotationWriter extends AnnotationVisitor { private AnnotationWriter nextAnnotation; // ----------------------------------------------------------------------------------------------- - // Constructors + // Constructors and factories // ----------------------------------------------------------------------------------------------- /** @@ -104,8 +104,8 @@ final class AnnotationWriter extends AnnotationVisitor { * the visited content must be stored. This ByteVector must already contain all the fields of * the structure except the last one (the element_value_pairs array). * @param previousAnnotation the previously visited annotation of the - * Runtime[In]Visible[Type]Annotations attribute to which this annotation belongs, or null in - * other cases (e.g. nested or array annotations). + * Runtime[In]Visible[Type]Annotations attribute to which this annotation belongs, or + * {@literal null} in other cases (e.g. nested or array annotations). */ AnnotationWriter( final SymbolTable symbolTable, @@ -125,21 +125,61 @@ final class AnnotationWriter extends AnnotationVisitor { } /** - * Constructs a new {@link AnnotationWriter} using named values. + * Creates a new {@link AnnotationWriter} using named values. * * @param symbolTable where the constants used in this AnnotationWriter must be stored. - * @param annotation where the 'annotation' or 'type_annotation' JVMS structure corresponding to - * the visited content must be stored. This ByteVector must already contain all the fields of - * the structure except the last one (the element_value_pairs array). + * @param descriptor the class descriptor of the annotation class. * @param previousAnnotation the previously visited annotation of the - * Runtime[In]Visible[Type]Annotations attribute to which this annotation belongs, or null in - * other cases (e.g. nested or array annotations). + * Runtime[In]Visible[Type]Annotations attribute to which this annotation belongs, or + * {@literal null} in other cases (e.g. nested or array annotations). + * @return a new {@link AnnotationWriter} for the given annotation descriptor. */ - AnnotationWriter( + static AnnotationWriter create( final SymbolTable symbolTable, - final ByteVector annotation, + final String descriptor, + final AnnotationWriter previousAnnotation) { + // Create a ByteVector to hold an 'annotation' JVMS structure. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16. + ByteVector annotation = new ByteVector(); + // Write type_index and reserve space for num_element_value_pairs. + annotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0); + return new AnnotationWriter( + symbolTable, /* useNamedValues = */ true, annotation, previousAnnotation); + } + + /** + * Creates a new {@link AnnotationWriter} using named values. + * + * @param symbolTable where the constants used in this AnnotationWriter must be stored. + * @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 previousAnnotation the previously visited annotation of the + * Runtime[In]Visible[Type]Annotations attribute to which this annotation belongs, or + * {@literal null} in other cases (e.g. nested or array annotations). + * @return a new {@link AnnotationWriter} for the given type annotation reference and descriptor. + */ + static AnnotationWriter create( + final SymbolTable symbolTable, + final int typeRef, + final TypePath typePath, + final String descriptor, final AnnotationWriter previousAnnotation) { - this(symbolTable, /* useNamedValues = */ true, annotation, previousAnnotation); + // Create a ByteVector to hold a 'type_annotation' JVMS structure. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20. + ByteVector typeAnnotation = new ByteVector(); + // Write target_type, target_info, and target_path. + TypeReference.putTarget(typeRef, typeAnnotation); + TypePath.put(typePath, typeAnnotation); + // Write type_index and reserve space for num_element_value_pairs. + typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0); + return new AnnotationWriter( + symbolTable, /* useNamedValues = */ true, typeAnnotation, previousAnnotation); } // ----------------------------------------------------------------------------------------------- @@ -244,7 +284,7 @@ public AnnotationVisitor visitAnnotation(final String name, final String descrip } // Write tag and type_index, and reserve 2 bytes for num_element_value_pairs. annotation.put12('@', symbolTable.addConstantUtf8(descriptor)).putShort(0); - return new AnnotationWriter(symbolTable, annotation, null); + return new AnnotationWriter(symbolTable, /* useNamedValues = */ true, annotation, null); } @Override @@ -284,7 +324,7 @@ public void visitEnd() { * and all its predecessors (see {@link #previousAnnotation}. Also adds the attribute name * to the constant pool of the class (if not null). * - * @param attributeName one of "Runtime[In]Visible[Type]Annotations", or null. + * @param attributeName one of "Runtime[In]Visible[Type]Annotations", or {@literal null}. * @return the size in bytes of a Runtime[In]Visible[Type]Annotations attribute containing this * annotation and all its predecessors. This includes the size of the attribute_name_index and * attribute_length fields. @@ -303,6 +343,56 @@ int computeAnnotationsSize(final String attributeName) { return attributeSize; } + /** + * Returns the size of the Runtime[In]Visible[Type]Annotations attributes containing the given + * annotations and all their predecessors (see {@link #previousAnnotation}. Also adds the + * attribute names to the constant pool of the class (if not null). + * + * @param lastRuntimeVisibleAnnotation The last runtime visible annotation of a field, method or + * class. The previous ones can be accessed with the {@link #previousAnnotation} field. May be + * {@literal null}. + * @param lastRuntimeInvisibleAnnotation The last runtime invisible annotation of this a field, + * method or class. The previous ones can be accessed with the {@link #previousAnnotation} + * field. May be {@literal null}. + * @param lastRuntimeVisibleTypeAnnotation The last runtime visible type annotation of this a + * field, method or class. The previous ones can be accessed with the {@link + * #previousAnnotation} field. May be {@literal null}. + * @param lastRuntimeInvisibleTypeAnnotation The last runtime invisible type annotation of a + * field, method or class field. The previous ones can be accessed with the {@link + * #previousAnnotation} field. May be {@literal null}. + * @return the size in bytes of a Runtime[In]Visible[Type]Annotations attribute containing the + * given annotations and all their predecessors. This includes the size of the + * attribute_name_index and attribute_length fields. + */ + static int computeAnnotationsSize( + final AnnotationWriter lastRuntimeVisibleAnnotation, + final AnnotationWriter lastRuntimeInvisibleAnnotation, + final AnnotationWriter lastRuntimeVisibleTypeAnnotation, + final AnnotationWriter lastRuntimeInvisibleTypeAnnotation) { + int size = 0; + if (lastRuntimeVisibleAnnotation != null) { + size += + lastRuntimeVisibleAnnotation.computeAnnotationsSize( + Constants.RUNTIME_VISIBLE_ANNOTATIONS); + } + if (lastRuntimeInvisibleAnnotation != null) { + size += + lastRuntimeInvisibleAnnotation.computeAnnotationsSize( + Constants.RUNTIME_INVISIBLE_ANNOTATIONS); + } + if (lastRuntimeVisibleTypeAnnotation != null) { + size += + lastRuntimeVisibleTypeAnnotation.computeAnnotationsSize( + Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS); + } + if (lastRuntimeInvisibleTypeAnnotation != null) { + size += + lastRuntimeInvisibleTypeAnnotation.computeAnnotationsSize( + Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS); + } + return size; + } + /** * Puts a Runtime[In]Visible[Type]Annotations attribute containing this annotations and all its * predecessors (see {@link #previousAnnotation} in the given ByteVector. Annotations are @@ -335,6 +425,51 @@ void putAnnotations(final int attributeNameIndex, final ByteVector output) { } } + /** + * Puts the Runtime[In]Visible[Type]Annotations attributes containing the given annotations and + * all their predecessors (see {@link #previousAnnotation} in the given ByteVector. + * Annotations are put in the same order they have been visited. + * + * @param symbolTable where the constants used in the AnnotationWriter instances are stored. + * @param lastRuntimeVisibleAnnotation The last runtime visible annotation of a field, method or + * class. The previous ones can be accessed with the {@link #previousAnnotation} field. May be + * {@literal null}. + * @param lastRuntimeInvisibleAnnotation The last runtime invisible annotation of this a field, + * method or class. The previous ones can be accessed with the {@link #previousAnnotation} + * field. May be {@literal null}. + * @param lastRuntimeVisibleTypeAnnotation The last runtime visible type annotation of this a + * field, method or class. The previous ones can be accessed with the {@link + * #previousAnnotation} field. May be {@literal null}. + * @param lastRuntimeInvisibleTypeAnnotation The last runtime invisible type annotation of a + * field, method or class field. The previous ones can be accessed with the {@link + * #previousAnnotation} field. May be {@literal null}. + * @param output where the attributes must be put. + */ + static void putAnnotations( + final SymbolTable symbolTable, + final AnnotationWriter lastRuntimeVisibleAnnotation, + final AnnotationWriter lastRuntimeInvisibleAnnotation, + final AnnotationWriter lastRuntimeVisibleTypeAnnotation, + final AnnotationWriter lastRuntimeInvisibleTypeAnnotation, + final ByteVector output) { + if (lastRuntimeVisibleAnnotation != null) { + lastRuntimeVisibleAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_ANNOTATIONS), output); + } + if (lastRuntimeInvisibleAnnotation != null) { + lastRuntimeInvisibleAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_ANNOTATIONS), output); + } + if (lastRuntimeVisibleTypeAnnotation != null) { + lastRuntimeVisibleTypeAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS), output); + } + if (lastRuntimeInvisibleTypeAnnotation != null) { + lastRuntimeInvisibleTypeAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS), output); + } + } + /** * Returns the size of a Runtime[In]VisibleParameterAnnotations attribute containing all the * annotation lists from the given AnnotationWriter sub-array. Also adds the attribute name to the diff --git a/spring-core/src/main/java/org/springframework/asm/Attribute.java b/spring-core/src/main/java/org/springframework/asm/Attribute.java index 7db7de081b0a..e40c42fc20fd 100644 --- a/spring-core/src/main/java/org/springframework/asm/Attribute.java +++ b/spring-core/src/main/java/org/springframework/asm/Attribute.java @@ -28,7 +28,7 @@ package org.springframework.asm; /** - * A non standard class, field, method or code attribute, as defined in the Java Virtual Machine + * A non standard class, field, method or Code attribute, as defined in the Java Virtual Machine * Specification (JVMS). * * @see JVMS @@ -52,7 +52,7 @@ public class Attribute { /** * The next attribute in this attribute list (Attribute instances can be linked via this field to - * store a list of class, field, method or code attributes). May be {@literal null}. + * store a list of class, field, method or Code attributes). May be {@literal null}. */ Attribute nextAttribute; @@ -80,9 +80,9 @@ public boolean isUnknown() { } /** - * Returns {@literal true} if this type of attribute is a code attribute. + * Returns {@literal true} if this type of attribute is a Code attribute. * - * @return {@literal true} if this type of attribute is a code attribute. + * @return {@literal true} if this type of attribute is a Code attribute. */ public boolean isCodeAttribute() { return false; @@ -92,7 +92,7 @@ public boolean isCodeAttribute() { * Returns the labels corresponding to this attribute. * * @return the labels corresponding to this attribute, or {@literal null} if this attribute is not - * a code attribute that contains labels. + * a Code attribute that contains labels. */ protected Label[] getLabels() { return new Label[0]; @@ -104,18 +104,18 @@ protected Label[] getLabels() { * ClassReader. * * @param classReader the class that contains the attribute to be read. - * @param offset index of the first byte of the attribute's content in {@link ClassReader#b}. The - * 6 attribute header bytes (attribute_name_index and attribute_length) are not taken into + * @param offset index of the first byte of the attribute's content in {@link ClassReader}. The 6 + * attribute header bytes (attribute_name_index and attribute_length) are not taken into * account here. * @param length the length of the attribute's content (excluding the 6 attribute header bytes). * @param charBuffer the buffer to be used to call the ClassReader methods requiring a * 'charBuffer' parameter. * @param codeAttributeOffset index of the first byte of content of the enclosing Code attribute - * in {@link ClassReader#b}, or -1 if the attribute to be read is not a code attribute. The 6 + * in {@link ClassReader}, or -1 if the attribute to be read is not a Code attribute. The 6 * attribute header bytes (attribute_name_index and attribute_length) are not taken into * account here. * @param labels the labels of the method's code, or {@literal null} if the attribute to be read - * is not a code attribute. + * is not a Code attribute. * @return a new {@link Attribute} object corresponding to the specified bytes. */ protected Attribute read( @@ -127,7 +127,7 @@ protected Attribute read( final Label[] labels) { Attribute attribute = new Attribute(type); attribute.content = new byte[length]; - System.arraycopy(classReader.b, offset, attribute.content, 0, length); + System.arraycopy(classReader.classFileBuffer, offset, attribute.content, 0, length); return attribute; } @@ -138,16 +138,16 @@ protected Attribute read( * * @param classWriter the class to which this attribute must be added. This parameter can be used * to add the items that corresponds to this attribute to the constant pool of this class. - * @param code the bytecode of the method corresponding to this code attribute, or {@literal null} - * if this attribute is not a code attribute. Corresponds to the 'code' field of the Code + * @param code the bytecode of the method corresponding to this Code attribute, or {@literal null} + * if this attribute is not a Code attribute. Corresponds to the 'code' field of the Code * attribute. * @param codeLength the length of the bytecode of the method corresponding to this code - * attribute, or 0 if this attribute is not a code attribute. Corresponds to the 'code_length' + * attribute, or 0 if this attribute is not a Code attribute. Corresponds to the 'code_length' * field of the Code attribute. - * @param maxStack the maximum stack size of the method corresponding to this code attribute, or - * -1 if this attribute is not a code attribute. + * @param maxStack the maximum stack size of the method corresponding to this Code attribute, or + * -1 if this attribute is not a Code attribute. * @param maxLocals the maximum number of local variables of the method corresponding to this code - * attribute, or -1 if this attribute is not a code attribute. + * attribute, or -1 if this attribute is not a Code attribute. * @return the byte array form of this attribute. */ protected ByteVector write( @@ -197,16 +197,16 @@ final int computeAttributesSize(final SymbolTable symbolTable) { * attribute_length) per attribute. Also adds the attribute type names to the constant pool. * * @param symbolTable where the constants used in the attributes must be stored. - * @param code the bytecode of the method corresponding to these code attributes, or {@literal - * null} if they are not code attributes. Corresponds to the 'code' field of the Code + * @param code the bytecode of the method corresponding to these Code attributes, or {@literal + * null} if they are not Code attributes. Corresponds to the 'code' field of the Code * attribute. * @param codeLength the length of the bytecode of the method corresponding to these code - * attributes, or 0 if they are not code attributes. Corresponds to the 'code_length' field of + * attributes, or 0 if they are not Code attributes. Corresponds to the 'code_length' field of * the Code attribute. - * @param maxStack the maximum stack size of the method corresponding to these code attributes, or - * -1 if they are not code attributes. + * @param maxStack the maximum stack size of the method corresponding to these Code attributes, or + * -1 if they are not Code attributes. * @param maxLocals the maximum number of local variables of the method corresponding to these - * code attributes, or -1 if they are not code attribute. + * Code attributes, or -1 if they are not Code attribute. * @return the size of all the attributes in this attribute list. This size includes the size of * the attribute headers. */ @@ -227,6 +227,42 @@ final int computeAttributesSize( return size; } + /** + * Returns the total size in bytes of all the attributes that correspond to the given field, + * method or class access flags and signature. This size includes the 6 header bytes + * (attribute_name_index and attribute_length) per attribute. Also adds the attribute type names + * to the constant pool. + * + * @param symbolTable where the constants used in the attributes must be stored. + * @param accessFlags some field, method or class access flags. + * @param signatureIndex the constant pool index of a field, method of class signature. + * @return the size of all the attributes in bytes. This size includes the size of the attribute + * headers. + */ + static int computeAttributesSize( + final SymbolTable symbolTable, final int accessFlags, final int signatureIndex) { + int size = 0; + // Before Java 1.5, synthetic fields are represented with a Synthetic attribute. + if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 + && symbolTable.getMajorVersion() < Opcodes.V1_5) { + // Synthetic attributes always use 6 bytes. + symbolTable.addConstantUtf8(Constants.SYNTHETIC); + size += 6; + } + if (signatureIndex != 0) { + // Signature attributes always use 8 bytes. + symbolTable.addConstantUtf8(Constants.SIGNATURE); + size += 8; + } + // ACC_DEPRECATED is ASM specific, the ClassFile format uses a Deprecated attribute instead. + if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { + // Deprecated attributes always use 6 bytes. + symbolTable.addConstantUtf8(Constants.DEPRECATED); + size += 6; + } + return size; + } + /** * Puts all the attributes of the attribute list that begins with this attribute, in the given * byte vector. This includes the 6 header bytes (attribute_name_index and attribute_length) per @@ -249,16 +285,16 @@ final void putAttributes(final SymbolTable symbolTable, final ByteVector output) * attribute. * * @param symbolTable where the constants used in the attributes must be stored. - * @param code the bytecode of the method corresponding to these code attributes, or {@literal - * null} if they are not code attributes. Corresponds to the 'code' field of the Code + * @param code the bytecode of the method corresponding to these Code attributes, or {@literal + * null} if they are not Code attributes. Corresponds to the 'code' field of the Code * attribute. * @param codeLength the length of the bytecode of the method corresponding to these code - * attributes, or 0 if they are not code attributes. Corresponds to the 'code_length' field of + * attributes, or 0 if they are not Code attributes. Corresponds to the 'code_length' field of * the Code attribute. - * @param maxStack the maximum stack size of the method corresponding to these code attributes, or - * -1 if they are not code attributes. + * @param maxStack the maximum stack size of the method corresponding to these Code attributes, or + * -1 if they are not Code attributes. * @param maxLocals the maximum number of local variables of the method corresponding to these - * code attributes, or -1 if they are not code attribute. + * Code attributes, or -1 if they are not Code attribute. * @param output where the attributes must be written. */ final void putAttributes( @@ -280,6 +316,37 @@ final void putAttributes( } } + /** + * Puts all the attributes that correspond to the given field, method or class access flags and + * signature, in the given byte vector. This includes the 6 header bytes (attribute_name_index and + * attribute_length) per attribute. + * + * @param symbolTable where the constants used in the attributes must be stored. + * @param accessFlags some field, method or class access flags. + * @param signatureIndex the constant pool index of a field, method of class signature. + * @param output where the attributes must be written. + */ + static void putAttributes( + final SymbolTable symbolTable, + final int accessFlags, + final int signatureIndex, + final ByteVector output) { + // Before Java 1.5, synthetic fields are represented with a Synthetic attribute. + if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 + && symbolTable.getMajorVersion() < Opcodes.V1_5) { + output.putShort(symbolTable.addConstantUtf8(Constants.SYNTHETIC)).putInt(0); + } + if (signatureIndex != 0) { + output + .putShort(symbolTable.addConstantUtf8(Constants.SIGNATURE)) + .putInt(2) + .putShort(signatureIndex); + } + if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { + output.putShort(symbolTable.addConstantUtf8(Constants.DEPRECATED)).putInt(0); + } + } + /** A set of attribute prototypes (attributes with the same type are considered equal). */ static final class Set { 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 4c7e04237e41..738cd2aa495a 100644 --- a/spring-core/src/main/java/org/springframework/asm/ClassReader.java +++ b/spring-core/src/main/java/org/springframework/asm/ClassReader.java @@ -90,6 +90,16 @@ public class ClassReader { /** The size of the temporary byte array used to read class input streams chunk by chunk. */ private static final int INPUT_STREAM_DATA_CHUNK_SIZE = 4096; + /** + * A byte array containing the JVMS ClassFile structure to be parsed. + * + * @deprecated Use {@link #readByte(int)} and the other read methods instead. This field will + * eventually be deleted. + */ + @Deprecated + // DontCheck(MemberName): can't be renamed (for backward binary compatibility). + public final byte[] b; + /** * A byte array containing the JVMS ClassFile structure to be parsed. The content of this array * must not be modified. This field is intended for {@link Attribute} sub classes, and is normally @@ -99,13 +109,13 @@ public class ClassReader { * necessarily start at offset 0. Use {@link #getItem} and {@link #header} to get correct * ClassFile element offsets within this byte array. */ - // DontCheck(MemberName): can't be renamed (for backward binary compatibility). - public final byte[] b; + final byte[] classFileBuffer; /** - * The offset in bytes, in {@link #b}, of each cp_info entry of the ClassFile's constant_pool - * array, plus one. In other words, the offset of constant pool entry i is given by - * cpInfoOffsets[i] - 1, i.e. its cp_info's tag field is given by b[cpInfoOffsets[i] - 1]. + * The offset in bytes, in {@link #classFileBuffer}, of each cp_info entry of the ClassFile's + * constant_pool array, plus one. In other words, the offset of constant pool entry i is + * given by cpInfoOffsets[i] - 1, i.e. its cp_info's tag field is given by b[cpInfoOffsets[i] - + * 1]. */ private final int[] cpInfoOffsets; @@ -122,8 +132,8 @@ public class ClassReader { private final ConstantDynamic[] constantDynamicValues; /** - * The start offsets in {@link #b} of each element of the bootstrap_methods array (in the - * BootstrapMethods attribute). + * The start offsets in {@link #classFileBuffer} of each element of the bootstrap_methods array + * (in the BootstrapMethods attribute). * * @see JVMS * 4.7.23 @@ -136,7 +146,7 @@ public class ClassReader { */ private final int maxStringLength; - /** The offset in bytes, in {@link #b}, of the ClassFile's access_flags field. */ + /** The offset in bytes of the ClassFile's access_flags field. */ public final int header; // ----------------------------------------------------------------------------------------------- @@ -176,10 +186,11 @@ public ClassReader( */ ClassReader( final byte[] classFileBuffer, final int classFileOffset, final boolean checkClassVersion) { - b = classFileBuffer; + this.classFileBuffer = classFileBuffer; + 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.V12) { + if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V13) { throw new IllegalArgumentException( "Unsupported class file major version " + readShort(classFileOffset + 6)); } @@ -195,8 +206,8 @@ public ClassReader( int currentCpInfoIndex = 1; int currentCpInfoOffset = classFileOffset + 10; int currentMaxStringLength = 0; + boolean hasBootstrapMethods = false; boolean hasConstantDynamic = false; - boolean hasConstantInvokeDynamic = false; // The offset of the other entries depend on the total size of all the previous entries. while (currentCpInfoIndex < constantPoolCount) { cpInfoOffsets[currentCpInfoIndex++] = currentCpInfoOffset + 1; @@ -212,11 +223,12 @@ public ClassReader( break; case Symbol.CONSTANT_DYNAMIC_TAG: cpInfoSize = 5; + hasBootstrapMethods = true; hasConstantDynamic = true; break; case Symbol.CONSTANT_INVOKE_DYNAMIC_TAG: cpInfoSize = 5; - hasConstantInvokeDynamic = true; + hasBootstrapMethods = true; break; case Symbol.CONSTANT_LONG_TAG: case Symbol.CONSTANT_DOUBLE_TAG: @@ -256,9 +268,7 @@ public ClassReader( // Read the BootstrapMethods attribute, if any (only get the offset of each method). bootstrapMethodOffsets = - (hasConstantDynamic | hasConstantInvokeDynamic) - ? readBootstrapMethodsAttribute(currentMaxStringLength) - : null; + hasBootstrapMethods ? readBootstrapMethodsAttribute(currentMaxStringLength) : null; } /** @@ -299,8 +309,7 @@ private static byte[] readStream(final InputStream inputStream, final boolean cl if (inputStream == null) { throw new IOException("Class not found"); } - try { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { byte[] data = new byte[INPUT_STREAM_DATA_CHUNK_SIZE]; int bytesRead; while ((bytesRead = inputStream.read(data, 0, data.length)) != -1) { @@ -696,7 +705,8 @@ public void accept( * attribute_name_index and attribute_length fields). * @param modulePackagesOffset the offset of the ModulePackages attribute (excluding the * attribute_info's attribute_name_index and attribute_length fields), or 0. - * @param moduleMainClass the string corresponding to the ModuleMainClass attribute, or null. + * @param moduleMainClass the string corresponding to the ModuleMainClass attribute, or {@literal + * null}. */ private void readModuleAttributes( final ClassVisitor classVisitor, @@ -1128,13 +1138,12 @@ private int readMethod( MethodWriter methodWriter = (MethodWriter) methodVisitor; if (methodWriter.canCopyMethodAttributes( this, - methodInfoOffset, - currentOffset - methodInfoOffset, synthetic, (context.currentMethodAccessFlags & Opcodes.ACC_DEPRECATED) != 0, readUnsignedShort(methodInfoOffset + 4), signatureIndex, exceptionsOffset)) { + methodWriter.setMethodAttributesSource(methodInfoOffset, currentOffset - methodInfoOffset); return currentOffset; } } @@ -1289,15 +1298,15 @@ private int readMethod( * * @param methodVisitor the visitor that must visit the Code attribute. * @param context information about the class being parsed. - * @param codeOffset the start offset in {@link #b} of the Code attribute, excluding its - * attribute_name_index and attribute_length fields. + * @param codeOffset the start offset in {@link #classFileBuffer} of the Code attribute, excluding + * its attribute_name_index and attribute_length fields. */ private void readCode( final MethodVisitor methodVisitor, final Context context, final int codeOffset) { int currentOffset = codeOffset; // Read the max_stack, max_locals and code_length fields. - final byte[] classFileBuffer = b; + final byte[] classBuffer = classFileBuffer; final char[] charBuffer = context.charBuffer; final int maxStack = readUnsignedShort(currentOffset); final int maxLocals = readUnsignedShort(currentOffset + 2); @@ -1310,7 +1319,7 @@ private void readCode( final Label[] labels = context.currentMethodLabels = new Label[codeLength + 1]; while (currentOffset < bytecodeEndOffset) { final int bytecodeOffset = currentOffset - bytecodeStartOffset; - final int opcode = classFileBuffer[currentOffset] & 0xFF; + final int opcode = classBuffer[currentOffset] & 0xFF; switch (opcode) { case Constants.NOP: case Constants.ACONST_NULL: @@ -1510,7 +1519,7 @@ private void readCode( currentOffset += 5; break; case Constants.WIDE: - switch (classFileBuffer[currentOffset + 1] & 0xFF) { + switch (classBuffer[currentOffset + 1] & 0xFF) { case Constants.ILOAD: case Constants.FLOAD: case Constants.ALOAD: @@ -1759,11 +1768,11 @@ private void readCode( // creating a label for each NEW instruction, and faster than fully decoding the whole stack // map table. for (int offset = stackMapFrameOffset; offset < stackMapTableEndOffset - 2; ++offset) { - if (classFileBuffer[offset] == Frame.ITEM_UNINITIALIZED) { + if (classBuffer[offset] == Frame.ITEM_UNINITIALIZED) { int potentialBytecodeOffset = readUnsignedShort(offset + 1); if (potentialBytecodeOffset >= 0 && potentialBytecodeOffset < codeLength - && (classFileBuffer[bytecodeStartOffset + potentialBytecodeOffset] & 0xFF) + && (classBuffer[bytecodeStartOffset + potentialBytecodeOffset] & 0xFF) == Opcodes.NEW) { createLabel(potentialBytecodeOffset, labels); } @@ -1859,7 +1868,7 @@ private void readCode( } // Visit the instruction at this bytecode offset. - int opcode = classFileBuffer[currentOffset] & 0xFF; + int opcode = classBuffer[currentOffset] & 0xFF; switch (opcode) { case Constants.NOP: case Constants.ACONST_NULL: @@ -2097,19 +2106,17 @@ private void readCode( break; } case Constants.ASM_GOTO_W: - { - // Replace ASM_GOTO_W with GOTO_W. - methodVisitor.visitJumpInsn( - Constants.GOTO_W, labels[currentBytecodeOffset + readInt(currentOffset + 1)]); - // The instruction just after is a jump target (because ASM_GOTO_W is used in patterns - // IFNOTxxx ASM_GOTO_W L:..., see MethodWriter), so we need to insert a frame - // here. - insertFrame = true; - currentOffset += 5; - break; - } + // Replace ASM_GOTO_W with GOTO_W. + methodVisitor.visitJumpInsn( + Constants.GOTO_W, labels[currentBytecodeOffset + readInt(currentOffset + 1)]); + // The instruction just after is a jump target (because ASM_GOTO_W is used in patterns + // IFNOTxxx ASM_GOTO_W L:..., see MethodWriter), so we need to insert a frame + // here. + insertFrame = true; + currentOffset += 5; + break; case Constants.WIDE: - opcode = classFileBuffer[currentOffset + 1] & 0xFF; + opcode = classBuffer[currentOffset + 1] & 0xFF; if (opcode == Opcodes.IINC) { methodVisitor.visitIincInsn( readUnsignedShort(currentOffset + 2), readShort(currentOffset + 4)); @@ -2165,12 +2172,12 @@ private void readCode( case Constants.DSTORE: case Constants.ASTORE: case Constants.RET: - methodVisitor.visitVarInsn(opcode, classFileBuffer[currentOffset + 1] & 0xFF); + methodVisitor.visitVarInsn(opcode, classBuffer[currentOffset + 1] & 0xFF); currentOffset += 2; break; case Constants.BIPUSH: case Constants.NEWARRAY: - methodVisitor.visitIntInsn(opcode, classFileBuffer[currentOffset + 1]); + methodVisitor.visitIntInsn(opcode, classBuffer[currentOffset + 1]); currentOffset += 2; break; case Constants.SIPUSH: @@ -2178,8 +2185,7 @@ private void readCode( currentOffset += 3; break; case Constants.LDC: - methodVisitor.visitLdcInsn( - readConst(classFileBuffer[currentOffset + 1] & 0xFF, charBuffer)); + methodVisitor.visitLdcInsn(readConst(classBuffer[currentOffset + 1] & 0xFF, charBuffer)); currentOffset += 2; break; case Constants.LDC_W: @@ -2205,7 +2211,7 @@ private void readCode( methodVisitor.visitFieldInsn(opcode, owner, name, descriptor); } else { boolean isInterface = - classFileBuffer[cpInfoOffset - 1] == Symbol.CONSTANT_INTERFACE_METHODREF_TAG; + classBuffer[cpInfoOffset - 1] == Symbol.CONSTANT_INTERFACE_METHODREF_TAG; methodVisitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface); } if (opcode == Opcodes.INVOKEINTERFACE) { @@ -2246,12 +2252,12 @@ private void readCode( break; case Constants.IINC: methodVisitor.visitIincInsn( - classFileBuffer[currentOffset + 1] & 0xFF, classFileBuffer[currentOffset + 2]); + classBuffer[currentOffset + 1] & 0xFF, classBuffer[currentOffset + 2]); currentOffset += 3; break; case Constants.MULTIANEWARRAY: methodVisitor.visitMultiANewArrayInsn( - readClass(currentOffset + 1, charBuffer), classFileBuffer[currentOffset + 3] & 0xFF); + readClass(currentOffset + 1, charBuffer), classBuffer[currentOffset + 3] & 0xFF); currentOffset += 4; break; default: @@ -2560,7 +2566,7 @@ private int[] readTypeAnnotations( int pathLength = readByte(currentOffset); if ((targetType >>> 24) == TypeReference.EXCEPTION_PARAMETER) { // Parse the target_path structure and create a corresponding TypePath. - TypePath path = pathLength == 0 ? null : new TypePath(b, currentOffset); + TypePath path = pathLength == 0 ? null : new TypePath(classFileBuffer, currentOffset); currentOffset += 1 + 2 * pathLength; // Parse the type_index field. String annotationDescriptor = readUTF8(currentOffset, charBuffer); @@ -2593,7 +2599,7 @@ private int[] readTypeAnnotations( * -1 if there is no such type_annotation of if it does not have a bytecode offset. * * @param typeAnnotationOffsets the offset of each 'type_annotation' entry in a - * Runtime[In]VisibleTypeAnnotations attribute, or null. + * Runtime[In]VisibleTypeAnnotations attribute, or {@literal null}. * @param typeAnnotationIndex the index a 'type_annotation' entry in typeAnnotationOffsets. * @return bytecode offset corresponding to the specified JVMS 'type_annotation' structure, or -1 * if there is no such type_annotation of if it does not have a bytecode offset. @@ -2685,7 +2691,7 @@ private int readTypeAnnotationTarget(final Context context, final int typeAnnota // Parse and store the target_path structure. int pathLength = readByte(currentOffset); context.currentTypeAnnotationTargetPath = - pathLength == 0 ? null : new TypePath(b, currentOffset); + pathLength == 0 ? null : new TypePath(classFileBuffer, currentOffset); // Return the start offset of the rest of the type_annotation structure. return currentOffset + 1 + 2 * pathLength; } @@ -2707,7 +2713,7 @@ private void readParameterAnnotations( final int runtimeParameterAnnotationsOffset, final boolean visible) { int currentOffset = runtimeParameterAnnotationsOffset; - int numParameters = b[currentOffset++] & 0xFF; + int numParameters = classFileBuffer[currentOffset++] & 0xFF; methodVisitor.visitAnnotableParameterCount(numParameters, visible); char[] charBuffer = context.charBuffer; for (int i = 0; i < numParameters; ++i) { @@ -2775,8 +2781,8 @@ private int readElementValues( * Reads a JVMS 'element_value' structure and makes the given visitor visit it. * * @param annotationVisitor the visitor that must visit the element_value structure. - * @param elementValueOffset the start offset in {@link #b} of the element_value structure to be - * read. + * @param elementValueOffset the start offset in {@link #classFileBuffer} of the element_value + * structure to be read. * @param elementName the name of the element_value structure to be read, or {@literal null}. * @param charBuffer the buffer used to read strings in the constant pool. * @return the end offset of the JVMS 'element_value' structure. @@ -2788,7 +2794,7 @@ private int readElementValue( final char[] charBuffer) { int currentOffset = elementValueOffset; if (annotationVisitor == null) { - switch (b[currentOffset] & 0xFF) { + switch (classFileBuffer[currentOffset] & 0xFF) { case 'e': // enum_const_value return currentOffset + 5; case '@': // annotation_value @@ -2799,7 +2805,7 @@ private int readElementValue( return currentOffset + 3; } } - switch (b[currentOffset++] & 0xFF) { + switch (classFileBuffer[currentOffset++] & 0xFF) { case 'B': // const_value_index, CONSTANT_Integer annotationVisitor.visit( elementName, (byte) readInt(cpInfoOffsets[readUnsignedShort(currentOffset)])); @@ -2865,7 +2871,7 @@ private int readElementValue( /* named = */ false, charBuffer); } - switch (b[currentOffset] & 0xFF) { + switch (classFileBuffer[currentOffset] & 0xFF) { case 'B': byte[] byteValues = new byte[numValues]; for (int i = 0; i < numValues; i++) { @@ -3027,9 +3033,9 @@ private void computeImplicitFrame(final Context context) { * object. This method can also be used to read a full_frame structure, excluding its frame_type * field (this is used to parse the legacy StackMap attributes). * - * @param stackMapFrameOffset the start offset in {@link #b} of the stack_map_frame_value - * structure to be read, or the start offset of a full_frame structure (excluding its - * frame_type field). + * @param stackMapFrameOffset the start offset in {@link #classFileBuffer} of the + * stack_map_frame_value structure to be read, or the start offset of a full_frame structure + * (excluding its frame_type field). * @param compressed true to read a 'stack_map_frame' structure, false to read a 'full_frame' * structure without its frame_type field. * @param expand if the stack map frame must be expanded. See {@link #EXPAND_FRAMES}. @@ -3047,7 +3053,7 @@ private int readStackMapFrame( int frameType; if (compressed) { // Read the frame_type field. - frameType = b[currentOffset++] & 0xFF; + frameType = classFileBuffer[currentOffset++] & 0xFF; } else { frameType = Frame.FULL_FRAME; context.currentFrameOffset = -1; @@ -3142,7 +3148,7 @@ private int readVerificationTypeInfo( final char[] charBuffer, final Label[] labels) { int currentOffset = verificationTypeInfoOffset; - int tag = b[currentOffset++] & 0xFF; + int tag = classFileBuffer[currentOffset++] & 0xFF; switch (tag) { case Frame.ITEM_TOP: frame[index] = Opcodes.TOP; @@ -3184,9 +3190,11 @@ private int readVerificationTypeInfo( // ---------------------------------------------------------------------------------------------- /** - * Returns the offset in {@link #b} of the first ClassFile's 'attributes' array field entry. + * Returns the offset in {@link #classFileBuffer} of the first ClassFile's 'attributes' array + * field entry. * - * @return the offset in {@link #b} of the first ClassFile's 'attributes' array field entry. + * @return the offset in {@link #classFileBuffer} of the first ClassFile's 'attributes' array + * field entry. */ final int getFirstAttributeOffset() { // Skip the access_flags, this_class, super_class, and interfaces_count fields (using 2 bytes @@ -3233,7 +3241,7 @@ final int getFirstAttributeOffset() { * * @param maxStringLength a conservative estimate of the maximum length of the strings contained * in the constant pool of the class. - * @return the offsets of the bootstrap methods or null. + * @return the offsets of the bootstrap methods. */ private int[] readBootstrapMethodsAttribute(final int maxStringLength) { char[] charBuffer = new char[maxStringLength]; @@ -3260,23 +3268,25 @@ private int[] readBootstrapMethodsAttribute(final int maxStringLength) { } currentAttributeOffset += attributeLength; } - return null; + throw new IllegalArgumentException(); } /** - * Reads a non standard JVMS 'attribute' structure in {@link #b}. + * Reads a non standard JVMS 'attribute' structure in {@link #classFileBuffer}. * * @param attributePrototypes prototypes of the attributes that must be parsed during the visit of * the class. Any attribute whose type is not equal to the type of one the prototypes will not * be parsed: its byte array value will be passed unchanged to the ClassWriter. * @param type the type of the attribute. - * @param offset the start offset of the JVMS 'attribute' structure in {@link #b}. The 6 attribute - * header bytes (attribute_name_index and attribute_length) are not taken into account here. + * @param offset the start offset of the JVMS 'attribute' structure in {@link #classFileBuffer}. + * The 6 attribute header bytes (attribute_name_index and attribute_length) are not taken into + * account here. * @param length the length of the attribute's content (excluding the 6 attribute header bytes). * @param charBuffer the buffer to be used to read strings in the constant pool. - * @param codeAttributeOffset the start offset of the enclosing Code attribute in {@link #b}, or - * -1 if the attribute to be read is not a code attribute. The 6 attribute header bytes - * (attribute_name_index and attribute_length) are not taken into account here. + * @param codeAttributeOffset the start offset of the enclosing Code attribute in {@link + * #classFileBuffer}, or -1 if the attribute to be read is not a code attribute. The 6 + * attribute header bytes (attribute_name_index and attribute_length) are not taken into + * account here. * @param labels the labels of the method's code, or {@literal null} if the attribute to be read * is not a code attribute. * @return the attribute that has been read. @@ -3312,13 +3322,14 @@ public int getItemCount() { } /** - * Returns the start offset in {@link #b} of a JVMS 'cp_info' structure (i.e. a constant pool - * entry), plus one. This method is intended for {@link Attribute} sub classes, and is normally - * not needed by class generators or adapters. + * Returns the start offset in this {@link ClassReader} of a JVMS 'cp_info' structure (i.e. a + * constant pool entry), plus one. This method is intended for {@link Attribute} sub classes, + * and is normally not needed by class generators or adapters. * * @param constantPoolEntryIndex the index a constant pool entry in the class's constant pool * table. - * @return the start offset in {@link #b} of the corresponding JVMS 'cp_info' structure, plus one. + * @return the start offset in this {@link ClassReader} of the corresponding JVMS 'cp_info' + * structure, plus one. */ public int getItem(final int constantPoolEntryIndex) { return cpInfoOffsets[constantPoolEntryIndex]; @@ -3336,60 +3347,60 @@ public int getMaxStringLength() { } /** - * Reads a byte value in {@link #b}. This method is intended for {@link Attribute} sub classes, - * and is normally not needed by class generators or adapters. + * Reads a byte value in this {@link ClassReader}. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. * - * @param offset the start offset of the value to be read in {@link #b}. + * @param offset the start offset of the value to be read in this {@link ClassReader}. * @return the read value. */ public int readByte(final int offset) { - return b[offset] & 0xFF; + return classFileBuffer[offset] & 0xFF; } /** - * Reads an unsigned short value in {@link #b}. This method is intended for {@link Attribute} - * sub classes, and is normally not needed by class generators or adapters. + * Reads an unsigned short value in this {@link ClassReader}. This method is intended for + * {@link Attribute} sub classes, and is normally not needed by class generators or adapters. * - * @param offset the start index of the value to be read in {@link #b}. + * @param offset the start index of the value to be read in this {@link ClassReader}. * @return the read value. */ public int readUnsignedShort(final int offset) { - byte[] classFileBuffer = b; - return ((classFileBuffer[offset] & 0xFF) << 8) | (classFileBuffer[offset + 1] & 0xFF); + byte[] classBuffer = classFileBuffer; + return ((classBuffer[offset] & 0xFF) << 8) | (classBuffer[offset + 1] & 0xFF); } /** - * Reads a signed short value in {@link #b}. This method is intended for {@link Attribute} sub - * classes, and is normally not needed by class generators or adapters. + * Reads a signed short value in this {@link ClassReader}. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. * - * @param offset the start offset of the value to be read in {@link #b}. + * @param offset the start offset of the value to be read in this {@link ClassReader}. * @return the read value. */ public short readShort(final int offset) { - byte[] classFileBuffer = b; - return (short) (((classFileBuffer[offset] & 0xFF) << 8) | (classFileBuffer[offset + 1] & 0xFF)); + byte[] classBuffer = classFileBuffer; + return (short) (((classBuffer[offset] & 0xFF) << 8) | (classBuffer[offset + 1] & 0xFF)); } /** - * Reads a signed int value in {@link #b}. This method is intended for {@link Attribute} sub - * classes, and is normally not needed by class generators or adapters. + * Reads a signed int value in this {@link ClassReader}. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. * - * @param offset the start offset of the value to be read in {@link #b}. + * @param offset the start offset of the value to be read in this {@link ClassReader}. * @return the read value. */ public int readInt(final int offset) { - byte[] classFileBuffer = b; - return ((classFileBuffer[offset] & 0xFF) << 24) - | ((classFileBuffer[offset + 1] & 0xFF) << 16) - | ((classFileBuffer[offset + 2] & 0xFF) << 8) - | (classFileBuffer[offset + 3] & 0xFF); + byte[] classBuffer = classFileBuffer; + return ((classBuffer[offset] & 0xFF) << 24) + | ((classBuffer[offset + 1] & 0xFF) << 16) + | ((classBuffer[offset + 2] & 0xFF) << 8) + | (classBuffer[offset + 3] & 0xFF); } /** - * Reads a signed long value in {@link #b}. This method is intended for {@link Attribute} sub - * classes, and is normally not needed by class generators or adapters. + * Reads a signed long value in this {@link ClassReader}. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. * - * @param offset the start offset of the value to be read in {@link #b}. + * @param offset the start offset of the value to be read in this {@link ClassReader}. * @return the read value. */ public long readLong(final int offset) { @@ -3399,11 +3410,12 @@ public long readLong(final int offset) { } /** - * Reads a CONSTANT_Utf8 constant pool entry in {@link #b}. This method is intended for {@link - * Attribute} sub classes, and is normally not needed by class generators or adapters. + * Reads a CONSTANT_Utf8 constant pool entry in this {@link ClassReader}. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by class generators or + * adapters. * - * @param offset the start offset of an unsigned short value in {@link #b}, whose value is the - * index of a CONSTANT_Utf8 entry in the class's constant pool table. + * @param offset the start offset of an unsigned short value in this {@link ClassReader}, whose + * value is the index of a CONSTANT_Utf8 entry in the class's constant pool table. * @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently * large. It is not automatically resized. * @return the String corresponding to the specified CONSTANT_Utf8 entry. @@ -3418,7 +3430,7 @@ public String readUTF8(final int offset, final char[] charBuffer) { } /** - * Reads a CONSTANT_Utf8 constant pool entry in {@link #b}. + * Reads a CONSTANT_Utf8 constant pool entry in {@link #classFileBuffer}. * * @param constantPoolEntryIndex the index of a CONSTANT_Utf8 entry in the class's constant pool * table. @@ -3437,7 +3449,7 @@ final String readUtf(final int constantPoolEntryIndex, final char[] charBuffer) } /** - * Reads an UTF8 string in {@link #b}. + * Reads an UTF8 string in {@link #classFileBuffer}. * * @param utfOffset the start offset of the UTF8 string to be read. * @param utfLength the length of the UTF8 string to be read. @@ -3449,20 +3461,20 @@ private String readUtf(final int utfOffset, final int utfLength, final char[] ch int currentOffset = utfOffset; int endOffset = currentOffset + utfLength; int strLength = 0; - byte[] classFileBuffer = b; + byte[] classBuffer = classFileBuffer; while (currentOffset < endOffset) { - int currentByte = classFileBuffer[currentOffset++]; + int currentByte = classBuffer[currentOffset++]; if ((currentByte & 0x80) == 0) { charBuffer[strLength++] = (char) (currentByte & 0x7F); } else if ((currentByte & 0xE0) == 0xC0) { charBuffer[strLength++] = - (char) (((currentByte & 0x1F) << 6) + (classFileBuffer[currentOffset++] & 0x3F)); + (char) (((currentByte & 0x1F) << 6) + (classBuffer[currentOffset++] & 0x3F)); } else { charBuffer[strLength++] = (char) (((currentByte & 0xF) << 12) - + ((classFileBuffer[currentOffset++] & 0x3F) << 6) - + (classFileBuffer[currentOffset++] & 0x3F)); + + ((classBuffer[currentOffset++] & 0x3F) << 6) + + (classBuffer[currentOffset++] & 0x3F)); } } return new String(charBuffer, 0, strLength); @@ -3470,12 +3482,13 @@ private String readUtf(final int utfOffset, final int utfLength, final char[] ch /** * Reads a CONSTANT_Class, CONSTANT_String, CONSTANT_MethodType, CONSTANT_Module or - * CONSTANT_Package constant pool entry in {@link #b}. This method is intended for {@link - * Attribute} sub classes, and is normally not needed by class generators or adapters. + * CONSTANT_Package constant pool entry in {@link #classFileBuffer}. This method is intended + * for {@link Attribute} sub classes, and is normally not needed by class generators or + * adapters. * - * @param offset the start offset of an unsigned short value in {@link #b}, whose value is the - * index of a CONSTANT_Class, CONSTANT_String, CONSTANT_MethodType, CONSTANT_Module or - * CONSTANT_Package entry in class's constant pool table. + * @param offset the start offset of an unsigned short value in {@link #classFileBuffer}, whose + * value is the index of a CONSTANT_Class, CONSTANT_String, CONSTANT_MethodType, + * CONSTANT_Module or CONSTANT_Package entry in class's constant pool table. * @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently * large. It is not automatically resized. * @return the String corresponding to the specified constant pool entry. @@ -3487,11 +3500,12 @@ private String readStringish(final int offset, final char[] charBuffer) { } /** - * Reads a CONSTANT_Class constant pool entry in {@link #b}. This method is intended for {@link - * Attribute} sub classes, and is normally not needed by class generators or adapters. + * Reads a CONSTANT_Class constant pool entry in this {@link ClassReader}. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by class generators or + * adapters. * - * @param offset the start offset of an unsigned short value in {@link #b}, whose value is the - * index of a CONSTANT_Class entry in class's constant pool table. + * @param offset the start offset of an unsigned short value in this {@link ClassReader}, whose + * value is the index of a CONSTANT_Class entry in class's constant pool table. * @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently * large. It is not automatically resized. * @return the String corresponding to the specified CONSTANT_Class entry. @@ -3501,11 +3515,12 @@ public String readClass(final int offset, final char[] charBuffer) { } /** - * Reads a CONSTANT_Module constant pool entry in {@link #b}. This method is intended for - * {@link Attribute} sub classes, and is normally not needed by class generators or adapters. + * Reads a CONSTANT_Module constant pool entry in this {@link ClassReader}. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by class generators or + * adapters. * - * @param offset the start offset of an unsigned short value in {@link #b}, whose value is the - * index of a CONSTANT_Module entry in class's constant pool table. + * @param offset the start offset of an unsigned short value in this {@link ClassReader}, whose + * value is the index of a CONSTANT_Module entry in class's constant pool table. * @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently * large. It is not automatically resized. * @return the String corresponding to the specified CONSTANT_Module entry. @@ -3515,11 +3530,12 @@ public String readModule(final int offset, final char[] charBuffer) { } /** - * Reads a CONSTANT_Package constant pool entry in {@link #b}. This method is intended for - * {@link Attribute} sub classes, and is normally not needed by class generators or adapters. + * Reads a CONSTANT_Package constant pool entry in this {@link ClassReader}. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by class generators or + * adapters. * - * @param offset the start offset of an unsigned short value in {@link #b}, whose value is the - * index of a CONSTANT_Package entry in class's constant pool table. + * @param offset the start offset of an unsigned short value in this {@link ClassReader}, whose + * value is the index of a CONSTANT_Package entry in class's constant pool table. * @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently * large. It is not automatically resized. * @return the String corresponding to the specified CONSTANT_Package entry. @@ -3529,7 +3545,7 @@ public String readPackage(final int offset, final char[] charBuffer) { } /** - * Reads a CONSTANT_Dynamic constant pool entry in {@link #b}. + * Reads a CONSTANT_Dynamic constant pool entry in {@link #classFileBuffer}. * * @param constantPoolEntryIndex the index of a CONSTANT_Dynamic entry in the class's constant * pool table. @@ -3560,8 +3576,9 @@ private ConstantDynamic readConstantDynamic( } /** - * Reads a numeric or string constant pool entry in {@link #b}. This method is intended for - * {@link Attribute} sub classes, and is normally not needed by class generators or adapters. + * Reads a numeric or string constant pool entry in this {@link ClassReader}. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by class generators or + * adapters. * * @param constantPoolEntryIndex the index of a CONSTANT_Integer, CONSTANT_Float, CONSTANT_Long, * CONSTANT_Double, CONSTANT_Class, CONSTANT_String, CONSTANT_MethodType, @@ -3574,7 +3591,7 @@ private ConstantDynamic readConstantDynamic( */ public Object readConst(final int constantPoolEntryIndex, final char[] charBuffer) { int cpInfoOffset = cpInfoOffsets[constantPoolEntryIndex]; - switch (b[cpInfoOffset - 1]) { + switch (classFileBuffer[cpInfoOffset - 1]) { case Symbol.CONSTANT_INTEGER_TAG: return readInt(cpInfoOffset); case Symbol.CONSTANT_FLOAT_TAG: @@ -3597,7 +3614,7 @@ public Object readConst(final int constantPoolEntryIndex, final char[] charBuffe String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer); String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer); boolean isInterface = - b[referenceCpInfoOffset - 1] == Symbol.CONSTANT_INTERFACE_METHODREF_TAG; + classFileBuffer[referenceCpInfoOffset - 1] == Symbol.CONSTANT_INTERFACE_METHODREF_TAG; return new Handle(referenceKind, owner, name, descriptor, isInterface); case Symbol.CONSTANT_DYNAMIC_TAG: return readConstantDynamic(constantPoolEntryIndex, charBuffer); 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 4fafa67edca3..463499cacce8 100644 --- a/spring-core/src/main/java/org/springframework/asm/ClassVisitor.java +++ b/spring-core/src/main/java/org/springframework/asm/ClassVisitor.java @@ -44,7 +44,7 @@ public abstract class ClassVisitor { */ protected final int api; - /** The class visitor to which this visitor must delegate method calls. May be null. */ + /** The class visitor to which this visitor must delegate method calls. May be {@literal null}. */ protected ClassVisitor cv; /** @@ -66,8 +66,8 @@ public ClassVisitor(final int api) { * null. */ public ClassVisitor(final int api, final ClassVisitor classVisitor) { - if (api != Opcodes.ASM6 && api != Opcodes.ASM5 && api != Opcodes.ASM4 && api != Opcodes.ASM7) { - throw new IllegalArgumentException(); + if (api != Opcodes.ASM7 && api != Opcodes.ASM6 && api != Opcodes.ASM5 && api != Opcodes.ASM4) { + throw new IllegalArgumentException("Unsupported api " + api); } this.api = api; this.cv = classVisitor; 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 d383735a756a..700e7fd52611 100644 --- a/spring-core/src/main/java/org/springframework/asm/ClassWriter.java +++ b/spring-core/src/main/java/org/springframework/asm/ClassWriter.java @@ -313,37 +313,26 @@ public final void visitOuterClass( @Override public final AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { - // Create a ByteVector to hold an 'annotation' JVMS structure. - // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16. - ByteVector annotation = new ByteVector(); - // Write type_index and reserve space for num_element_value_pairs. - annotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0); if (visible) { return lastRuntimeVisibleAnnotation = - new AnnotationWriter(symbolTable, annotation, lastRuntimeVisibleAnnotation); + AnnotationWriter.create(symbolTable, descriptor, lastRuntimeVisibleAnnotation); } else { return lastRuntimeInvisibleAnnotation = - new AnnotationWriter(symbolTable, annotation, lastRuntimeInvisibleAnnotation); + AnnotationWriter.create(symbolTable, descriptor, lastRuntimeInvisibleAnnotation); } } @Override public final AnnotationVisitor visitTypeAnnotation( final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { - // Create a ByteVector to hold a 'type_annotation' JVMS structure. - // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20. - ByteVector typeAnnotation = new ByteVector(); - // Write target_type, target_info, and target_path. - TypeReference.putTarget(typeRef, typeAnnotation); - TypePath.put(typePath, typeAnnotation); - // Write type_index and reserve space for num_element_value_pairs. - typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0); if (visible) { return lastRuntimeVisibleTypeAnnotation = - new AnnotationWriter(symbolTable, typeAnnotation, lastRuntimeVisibleTypeAnnotation); + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastRuntimeVisibleTypeAnnotation); } else { return lastRuntimeInvisibleTypeAnnotation = - new AnnotationWriter(symbolTable, typeAnnotation, lastRuntimeInvisibleTypeAnnotation); + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastRuntimeInvisibleTypeAnnotation); } } @@ -438,7 +427,7 @@ public final void visitEnd() { * @throws ClassTooLargeException if the constant pool of the class is too large. * @throws MethodTooLargeException if the Code attribute of a method is too large. */ - public byte[] toByteArray() throws ClassTooLargeException, MethodTooLargeException { + public byte[] toByteArray() { // First step: compute the size in bytes of the ClassFile structure. // The magic field uses 4 bytes, 10 mandatory fields (minor_version, major_version, // constant_pool_count, access_flags, this_class, super_class, interfaces_count, fields_count, @@ -617,22 +606,13 @@ public byte[] toByteArray() throws ClassTooLargeException, MethodTooLargeExcepti if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { result.putShort(symbolTable.addConstantUtf8(Constants.DEPRECATED)).putInt(0); } - if (lastRuntimeVisibleAnnotation != null) { - lastRuntimeVisibleAnnotation.putAnnotations( - symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_ANNOTATIONS), result); - } - if (lastRuntimeInvisibleAnnotation != null) { - lastRuntimeInvisibleAnnotation.putAnnotations( - symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_ANNOTATIONS), result); - } - if (lastRuntimeVisibleTypeAnnotation != null) { - lastRuntimeVisibleTypeAnnotation.putAnnotations( - symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS), result); - } - if (lastRuntimeInvisibleTypeAnnotation != null) { - lastRuntimeInvisibleTypeAnnotation.putAnnotations( - symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS), result); - } + AnnotationWriter.putAnnotations( + symbolTable, + lastRuntimeVisibleAnnotation, + lastRuntimeInvisibleAnnotation, + lastRuntimeVisibleTypeAnnotation, + lastRuntimeInvisibleTypeAnnotation, + result); symbolTable.putBootstrapMethods(result); if (moduleWriter != null) { moduleWriter.putAttributes(result); @@ -980,7 +960,7 @@ protected String getCommonSuperClass(final String type1, final String type2) { * @return ClassLoader */ protected ClassLoader getClassLoader() { - // SPRING PATCH: PREFER THREAD CONTEXT CLASSLOADER FOR APPLICATION CLASSES + // SPRING PATCH: prefer thread context ClassLoader for application classes ClassLoader classLoader = null; try { classLoader = Thread.currentThread().getContextClassLoader(); 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 390178a89a37..04d3d2e92221 100644 --- a/spring-core/src/main/java/org/springframework/asm/FieldVisitor.java +++ b/spring-core/src/main/java/org/springframework/asm/FieldVisitor.java @@ -42,7 +42,7 @@ public abstract class FieldVisitor { */ protected final int api; - /** The field visitor to which this visitor must delegate method calls. May be null. */ + /** The field visitor to which this visitor must delegate method calls. May be {@literal null}. */ protected FieldVisitor fv; /** @@ -64,8 +64,8 @@ public FieldVisitor(final int api) { * null. */ public FieldVisitor(final int api, final FieldVisitor fieldVisitor) { - if (api != Opcodes.ASM6 && api != Opcodes.ASM5 && api != Opcodes.ASM4 && api != Opcodes.ASM7) { - throw new IllegalArgumentException(); + if (api != Opcodes.ASM7 && api != Opcodes.ASM6 && api != Opcodes.ASM5 && api != Opcodes.ASM4) { + throw new IllegalArgumentException("Unsupported api " + api); } 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 c09398f9f97b..0b4ca89d7e3d 100644 --- a/spring-core/src/main/java/org/springframework/asm/FieldWriter.java +++ b/spring-core/src/main/java/org/springframework/asm/FieldWriter.java @@ -143,37 +143,26 @@ final class FieldWriter extends FieldVisitor { @Override public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { - // Create a ByteVector to hold an 'annotation' JVMS structure. - // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16. - ByteVector annotation = new ByteVector(); - // Write type_index and reserve space for num_element_value_pairs. - annotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0); if (visible) { return lastRuntimeVisibleAnnotation = - new AnnotationWriter(symbolTable, annotation, lastRuntimeVisibleAnnotation); + AnnotationWriter.create(symbolTable, descriptor, lastRuntimeVisibleAnnotation); } else { return lastRuntimeInvisibleAnnotation = - new AnnotationWriter(symbolTable, annotation, lastRuntimeInvisibleAnnotation); + AnnotationWriter.create(symbolTable, descriptor, lastRuntimeInvisibleAnnotation); } } @Override public AnnotationVisitor visitTypeAnnotation( final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { - // Create a ByteVector to hold a 'type_annotation' JVMS structure. - // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20. - ByteVector typeAnnotation = new ByteVector(); - // Write target_type, target_info, and target_path. - TypeReference.putTarget(typeRef, typeAnnotation); - TypePath.put(typePath, typeAnnotation); - // Write type_index and reserve space for num_element_value_pairs. - typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0); if (visible) { return lastRuntimeVisibleTypeAnnotation = - new AnnotationWriter(symbolTable, typeAnnotation, lastRuntimeVisibleTypeAnnotation); + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastRuntimeVisibleTypeAnnotation); } else { return lastRuntimeInvisibleTypeAnnotation = - new AnnotationWriter(symbolTable, typeAnnotation, lastRuntimeInvisibleTypeAnnotation); + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastRuntimeInvisibleTypeAnnotation); } } @@ -208,44 +197,13 @@ int computeFieldInfoSize() { symbolTable.addConstantUtf8(Constants.CONSTANT_VALUE); size += 8; } - // Before Java 1.5, synthetic fields are represented with a Synthetic attribute. - if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 - && symbolTable.getMajorVersion() < Opcodes.V1_5) { - // Synthetic attributes always use 6 bytes. - symbolTable.addConstantUtf8(Constants.SYNTHETIC); - size += 6; - } - if (signatureIndex != 0) { - // Signature attributes always use 8 bytes. - symbolTable.addConstantUtf8(Constants.SIGNATURE); - size += 8; - } - // ACC_DEPRECATED is ASM specific, the ClassFile format uses a Deprecated attribute instead. - if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { - // Deprecated attributes always use 6 bytes. - symbolTable.addConstantUtf8(Constants.DEPRECATED); - size += 6; - } - if (lastRuntimeVisibleAnnotation != null) { - size += - lastRuntimeVisibleAnnotation.computeAnnotationsSize( - Constants.RUNTIME_VISIBLE_ANNOTATIONS); - } - if (lastRuntimeInvisibleAnnotation != null) { - size += - lastRuntimeInvisibleAnnotation.computeAnnotationsSize( - Constants.RUNTIME_INVISIBLE_ANNOTATIONS); - } - if (lastRuntimeVisibleTypeAnnotation != null) { - size += - lastRuntimeVisibleTypeAnnotation.computeAnnotationsSize( - Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS); - } - if (lastRuntimeInvisibleTypeAnnotation != null) { - size += - lastRuntimeInvisibleTypeAnnotation.computeAnnotationsSize( - Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS); - } + size += Attribute.computeAttributesSize(symbolTable, accessFlags, signatureIndex); + size += + AnnotationWriter.computeAnnotationsSize( + lastRuntimeVisibleAnnotation, + lastRuntimeInvisibleAnnotation, + lastRuntimeVisibleTypeAnnotation, + lastRuntimeInvisibleTypeAnnotation); if (firstAttribute != null) { size += firstAttribute.computeAttributesSize(symbolTable); } @@ -302,34 +260,14 @@ void putFieldInfo(final ByteVector output) { .putInt(2) .putShort(constantValueIndex); } - if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && useSyntheticAttribute) { - output.putShort(symbolTable.addConstantUtf8(Constants.SYNTHETIC)).putInt(0); - } - if (signatureIndex != 0) { - output - .putShort(symbolTable.addConstantUtf8(Constants.SIGNATURE)) - .putInt(2) - .putShort(signatureIndex); - } - if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { - output.putShort(symbolTable.addConstantUtf8(Constants.DEPRECATED)).putInt(0); - } - if (lastRuntimeVisibleAnnotation != null) { - lastRuntimeVisibleAnnotation.putAnnotations( - symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_ANNOTATIONS), output); - } - if (lastRuntimeInvisibleAnnotation != null) { - lastRuntimeInvisibleAnnotation.putAnnotations( - symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_ANNOTATIONS), output); - } - if (lastRuntimeVisibleTypeAnnotation != null) { - lastRuntimeVisibleTypeAnnotation.putAnnotations( - symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS), output); - } - if (lastRuntimeInvisibleTypeAnnotation != null) { - lastRuntimeInvisibleTypeAnnotation.putAnnotations( - symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS), output); - } + Attribute.putAttributes(symbolTable, accessFlags, signatureIndex, output); + AnnotationWriter.putAnnotations( + symbolTable, + lastRuntimeVisibleAnnotation, + lastRuntimeInvisibleAnnotation, + lastRuntimeVisibleTypeAnnotation, + lastRuntimeInvisibleTypeAnnotation, + output); if (firstAttribute != null) { firstAttribute.putAttributes(symbolTable, output); } diff --git a/spring-core/src/main/java/org/springframework/asm/Frame.java b/spring-core/src/main/java/org/springframework/asm/Frame.java index 80e9351ef4d4..cc24a0d6621f 100644 --- a/spring-core/src/main/java/org/springframework/asm/Frame.java +++ b/spring-core/src/main/java/org/springframework/asm/Frame.java @@ -54,19 +54,19 @@ * *
  *   =====================================
- *   |.DIM|KIND|FLAG|...............VALUE|
+ *   |...DIM|KIND|.F|...............VALUE|
  *   =====================================
  * 
* *
    - *
  • the DIM field, stored in the 4 most significant bits, is a signed number of array - * dimensions (from -8 to 7, included). It can be retrieved with {@link #DIM_MASK} and a right - * shift of {@link #DIM_SHIFT}. + *
  • the DIM field, stored in the 6 most significant bits, is a signed number of array + * dimensions (from -32 to 31, included). It can be retrieved with {@link #DIM_MASK} and a + * right shift of {@link #DIM_SHIFT}. *
  • the KIND field, stored in 4 bits, indicates the kind of VALUE used. These 4 bits can be * retrieved with {@link #KIND_MASK} and, without any shift, must be equal to {@link * #CONSTANT_KIND}, {@link #REFERENCE_KIND}, {@link #UNINITIALIZED_KIND}, {@link #LOCAL_KIND} * or {@link #STACK_KIND}. - *
  • the FLAGS field, stored in 4 bits, contains up to 4 boolean flags. Currently only one flag + *
  • the FLAGS field, stored in 2 bits, contains up to 2 boolean flags. Currently only one flag * is defined, namely {@link #TOP_IF_LONG_OR_DOUBLE_FLAG}. *
  • the VALUE field, stored in the remaining 20 bits, contains either *
      @@ -89,9 +89,9 @@ *

      Output frames can contain abstract types of any kind and with a positive or negative array * dimension (and even unassigned types, represented by 0 - which does not correspond to any valid * abstract type value). Input frames can only contain CONSTANT_KIND, REFERENCE_KIND or - * UNINITIALIZED_KIND abstract types of positive or null array dimension. In all cases the type - * table contains only internal type names (array type descriptors are forbidden - array dimensions - * must be represented through the DIM field). + * UNINITIALIZED_KIND abstract types of positive or {@literal null} array dimension. In all cases + * the type table contains only internal type names (array type descriptors are forbidden - array + * dimensions must be represented through the DIM field). * *

      The LONG and DOUBLE types are always represented by using two slots (LONG + TOP or DOUBLE + * TOP), for local variables as well as in the operand stack. This is necessary to be able to @@ -129,18 +129,25 @@ class Frame { private static final int ITEM_ASM_CHAR = 11; private static final int ITEM_ASM_SHORT = 12; + // The size and offset in bits of each field of an abstract type. + + private static final int DIM_SIZE = 6; + private static final int KIND_SIZE = 4; + private static final int FLAGS_SIZE = 2; + private static final int VALUE_SIZE = 32 - DIM_SIZE - KIND_SIZE - FLAGS_SIZE; + + private static final int DIM_SHIFT = KIND_SIZE + FLAGS_SIZE + VALUE_SIZE; + private static final int KIND_SHIFT = FLAGS_SIZE + VALUE_SIZE; + private static final int FLAGS_SHIFT = VALUE_SIZE; + // Bitmasks to get each field of an abstract type. - private static final int DIM_MASK = 0xF0000000; - private static final int KIND_MASK = 0x0F000000; - private static final int FLAGS_MASK = 0x00F00000; - private static final int VALUE_MASK = 0x000FFFFF; + private static final int DIM_MASK = ((1 << DIM_SIZE) - 1) << DIM_SHIFT; + private static final int KIND_MASK = ((1 << KIND_SIZE) - 1) << KIND_SHIFT; + private static final int VALUE_MASK = (1 << VALUE_SIZE) - 1; // Constants to manipulate the DIM field of an abstract type. - /** The number of right shift bits to use to get the array dimensions of an abstract type. */ - private static final int DIM_SHIFT = 28; - /** The constant to be added to an abstract type to get one with one more array dimension. */ private static final int ARRAY_OF = +1 << DIM_SHIFT; @@ -149,11 +156,11 @@ class Frame { // Possible values for the KIND field of an abstract type. - private static final int CONSTANT_KIND = 0x01000000; - private static final int REFERENCE_KIND = 0x02000000; - private static final int UNINITIALIZED_KIND = 0x03000000; - private static final int LOCAL_KIND = 0x04000000; - private static final int STACK_KIND = 0x05000000; + private static final int CONSTANT_KIND = 1 << KIND_SHIFT; + private static final int REFERENCE_KIND = 2 << KIND_SHIFT; + private static final int UNINITIALIZED_KIND = 3 << KIND_SHIFT; + private static final int LOCAL_KIND = 4 << KIND_SHIFT; + private static final int STACK_KIND = 5 << KIND_SHIFT; // Possible flags for the FLAGS field of an abstract type. @@ -162,7 +169,7 @@ class Frame { * concrete type is LONG or DOUBLE, TOP should be used instead (because the value has been * partially overridden with an xSTORE instruction). */ - private static final int TOP_IF_LONG_OR_DOUBLE_FLAG = 0x00100000 & FLAGS_MASK; + private static final int TOP_IF_LONG_OR_DOUBLE_FLAG = 1 << FLAGS_SHIFT; // Useful predefined abstract types (all the possible CONSTANT_KIND types). @@ -540,7 +547,8 @@ private void push(final int abstractType) { * @param descriptor a type or method descriptor (in which case its return type is pushed). */ private void push(final SymbolTable symbolTable, final String descriptor) { - int typeDescriptorOffset = descriptor.charAt(0) == '(' ? descriptor.indexOf(')') + 1 : 0; + int typeDescriptorOffset = + descriptor.charAt(0) == '(' ? Type.getReturnTypeOffset(descriptor) : 0; int abstractType = getAbstractTypeFromDescriptor(symbolTable, descriptor, typeDescriptorOffset); if (abstractType != 0) { push(abstractType); @@ -1103,6 +1111,42 @@ void execute( // Frame merging methods, used in the second step of the stack map frame computation algorithm // ----------------------------------------------------------------------------------------------- + /** + * Computes the concrete output type corresponding to a given abstract output type. + * + * @param abstractOutputType an abstract output type. + * @param numStack the size of the input stack, used to resolve abstract output types of + * STACK_KIND kind. + * @return the concrete output type corresponding to 'abstractOutputType'. + */ + private int getConcreteOutputType(final int abstractOutputType, final int numStack) { + int dim = abstractOutputType & DIM_MASK; + int kind = abstractOutputType & KIND_MASK; + if (kind == LOCAL_KIND) { + // By definition, a LOCAL_KIND type designates the concrete type of a local variable at + // the beginning of the basic block corresponding to this frame (which is known when + // this method is called, but was not when the abstract type was computed). + int concreteOutputType = dim + inputLocals[abstractOutputType & VALUE_MASK]; + if ((abstractOutputType & TOP_IF_LONG_OR_DOUBLE_FLAG) != 0 + && (concreteOutputType == LONG || concreteOutputType == DOUBLE)) { + concreteOutputType = TOP; + } + return concreteOutputType; + } else if (kind == STACK_KIND) { + // By definition, a STACK_KIND type designates the concrete type of a local variable at + // the beginning of the basic block corresponding to this frame (which is known when + // this method is called, but was not when the abstract type was computed). + int concreteOutputType = dim + inputStack[numStack - (abstractOutputType & VALUE_MASK)]; + if ((abstractOutputType & TOP_IF_LONG_OR_DOUBLE_FLAG) != 0 + && (concreteOutputType == LONG || concreteOutputType == DOUBLE)) { + concreteOutputType = TOP; + } + return concreteOutputType; + } else { + return abstractOutputType; + } + } + /** * Merges the input frame of the given {@link Frame} with the input and output frames of this * {@link Frame}. Returns {@literal true} if the given frame has been changed by this operation @@ -1137,29 +1181,7 @@ final boolean merge( // value at the beginning of the block. concreteOutputType = inputLocals[i]; } else { - int dim = abstractOutputType & DIM_MASK; - int kind = abstractOutputType & KIND_MASK; - if (kind == LOCAL_KIND) { - // By definition, a LOCAL_KIND type designates the concrete type of a local variable at - // the beginning of the basic block corresponding to this frame (which is known when - // this method is called, but was not when the abstract type was computed). - concreteOutputType = dim + inputLocals[abstractOutputType & VALUE_MASK]; - if ((abstractOutputType & TOP_IF_LONG_OR_DOUBLE_FLAG) != 0 - && (concreteOutputType == LONG || concreteOutputType == DOUBLE)) { - concreteOutputType = TOP; - } - } else if (kind == STACK_KIND) { - // By definition, a STACK_KIND type designates the concrete type of a local variable at - // the beginning of the basic block corresponding to this frame (which is known when - // this method is called, but was not when the abstract type was computed). - concreteOutputType = dim + inputStack[numStack - (abstractOutputType & VALUE_MASK)]; - if ((abstractOutputType & TOP_IF_LONG_OR_DOUBLE_FLAG) != 0 - && (concreteOutputType == LONG || concreteOutputType == DOUBLE)) { - concreteOutputType = TOP; - } - } else { - concreteOutputType = abstractOutputType; - } + concreteOutputType = getConcreteOutputType(abstractOutputType, numStack); } } else { // If the local variable has never been assigned in this basic block, it is equal to its @@ -1213,25 +1235,8 @@ final boolean merge( // Then, do this for the stack operands that have pushed in the basic block (this code is the // same as the one above for local variables). for (int i = 0; i < outputStackTop; ++i) { - int concreteOutputType; int abstractOutputType = outputStack[i]; - int dim = abstractOutputType & DIM_MASK; - int kind = abstractOutputType & KIND_MASK; - if (kind == LOCAL_KIND) { - concreteOutputType = dim + inputLocals[abstractOutputType & VALUE_MASK]; - if ((abstractOutputType & TOP_IF_LONG_OR_DOUBLE_FLAG) != 0 - && (concreteOutputType == LONG || concreteOutputType == DOUBLE)) { - concreteOutputType = TOP; - } - } else if (kind == STACK_KIND) { - concreteOutputType = dim + inputStack[numStack - (abstractOutputType & VALUE_MASK)]; - if ((abstractOutputType & TOP_IF_LONG_OR_DOUBLE_FLAG) != 0 - && (concreteOutputType == LONG || concreteOutputType == DOUBLE)) { - concreteOutputType = TOP; - } - } else { - concreteOutputType = abstractOutputType; - } + int concreteOutputType = getConcreteOutputType(abstractOutputType, numStack); if (initializations != null) { concreteOutputType = getInitializedType(symbolTable, concreteOutputType); } @@ -1248,10 +1253,10 @@ final boolean merge( * @param symbolTable the type table to use to lookup and store type {@link Symbol}. * @param sourceType the abstract type with which the abstract type array element must be merged. * This type should be of {@link #CONSTANT_KIND}, {@link #REFERENCE_KIND} or {@link - * #UNINITIALIZED_KIND} kind, with positive or null array dimensions. + * #UNINITIALIZED_KIND} kind, with positive or {@literal null} array dimensions. * @param dstTypes an array of abstract types. These types should be of {@link #CONSTANT_KIND}, - * {@link #REFERENCE_KIND} or {@link #UNINITIALIZED_KIND} kind, with positive or null array - * dimensions. + * {@link #REFERENCE_KIND} or {@link #UNINITIALIZED_KIND} kind, with positive or {@literal + * null} array dimensions. * @param dstIndex the index of the type that must be merged in dstTypes. * @return {@literal true} if the type array has been modified by this operation. */ diff --git a/spring-core/src/main/java/org/springframework/asm/Label.java b/spring-core/src/main/java/org/springframework/asm/Label.java index 0004179aa34e..e9e2f9e0d2e6 100644 --- a/spring-core/src/main/java/org/springframework/asm/Label.java +++ b/spring-core/src/main/java/org/springframework/asm/Label.java @@ -227,7 +227,8 @@ public class Label { /** * The maximum height reached by the output stack, relatively to the top of the input stack, in - * the basic block corresponding to this label. This maximum is always positive or null. + * the basic block corresponding to this label. This maximum is always positive or {@literal + * null}. */ short outputStackMax; @@ -264,12 +265,12 @@ public class Label { Edge outgoingEdges; /** - * The next element in the list of labels to which this label belongs, or null if it does not - * belong to any list. All lists of labels must end with the {@link #EMPTY_LIST} sentinel, in - * order to ensure that this field is null if and only if this label does not belong to a list of - * labels. Note that there can be several lists of labels at the same time, but that a label can - * belong to at most one list at a time (unless some lists share a common tail, but this is not - * used in practice). + * The next element in the list of labels to which this label belongs, or {@literal null} if it + * does not belong to any list. All lists of labels must end with the {@link #EMPTY_LIST} + * sentinel, in order to ensure that this field is null if and only if this label does not belong + * to a list of labels. Note that there can be several lists of labels at the same time, but that + * a label can belong to at most one list at a time (unless some lists share a common tail, but + * this is not used in practice). * *

      List of labels are used in {@link MethodWriter#computeAllFrames} and {@link * MethodWriter#computeMaxStackAndLocal} to compute stack map frames and the maximum stack size, 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 5c404ac68f85..0f987f072919 100644 --- a/spring-core/src/main/java/org/springframework/asm/MethodVisitor.java +++ b/spring-core/src/main/java/org/springframework/asm/MethodVisitor.java @@ -56,7 +56,9 @@ public abstract class MethodVisitor { */ protected final int api; - /** The method visitor to which this visitor must delegate method calls. May be null. */ + /** + * The method visitor to which this visitor must delegate method calls. May be {@literal null}. + */ protected MethodVisitor mv; /** @@ -78,8 +80,8 @@ public MethodVisitor(final int api) { * be null. */ public MethodVisitor(final int api, final MethodVisitor methodVisitor) { - if (api != Opcodes.ASM6 && api != Opcodes.ASM5 && api != Opcodes.ASM4 && api != Opcodes.ASM7) { - throw new IllegalArgumentException(); + if (api != Opcodes.ASM7 && api != Opcodes.ASM6 && api != Opcodes.ASM5 && api != Opcodes.ASM4) { + throw new IllegalArgumentException("Unsupported api " + api); } this.api = api; this.mv = methodVisitor; @@ -92,7 +94,7 @@ public MethodVisitor(final int api, final MethodVisitor methodVisitor) { /** * Visits a parameter of this method. * - * @param name parameter name or null if none is provided. + * @param name parameter name or {@literal null} if none is provided. * @param access the parameter's access flags, only {@code ACC_FINAL}, {@code ACC_SYNTHETIC} * or/and {@code ACC_MANDATED} are allowed (see {@link Opcodes}). */ @@ -395,14 +397,8 @@ public void visitFieldInsn( @Deprecated public void visitMethodInsn( final int opcode, final String owner, final String name, final String descriptor) { - if (api >= Opcodes.ASM5) { - boolean isInterface = opcode == Opcodes.INVOKEINTERFACE; - visitMethodInsn(opcode, owner, name, descriptor, isInterface); - return; - } - if (mv != null) { - mv.visitMethodInsn(opcode, owner, name, descriptor); - } + int opcodeAndSource = opcode | (api < Opcodes.ASM5 ? Opcodes.SOURCE_DEPRECATED : 0); + visitMethodInsn(opcodeAndSource, owner, name, descriptor, opcode == Opcodes.INVOKEINTERFACE); } /** @@ -422,15 +418,15 @@ public void visitMethodInsn( final String name, final String descriptor, final boolean isInterface) { - if (api < Opcodes.ASM5) { + if (api < Opcodes.ASM5 && (opcode & Opcodes.SOURCE_DEPRECATED) == 0) { if (isInterface != (opcode == Opcodes.INVOKEINTERFACE)) { - throw new IllegalArgumentException("INVOKESPECIAL/STATIC on interfaces requires ASM5"); + throw new UnsupportedOperationException("INVOKESPECIAL/STATIC on interfaces requires ASM5"); } visitMethodInsn(opcode, owner, name, descriptor); return; } if (mv != null) { - mv.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + mv.visitMethodInsn(opcode & ~Opcodes.SOURCE_MASK, owner, name, descriptor, isInterface); } } 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 7719a2db90c9..bc16e433a195 100644 --- a/spring-core/src/main/java/org/springframework/asm/MethodWriter.java +++ b/spring-core/src/main/java/org/springframework/asm/MethodWriter.java @@ -654,37 +654,26 @@ public AnnotationVisitor visitAnnotationDefault() { @Override public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { - // Create a ByteVector to hold an 'annotation' JVMS structure. - // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16. - ByteVector annotation = new ByteVector(); - // Write type_index and reserve space for num_element_value_pairs. - annotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0); if (visible) { return lastRuntimeVisibleAnnotation = - new AnnotationWriter(symbolTable, annotation, lastRuntimeVisibleAnnotation); + AnnotationWriter.create(symbolTable, descriptor, lastRuntimeVisibleAnnotation); } else { return lastRuntimeInvisibleAnnotation = - new AnnotationWriter(symbolTable, annotation, lastRuntimeInvisibleAnnotation); + AnnotationWriter.create(symbolTable, descriptor, lastRuntimeInvisibleAnnotation); } } @Override public AnnotationVisitor visitTypeAnnotation( final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { - // Create a ByteVector to hold a 'type_annotation' JVMS structure. - // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20. - ByteVector typeAnnotation = new ByteVector(); - // Write target_type, target_info, and target_path. - TypeReference.putTarget(typeRef, typeAnnotation); - TypePath.put(typePath, typeAnnotation); - // Write type_index and reserve space for num_element_value_pairs. - typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0); if (visible) { return lastRuntimeVisibleTypeAnnotation = - new AnnotationWriter(symbolTable, typeAnnotation, lastRuntimeVisibleTypeAnnotation); + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastRuntimeVisibleTypeAnnotation); } else { return lastRuntimeInvisibleTypeAnnotation = - new AnnotationWriter(symbolTable, typeAnnotation, lastRuntimeInvisibleTypeAnnotation); + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastRuntimeInvisibleTypeAnnotation); } } @@ -700,27 +689,24 @@ public void visitAnnotableParameterCount(final int parameterCount, final boolean @Override public AnnotationVisitor visitParameterAnnotation( final int parameter, final String annotationDescriptor, final boolean visible) { - // Create a ByteVector to hold an 'annotation' JVMS structure. - // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16. - ByteVector annotation = new ByteVector(); - // Write type_index and reserve space for num_element_value_pairs. - annotation.putShort(symbolTable.addConstantUtf8(annotationDescriptor)).putShort(0); if (visible) { if (lastRuntimeVisibleParameterAnnotations == null) { lastRuntimeVisibleParameterAnnotations = new AnnotationWriter[Type.getArgumentTypes(descriptor).length]; } return lastRuntimeVisibleParameterAnnotations[parameter] = - new AnnotationWriter( - symbolTable, annotation, lastRuntimeVisibleParameterAnnotations[parameter]); + AnnotationWriter.create( + symbolTable, annotationDescriptor, lastRuntimeVisibleParameterAnnotations[parameter]); } else { if (lastRuntimeInvisibleParameterAnnotations == null) { lastRuntimeInvisibleParameterAnnotations = new AnnotationWriter[Type.getArgumentTypes(descriptor).length]; } return lastRuntimeInvisibleParameterAnnotations[parameter] = - new AnnotationWriter( - symbolTable, annotation, lastRuntimeInvisibleParameterAnnotations[parameter]); + AnnotationWriter.create( + symbolTable, + annotationDescriptor, + lastRuntimeInvisibleParameterAnnotations[parameter]); } } @@ -1415,20 +1401,22 @@ public void visitMultiANewArrayInsn(final String descriptor, final int numDimens @Override public AnnotationVisitor visitInsnAnnotation( final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { - // Create a ByteVector to hold a 'type_annotation' JVMS structure. - // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20. - ByteVector typeAnnotation = new ByteVector(); - // Write target_type, target_info, and target_path. - TypeReference.putTarget((typeRef & 0xFF0000FF) | (lastBytecodeOffset << 8), typeAnnotation); - TypePath.put(typePath, typeAnnotation); - // Write type_index and reserve space for num_element_value_pairs. - typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0); if (visible) { return lastCodeRuntimeVisibleTypeAnnotation = - new AnnotationWriter(symbolTable, typeAnnotation, lastCodeRuntimeVisibleTypeAnnotation); + AnnotationWriter.create( + symbolTable, + (typeRef & 0xFF0000FF) | (lastBytecodeOffset << 8), + typePath, + descriptor, + lastCodeRuntimeVisibleTypeAnnotation); } else { return lastCodeRuntimeInvisibleTypeAnnotation = - new AnnotationWriter(symbolTable, typeAnnotation, lastCodeRuntimeInvisibleTypeAnnotation); + AnnotationWriter.create( + symbolTable, + (typeRef & 0xFF0000FF) | (lastBytecodeOffset << 8), + typePath, + descriptor, + lastCodeRuntimeInvisibleTypeAnnotation); } } @@ -1449,20 +1437,14 @@ public void visitTryCatchBlock( @Override public AnnotationVisitor visitTryCatchAnnotation( final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { - // Create a ByteVector to hold a 'type_annotation' JVMS structure. - // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20. - ByteVector typeAnnotation = new ByteVector(); - // Write target_type, target_info, and target_path. - TypeReference.putTarget(typeRef, typeAnnotation); - TypePath.put(typePath, typeAnnotation); - // Write type_index and reserve space for num_element_value_pairs. - typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0); if (visible) { return lastCodeRuntimeVisibleTypeAnnotation = - new AnnotationWriter(symbolTable, typeAnnotation, lastCodeRuntimeVisibleTypeAnnotation); + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastCodeRuntimeVisibleTypeAnnotation); } else { return lastCodeRuntimeInvisibleTypeAnnotation = - new AnnotationWriter(symbolTable, typeAnnotation, lastCodeRuntimeInvisibleTypeAnnotation); + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastCodeRuntimeInvisibleTypeAnnotation); } } @@ -1530,10 +1512,18 @@ public AnnotationVisitor visitLocalVariableAnnotation( typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0); if (visible) { return lastCodeRuntimeVisibleTypeAnnotation = - new AnnotationWriter(symbolTable, typeAnnotation, lastCodeRuntimeVisibleTypeAnnotation); + new AnnotationWriter( + symbolTable, + /* useNamedValues = */ true, + typeAnnotation, + lastCodeRuntimeVisibleTypeAnnotation); } else { return lastCodeRuntimeInvisibleTypeAnnotation = - new AnnotationWriter(symbolTable, typeAnnotation, lastCodeRuntimeInvisibleTypeAnnotation); + new AnnotationWriter( + symbolTable, + /* useNamedValues = */ true, + typeAnnotation, + lastCodeRuntimeInvisibleTypeAnnotation); } } @@ -2004,10 +1994,6 @@ private void putFrameType(final Object type) { * attribute) are the same as the corresponding attributes in the given method. * * @param source the source ClassReader from which the attributes of this method might be copied. - * @param methodInfoOffset the offset in 'source.b' of the method_info JVMS structure from which - * the attributes of this method might be copied. - * @param methodInfoLength the length in 'source.b' of the method_info JVMS structure from which - * the attributes of this method might be copied. * @param hasSyntheticAttribute whether the method_info JVMS structure from which the attributes * of this method might be copied contains a Synthetic attribute. * @param hasDeprecatedAttribute whether the method_info JVMS structure from which the attributes @@ -2024,8 +2010,6 @@ private void putFrameType(final Object type) { */ boolean canCopyMethodAttributes( final ClassReader source, - final int methodInfoOffset, - final int methodInfoLength, final boolean hasSyntheticAttribute, final boolean hasDeprecatedAttribute, final int descriptorIndex, @@ -2060,12 +2044,23 @@ boolean canCopyMethodAttributes( currentExceptionOffset += 2; } } + return true; + } + + /** + * Sets the source from which the attributes of this method will be copied. + * + * @param methodInfoOffset the offset in 'symbolTable.getSource()' of the method_info JVMS + * structure from which the attributes of this method will be copied. + * @param methodInfoLength the length in 'symbolTable.getSource()' of the method_info JVMS + * structure from which the attributes of this method will be copied. + */ + void setMethodAttributesSource(final int methodInfoOffset, final int methodInfoLength) { // Don't copy the attributes yet, instead store their location in the source class reader so // they can be copied later, in {@link #putMethodInfo}. Note that we skip the 6 header bytes // of the method_info JVMS structure. this.sourceOffset = methodInfoOffset + 6; this.sourceLength = methodInfoLength - 6; - return true; } /** @@ -2133,29 +2128,13 @@ int computeMethodInfoSize() { symbolTable.addConstantUtf8(Constants.EXCEPTIONS); size += 8 + 2 * numberOfExceptions; } - boolean useSyntheticAttribute = symbolTable.getMajorVersion() < Opcodes.V1_5; - if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && useSyntheticAttribute) { - symbolTable.addConstantUtf8(Constants.SYNTHETIC); - size += 6; - } - if (signatureIndex != 0) { - symbolTable.addConstantUtf8(Constants.SIGNATURE); - size += 8; - } - if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { - symbolTable.addConstantUtf8(Constants.DEPRECATED); - size += 6; - } - if (lastRuntimeVisibleAnnotation != null) { - size += - lastRuntimeVisibleAnnotation.computeAnnotationsSize( - Constants.RUNTIME_VISIBLE_ANNOTATIONS); - } - if (lastRuntimeInvisibleAnnotation != null) { - size += - lastRuntimeInvisibleAnnotation.computeAnnotationsSize( - Constants.RUNTIME_INVISIBLE_ANNOTATIONS); - } + size += Attribute.computeAttributesSize(symbolTable, accessFlags, signatureIndex); + size += + AnnotationWriter.computeAnnotationsSize( + lastRuntimeVisibleAnnotation, + lastRuntimeInvisibleAnnotation, + lastRuntimeVisibleTypeAnnotation, + lastRuntimeInvisibleTypeAnnotation); if (lastRuntimeVisibleParameterAnnotations != null) { size += AnnotationWriter.computeParameterAnnotationsSize( @@ -2174,16 +2153,6 @@ int computeMethodInfoSize() { ? lastRuntimeInvisibleParameterAnnotations.length : invisibleAnnotableParameterCount); } - if (lastRuntimeVisibleTypeAnnotation != null) { - size += - lastRuntimeVisibleTypeAnnotation.computeAnnotationsSize( - Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS); - } - if (lastRuntimeInvisibleTypeAnnotation != null) { - size += - lastRuntimeInvisibleTypeAnnotation.computeAnnotationsSize( - Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS); - } if (defaultValue != null) { symbolTable.addConstantUtf8(Constants.ANNOTATION_DEFAULT); size += 6 + defaultValue.length; @@ -2211,7 +2180,7 @@ void putMethodInfo(final ByteVector output) { output.putShort(accessFlags & ~mask).putShort(nameIndex).putShort(descriptorIndex); // If this method_info must be copied from an existing one, copy it now and return early. if (sourceOffset != 0) { - output.putByteArray(symbolTable.getSource().b, sourceOffset, sourceLength); + output.putByteArray(symbolTable.getSource().classFileBuffer, sourceOffset, sourceLength); return; } // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. @@ -2365,26 +2334,14 @@ void putMethodInfo(final ByteVector output) { output.putShort(exceptionIndex); } } - if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && useSyntheticAttribute) { - output.putShort(symbolTable.addConstantUtf8(Constants.SYNTHETIC)).putInt(0); - } - if (signatureIndex != 0) { - output - .putShort(symbolTable.addConstantUtf8(Constants.SIGNATURE)) - .putInt(2) - .putShort(signatureIndex); - } - if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { - output.putShort(symbolTable.addConstantUtf8(Constants.DEPRECATED)).putInt(0); - } - if (lastRuntimeVisibleAnnotation != null) { - lastRuntimeVisibleAnnotation.putAnnotations( - symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_ANNOTATIONS), output); - } - if (lastRuntimeInvisibleAnnotation != null) { - lastRuntimeInvisibleAnnotation.putAnnotations( - symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_ANNOTATIONS), output); - } + Attribute.putAttributes(symbolTable, accessFlags, signatureIndex, output); + AnnotationWriter.putAnnotations( + symbolTable, + lastRuntimeVisibleAnnotation, + lastRuntimeInvisibleAnnotation, + lastRuntimeVisibleTypeAnnotation, + lastRuntimeInvisibleTypeAnnotation, + output); if (lastRuntimeVisibleParameterAnnotations != null) { AnnotationWriter.putParameterAnnotations( symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS), @@ -2403,14 +2360,6 @@ void putMethodInfo(final ByteVector output) { : invisibleAnnotableParameterCount, output); } - if (lastRuntimeVisibleTypeAnnotation != null) { - lastRuntimeVisibleTypeAnnotation.putAnnotations( - symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS), output); - } - if (lastRuntimeInvisibleTypeAnnotation != null) { - lastRuntimeInvisibleTypeAnnotation.putAnnotations( - symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS), output); - } if (defaultValue != null) { output .putShort(symbolTable.addConstantUtf8(Constants.ANNOTATION_DEFAULT)) 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 647f7a98cb96..e8c2b0628659 100644 --- a/spring-core/src/main/java/org/springframework/asm/ModuleVisitor.java +++ b/spring-core/src/main/java/org/springframework/asm/ModuleVisitor.java @@ -42,7 +42,9 @@ public abstract class ModuleVisitor { */ protected final int api; - /** The module visitor to which this visitor must delegate method calls. May be null. */ + /** + * The module visitor to which this visitor must delegate method calls. May be {@literal null}. + */ protected ModuleVisitor mv; /** @@ -64,8 +66,8 @@ public ModuleVisitor(final int api) { * be null. */ public ModuleVisitor(final int api, final ModuleVisitor moduleVisitor) { - if (api != Opcodes.ASM6 && api != Opcodes.ASM7) { - throw new IllegalArgumentException(); + if (api != Opcodes.ASM7 && api != Opcodes.ASM6) { + throw new IllegalArgumentException("Unsupported api " + api); } this.api = api; this.mv = moduleVisitor; 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 ee4b79e546f4..1cbfe9d8df78 100644 --- a/spring-core/src/main/java/org/springframework/asm/Opcodes.java +++ b/spring-core/src/main/java/org/springframework/asm/Opcodes.java @@ -48,8 +48,212 @@ public interface Opcodes { int ASM6 = 6 << 16 | 0 << 8; int ASM7 = 7 << 16 | 0 << 8; - // Java ClassFile versions (the minor version is stored in the 16 most - // significant bits, and the + /* + * 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 + * redirection should be done as follows: + * + *

      +   * public class StuffVisitor {
      +   *   ...
      +   *
      +   *   @Deprecated public void visitOldStuff(int arg, ...) {
      +   *     // SOURCE_DEPRECATED means "a call from a deprecated method using the old 'api' value".
      +   *     visitNewStuf(arg | (api < API_NEW ? SOURCE_DEPRECATED : 0), ...);
      +   *   }
      +   *
      +   *   public void visitNewStuff(int argAndSource, ...) {
      +   *     if (api < API_NEW && (argAndSource & SOURCE_DEPRECATED) == 0) {
      +   *       visitOldStuff(argAndSource, ...);
      +   *     } else {
      +   *       int arg = argAndSource & ~SOURCE_MASK;
      +   *       [ do stuff ]
      +   *     }
      +   *   }
      +   * }
      +   * 
      + * + *

      If 'api' is equal to API_NEW, there are two cases: + * + *

        + *
      • call visitNewStuff: the redirection test is skipped and 'do stuff' is executed directly. + *
      • call visitOldSuff: the source is not set to SOURCE_DEPRECATED before calling + * visitNewStuff, but the redirection test is skipped anyway in visitNewStuff, which + * directly executes 'do stuff'. + *
      + * + *

      If 'api' is equal to API_OLD, there are two cases: + * + *

        + *
      • call visitOldSuff: the source is set to SOURCE_DEPRECATED before calling visitNewStuff. + * Because of this visitNewStuff does not redirect back to visitOldStuff, and instead + * executes 'do stuff'. + *
      • call visitNewStuff: the call is redirected to visitOldStuff because the source is 0. + * visitOldStuff now sets the source to SOURCE_DEPRECATED and calls visitNewStuff back. This + * time visitNewStuff does not redirect the call, and instead executes 'do stuff'. + *
      + * + *

      User subclasses

      + * + *

      If a user subclass overrides one of these methods, there are only two cases: either 'api' is + * API_OLD and visitOldStuff is overridden (and visitNewStuff is not), or 'api' is API_NEW or + * more, and visitNewStuff is overridden (and visitOldStuff is not). Any other case is a user + * programming error. + * + *

      If 'api' is equal to API_NEW, the class hierarchy is equivalent to + * + *

      +   * public class StuffVisitor {
      +   *   @Deprecated public void visitOldStuff(int arg, ...) { visitNewStuf(arg, ...); }
      +   *   public void visitNewStuff(int arg, ...) { [ do stuff ] }
      +   * }
      +   * class UserStuffVisitor extends StuffVisitor {
      +   *   @Override public void visitNewStuff(int arg, ...) {
      +   *     super.visitNewStuff(int arg, ...); // optional
      +   *     [ do user stuff ]
      +   *   }
      +   * }
      +   * 
      + * + *

      It is then obvious that whether visitNewStuff or visitOldStuff is called, 'do stuff' and 'do + * user stuff' will be executed, in this order. + * + *

      If 'api' is equal to API_OLD, the class hierarchy is equivalent to + * + *

      +   * public class StuffVisitor {
      +   *   @Deprecated public void visitOldStuff(int arg, ...) {
      +   *     visitNewStuf(arg | SOURCE_DEPRECATED, ...);
      +   *   }
      +   *   public void visitNewStuff(int argAndSource...) {
      +   *     if ((argAndSource & SOURCE_DEPRECATED) == 0) {
      +   *       visitOldStuff(argAndSource, ...);
      +   *     } else {
      +   *       int arg = argAndSource & ~SOURCE_MASK;
      +   *       [ do stuff ]
      +   *     }
      +   *   }
      +   * }
      +   * class UserStuffVisitor extends StuffVisitor {
      +   *   @Override public void visitOldStuff(int arg, ...) {
      +   *     super.visitOldStuff(int arg, ...); // optional
      +   *     [ do user stuff ]
      +   *   }
      +   * }
      +   * 
      + * + *

      and there are two cases: + * + *

        + *
      • call visitOldSuff: in the call to super.visitOldStuff, the source is set to + * SOURCE_DEPRECATED and visitNewStuff is called. Here 'do stuff' is run because the source + * was previously set to SOURCE_DEPRECATED, and execution eventually returns to + * UserStuffVisitor.visitOldStuff, where 'do user stuff' is run. + *
      • call visitNewStuff: the call is redirected to UserStuffVisitor.visitOldStuff because the + * source is 0. Execution continues as in the previous case, resulting in 'do stuff' and 'do + * user stuff' being executed, in this order. + *
      + * + *

      ASM subclasses

      + * + *

      In ASM packages, subclasses of StuffVisitor can typically be sub classed again by the user, + * and can be used with API_OLD or API_NEW. Because of this, if such a subclass must override + * visitNewStuff, it must do so in the following way (and must not override visitOldStuff): + * + *

      +   * public class AsmStuffVisitor extends StuffVisitor {
      +   *   @Override public void visitNewStuff(int argAndSource, ...) {
      +   *     if (api < API_NEW && (argAndSource & SOURCE_DEPRECATED) == 0) {
      +   *       super.visitNewStuff(argAndSource, ...);
      +   *       return;
      +   *     }
      +   *     super.visitNewStuff(argAndSource, ...); // optional
      +   *     int arg = argAndSource & ~SOURCE_MASK;
      +   *     [ do other stuff ]
      +   *   }
      +   * }
      +   * 
      + * + *

      If a user class extends this with 'api' equal to API_NEW, the class hierarchy is equivalent + * to + * + *

      +   * public class StuffVisitor {
      +   *   @Deprecated public void visitOldStuff(int arg, ...) { visitNewStuf(arg, ...); }
      +   *   public void visitNewStuff(int arg, ...) { [ do stuff ] }
      +   * }
      +   * public class AsmStuffVisitor extends StuffVisitor {
      +   *   @Override public void visitNewStuff(int arg, ...) {
      +   *     super.visitNewStuff(arg, ...);
      +   *     [ do other stuff ]
      +   *   }
      +   * }
      +   * class UserStuffVisitor extends StuffVisitor {
      +   *   @Override public void visitNewStuff(int arg, ...) {
      +   *     super.visitNewStuff(int arg, ...);
      +   *     [ do user stuff ]
      +   *   }
      +   * }
      +   * 
      + * + *

      It is then obvious that whether visitNewStuff or visitOldStuff is called, 'do stuff', 'do + * other stuff' and 'do user stuff' will be executed, in this order. If, on the other hand, a user + * class extends AsmStuffVisitor with 'api' equal to API_OLD, the class hierarchy is equivalent to + * + *

      +   * public class StuffVisitor {
      +   *   @Deprecated public void visitOldStuff(int arg, ...) {
      +   *     visitNewStuf(arg | SOURCE_DEPRECATED, ...);
      +   *   }
      +   *   public void visitNewStuff(int argAndSource, ...) {
      +   *     if ((argAndSource & SOURCE_DEPRECATED) == 0) {
      +   *       visitOldStuff(argAndSource, ...);
      +   *     } else {
      +   *       int arg = argAndSource & ~SOURCE_MASK;
      +   *       [ do stuff ]
      +   *     }
      +   *   }
      +   * }
      +   * public class AsmStuffVisitor extends StuffVisitor {
      +   *   @Override public void visitNewStuff(int argAndSource, ...) {
      +   *     if ((argAndSource & SOURCE_DEPRECATED) == 0) {
      +   *       super.visitNewStuff(argAndSource, ...);
      +   *       return;
      +   *     }
      +   *     super.visitNewStuff(argAndSource, ...); // optional
      +   *     int arg = argAndSource & ~SOURCE_MASK;
      +   *     [ do other stuff ]
      +   *   }
      +   * }
      +   * class UserStuffVisitor extends StuffVisitor {
      +   *   @Override public void visitOldStuff(int arg, ...) {
      +   *     super.visitOldStuff(arg, ...);
      +   *     [ do user stuff ]
      +   *   }
      +   * }
      +   * 
      + * + *

      and, here again, whether visitNewStuff or visitOldStuff is called, 'do stuff', 'do other + * stuff' and 'do user stuff' will be executed, in this order (exercise left to the reader). + * + *

      Notes

      + * + *
        + *
      • the SOURCE_DEPRECATED flag is set only if 'api' is API_OLD, just before calling + * visitNewStuff. By hypothesis, this method is not overridden by the user. Therefore, user + * classes can never see this flag. Only ASM subclasses must take care of extracting the + * actual argument value by clearing the source flags. + *
      • because the SOURCE_DEPRECATED flag is immediately cleared in the caller, the caller can + * call visitOldStuff or visitNewStuff (in 'do stuff' and 'do user stuff') on a delegate + * visitor without any risks (breaking the redirection logic, "leaking" the flag, etc). + *
      • all the scenarios discussed above are unit tested in MethodVisitorTest. + *
      + */ + + int SOURCE_DEPRECATED = 0x100; + int SOURCE_MASK = SOURCE_DEPRECATED; + + // Java ClassFile versions (the minor version is stored in the 16 most significant bits, and the // major version in the 16 least significant bits). int V1_1 = 3 << 16 | 45; @@ -64,6 +268,7 @@ public interface Opcodes { int V10 = 0 << 16 | 54; int V11 = 0 << 16 | 55; int V12 = 0 << 16 | 56; + int V13 = 0 << 16 | 57; /** * Version flag indicating that the class is using 'preview' features. diff --git a/spring-core/src/main/java/org/springframework/asm/SymbolTable.java b/spring-core/src/main/java/org/springframework/asm/SymbolTable.java index 48a2802d7c1a..c2142392d0ef 100644 --- a/spring-core/src/main/java/org/springframework/asm/SymbolTable.java +++ b/spring-core/src/main/java/org/springframework/asm/SymbolTable.java @@ -139,7 +139,7 @@ final class SymbolTable { this.sourceClassReader = classReader; // Copy the constant pool binary content. - byte[] inputBytes = classReader.b; + byte[] inputBytes = classReader.classFileBuffer; int constantPoolOffset = classReader.getItem(1) - 1; int constantPoolLength = classReader.header - constantPoolOffset; constantPoolCount = classReader.getItemCount(); @@ -242,7 +242,7 @@ final class SymbolTable { */ private void copyBootstrapMethods(final ClassReader classReader, final char[] charBuffer) { // Find attributOffset of the 'bootstrap_methods' array. - byte[] inputBytes = classReader.b; + byte[] inputBytes = classReader.classFileBuffer; int currentAttributeOffset = classReader.getFirstAttributeOffset(); for (int i = classReader.readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) { String attributeName = classReader.readUTF8(currentAttributeOffset, charBuffer); @@ -1183,8 +1183,10 @@ int addUninitializedType(final String value, final int bytecodeOffset) { * corresponding to the common super class of the given types. */ int addMergedType(final int typeTableIndex1, final int typeTableIndex2) { - // TODO sort the arguments? The merge result should be independent of their order. - long data = typeTableIndex1 | (((long) typeTableIndex2) << 32); + long data = + typeTableIndex1 < typeTableIndex2 + ? typeTableIndex1 | (((long) typeTableIndex2) << 32) + : typeTableIndex2 | (((long) typeTableIndex1) << 32); int hashCode = hash(Symbol.MERGED_TYPE_TAG, typeTableIndex1 + typeTableIndex2); Entry entry = get(hashCode); while (entry != null) { diff --git a/spring-core/src/main/java/org/springframework/asm/Type.java b/spring-core/src/main/java/org/springframework/asm/Type.java index f4ef203986dc..e354c78e64bb 100644 --- a/spring-core/src/main/java/org/springframework/asm/Type.java +++ b/spring-core/src/main/java/org/springframework/asm/Type.java @@ -363,6 +363,27 @@ public Type getReturnType() { * @return the {@link Type} corresponding to the return type of the given method descriptor. */ public static Type getReturnType(final String methodDescriptor) { + return getTypeInternal( + methodDescriptor, getReturnTypeOffset(methodDescriptor), methodDescriptor.length()); + } + + /** + * Returns the {@link Type} corresponding to the return type of the given method. + * + * @param method a method. + * @return the {@link Type} corresponding to the return type of the given method. + */ + public static Type getReturnType(final Method method) { + return getType(method.getReturnType()); + } + + /** + * Returns the start index of the return type of the given method descriptor. + * + * @param methodDescriptor a method descriptor. + * @return the start index of the return type of the given method descriptor. + */ + static int getReturnTypeOffset(final String methodDescriptor) { // Skip the first character, which is always a '('. int currentOffset = 1; // Skip the argument types, one at a each loop iteration. @@ -375,17 +396,7 @@ public static Type getReturnType(final String methodDescriptor) { currentOffset = methodDescriptor.indexOf(';', currentOffset) + 1; } } - return getTypeInternal(methodDescriptor, currentOffset + 1, methodDescriptor.length()); - } - - /** - * Returns the {@link Type} corresponding to the return type of the given method. - * - * @param method a method. - * @return the {@link Type} corresponding to the return type of the given method. - */ - public static Type getReturnType(final Method method) { - return getType(method.getReturnType()); + return currentOffset + 1; } /** @@ -505,11 +516,7 @@ public String getDescriptor() { if (sort == OBJECT) { return valueBuffer.substring(valueBegin - 1, valueEnd + 1); } else if (sort == INTERNAL) { - return new StringBuilder() - .append('L') - .append(valueBuffer, valueBegin, valueEnd) - .append(';') - .toString(); + return 'L' + valueBuffer.substring(valueBegin, valueEnd) + ';'; } else { return valueBuffer.substring(valueBegin, valueEnd); } @@ -631,14 +638,7 @@ private static void appendDescriptor(final Class clazz, final StringBuilder s } stringBuilder.append(descriptor); } else { - stringBuilder.append('L'); - String name = currentClass.getName(); - int nameLength = name.length(); - for (int i = 0; i < nameLength; ++i) { - char car = name.charAt(i); - stringBuilder.append(car == '.' ? '/' : car); - } - stringBuilder.append(';'); + stringBuilder.append('L').append(getInternalName(currentClass)).append(';'); } }