From 864cb25eecae6497fa36c737e8ccaaf2f0c8ebee Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 30 Oct 2024 16:44:47 +0100 Subject: [PATCH] Upgrade to ASM 9.7.1 Closes gh-33821 --- .../org/springframework/asm/Attribute.java | 139 ++++++++++++++++-- .../org/springframework/asm/ClassReader.java | 21 ++- .../org/springframework/asm/ClassWriter.java | 30 +++- .../springframework/asm/MethodVisitor.java | 21 +-- .../java/org/springframework/asm/Opcodes.java | 1 + .../java/org/springframework/asm/Symbol.java | 4 +- .../org/springframework/asm/SymbolTable.java | 38 ++++- 7 files changed, 215 insertions(+), 39 deletions(-) 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 e40c42fc20f..9e68aea8069 100644 --- a/spring-core/src/main/java/org/springframework/asm/Attribute.java +++ b/spring-core/src/main/java/org/springframework/asm/Attribute.java @@ -44,11 +44,11 @@ public class Attribute { public final String type; /** - * The raw content of this attribute, only used for unknown attributes (see {@link #isUnknown()}). - * The 6 header bytes of the attribute (attribute_name_index and attribute_length) are not - * included. + * The raw content of this attribute, as returned by {@link + * #write(ClassWriter,byte[],int,int,int)}. The 6 header bytes of the attribute + * (attribute_name_index and attribute_length) are not included. */ - private byte[] content; + private ByteVector cachedContent; /** * The next attribute in this attribute list (Attribute instances can be linked via this field to @@ -93,7 +93,9 @@ public class Attribute { * * @return the labels corresponding to this attribute, or {@literal null} if this attribute is not * a Code attribute that contains labels. + * @deprecated no longer used by ASM. */ + @Deprecated protected Label[] getLabels() { return new Label[0]; } @@ -115,7 +117,9 @@ public class Attribute { * 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. Labels defined in the attribute must be created and added to this + * array, if not already present, by calling the {@link #readLabel} method (do not create + * {@link Label} instances directly). * @return a new {@link Attribute} object corresponding to the specified bytes. */ protected Attribute read( @@ -126,16 +130,99 @@ public class Attribute { final int codeAttributeOffset, final Label[] labels) { Attribute attribute = new Attribute(type); - attribute.content = new byte[length]; - System.arraycopy(classReader.classFileBuffer, offset, attribute.content, 0, length); + attribute.cachedContent = new ByteVector(classReader.readBytes(offset, length)); return attribute; } + /** + * Reads an attribute with the same {@link #type} as the given attribute. This method returns a + * new {@link Attribute} object, corresponding to the 'length' bytes starting at 'offset', in the + * given ClassReader. + * + * @param attribute The attribute prototype that is used for reading. + * @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}. 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}, 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. Labels defined in the attribute are added to this array, if not + * already present. + * @return a new {@link Attribute} object corresponding to the specified bytes. + */ + public static Attribute read( + final Attribute attribute, + final ClassReader classReader, + final int offset, + final int length, + final char[] charBuffer, + final int codeAttributeOffset, + final Label[] labels) { + return attribute.read(classReader, offset, length, charBuffer, codeAttributeOffset, labels); + } + + /** + * Returns the label corresponding to the given bytecode offset by calling {@link + * ClassReader#readLabel}. This creates and adds the label to the given array if it is not already + * present. Note that this created label may be a {@link Label} subclass instance, if the given + * ClassReader overrides {@link ClassReader#readLabel}. Hence {@link #read(ClassReader, int, int, + * char[], int, Label[])} must not manually create {@link Label} instances. + * + * @param bytecodeOffset a bytecode offset in a method. + * @param labels the already created labels, indexed by their offset. If a label already exists + * for bytecodeOffset this method does not create a new one. Otherwise it stores the new label + * in this array. + * @return a label for the given bytecode offset. + */ + public static Label readLabel( + final ClassReader classReader, final int bytecodeOffset, final Label[] labels) { + return classReader.readLabel(bytecodeOffset, labels); + } + + /** + * Calls {@link #write(ClassWriter,byte[],int,int,int)} if it has not already been called and + * returns its result or its (cached) previous result. + * + * @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 + * 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' + * 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 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. + * @return the byte array form of this attribute. + */ + private ByteVector maybeWrite( + final ClassWriter classWriter, + final byte[] code, + final int codeLength, + final int maxStack, + final int maxLocals) { + if (cachedContent == null) { + cachedContent = write(classWriter, code, codeLength, maxStack, maxLocals); + } + return cachedContent; + } + /** * Returns the byte array form of the content of this attribute. The 6 header bytes * (attribute_name_index and attribute_length) must not be added in the returned * ByteVector. * + *

This method is only invoked once to compute the binary form of this attribute. Subsequent + * changes to the attribute after it was written for the first time will not be considered. + * * @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} @@ -156,7 +243,39 @@ public class Attribute { final int codeLength, final int maxStack, final int maxLocals) { - return new ByteVector(content); + return cachedContent; + } + + /** + * Returns the byte array form of the content of the given attribute. The 6 header bytes + * (attribute_name_index and attribute_length) are not added in the returned byte array. + * + * @param attribute The attribute that should be written. + * @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 + * 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' + * 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 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. + * @return the byte array form of this attribute. + */ + public static byte[] write( + final Attribute attribute, + final ClassWriter classWriter, + final byte[] code, + final int codeLength, + final int maxStack, + final int maxLocals) { + ByteVector content = attribute.maybeWrite(classWriter, code, codeLength, maxStack, maxLocals); + byte[] result = new byte[content.length]; + System.arraycopy(content.data, 0, result, 0, content.length); + return result; } /** @@ -221,7 +340,7 @@ public class Attribute { Attribute attribute = this; while (attribute != null) { symbolTable.addConstantUtf8(attribute.type); - size += 6 + attribute.write(classWriter, code, codeLength, maxStack, maxLocals).length; + size += 6 + attribute.maybeWrite(classWriter, code, codeLength, maxStack, maxLocals).length; attribute = attribute.nextAttribute; } return size; @@ -308,7 +427,7 @@ public class Attribute { Attribute attribute = this; while (attribute != null) { ByteVector attributeContent = - attribute.write(classWriter, code, codeLength, maxStack, maxLocals); + attribute.maybeWrite(classWriter, code, codeLength, maxStack, maxLocals); // Put attribute_name_index and attribute_length. output.putShort(symbolTable.addConstantUtf8(attribute.type)).putInt(attributeContent.length); output.putByteArray(attributeContent.data, 0, attributeContent.length); 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 127361aa136..dca87bcc320 100644 --- a/spring-core/src/main/java/org/springframework/asm/ClassReader.java +++ b/spring-core/src/main/java/org/springframework/asm/ClassReader.java @@ -188,13 +188,14 @@ public class ClassReader { * @param classFileOffset the offset in byteBuffer of the first byte of the ClassFile to be read. * @param checkClassVersion whether to check the class version or not. */ + @SuppressWarnings("PMD.ConstructorCallsOverridableMethod") ClassReader( final byte[] classFileBuffer, final int classFileOffset, final boolean checkClassVersion) { 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.V23) { + if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V24) { throw new IllegalArgumentException( "Unsupported class file major version " + readShort(classFileOffset + 6)); } @@ -340,7 +341,7 @@ public class ClassReader { private static int computeBufferSize(final InputStream inputStream) throws IOException { int expectedLength = inputStream.available(); /* - * Some implementations can return 0 while holding available data (for example, new + * Some implementations can return 0 while holding available data (e.g. new * FileInputStream("/proc/a_file")). Also in some pathological cases a very small number might * be returned, and in this case we use a default size. */ @@ -2311,7 +2312,7 @@ public class ClassReader { { // A forward jump with an offset > 32767. In this case we automatically replace ASM_GOTO // with GOTO_W, ASM_JSR with JSR_W and ASM_IFxxx with IFNOTxxx GOTO_W L:..., - // where IFNOTxxx is the "opposite" opcode of ASMS_IFxxx (for example, IFNE for ASM_IFEQ) and + // where IFNOTxxx is the "opposite" opcode of ASMS_IFxxx (e.g. IFNE for ASM_IFEQ) and // where designates the instruction just after the GOTO_W. // First, change the ASM specific opcodes ASM_IFEQ ... ASM_JSR, ASM_IFNULL and // ASM_IFNONNULL to IFEQ ... JSR, IFNULL and IFNONNULL. @@ -3603,6 +3604,20 @@ public class ClassReader { return classFileBuffer[offset] & 0xFF; } + /** + * Reads several bytes 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 bytes to be read in this {@link ClassReader}. + * @param length the number of bytes to read. + * @return the read bytes. + */ + public byte[] readBytes(final int offset, final int length) { + byte[] result = new byte[length]; + System.arraycopy(classFileBuffer, offset, result, 0, length); + return result; + } + /** * 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. 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 676cd0584b2..0c59b7cd150 100644 --- a/spring-core/src/main/java/org/springframework/asm/ClassWriter.java +++ b/spring-core/src/main/java/org/springframework/asm/ClassWriter.java @@ -264,13 +264,7 @@ public class ClassWriter extends ClassVisitor { super(/* latest api = */ Opcodes.ASM9); this.flags = flags; symbolTable = classReader == null ? new SymbolTable(this) : new SymbolTable(this, classReader); - if ((flags & COMPUTE_FRAMES) != 0) { - compute = MethodWriter.COMPUTE_ALL_FRAMES; - } else if ((flags & COMPUTE_MAXS) != 0) { - compute = MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL; - } else { - compute = MethodWriter.COMPUTE_NOTHING; - } + setFlags(flags); } // ----------------------------------------------------------------------------------------------- @@ -1020,6 +1014,28 @@ public class ClassWriter extends ClassVisitor { return symbolTable.addConstantNameAndType(name, descriptor); } + /** + * Changes the computation strategy of method properties like max stack size, max number of local + * variables, and frames. + * + *

WARNING: {@link #setFlags(int)} method changes the behavior of new method visitors + * returned from {@link #visitMethod(int, String, String, String, String[])}. The behavior will be + * changed only after the next method visitor is returned. All the previously returned method + * visitors keep their previous behavior. + * + * @param flags option flags that can be used to modify the default behavior of this class. Must + * be zero or more of {@link #COMPUTE_MAXS} and {@link #COMPUTE_FRAMES}. + */ + public final void setFlags(final int flags) { + if ((flags & ClassWriter.COMPUTE_FRAMES) != 0) { + compute = MethodWriter.COMPUTE_ALL_FRAMES; + } else if ((flags & ClassWriter.COMPUTE_MAXS) != 0) { + compute = MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL; + } else { + compute = MethodWriter.COMPUTE_NOTHING; + } + } + // ----------------------------------------------------------------------------------------------- // Default method to compute common super classes when computing stack map frames // ----------------------------------------------------------------------------------------------- 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 35ffc81576e..6016a766a7f 100644 --- a/spring-core/src/main/java/org/springframework/asm/MethodVisitor.java +++ b/spring-core/src/main/java/org/springframework/asm/MethodVisitor.java @@ -30,19 +30,20 @@ package org.springframework.asm; /** * A visitor to visit a Java method. The methods of this class must be called in the following * order: ( {@code visitParameter} )* [ {@code visitAnnotationDefault} ] ( {@code visitAnnotation} | - * {@code visitAnnotableParameterCount} | {@code visitParameterAnnotation} {@code + * {@code visitAnnotableParameterCount} | {@code visitParameterAnnotation} | {@code * visitTypeAnnotation} | {@code visitAttribute} )* [ {@code visitCode} ( {@code visitFrame} | * {@code visitXInsn} | {@code visitLabel} | {@code visitInsnAnnotation} | {@code * visitTryCatchBlock} | {@code visitTryCatchAnnotation} | {@code visitLocalVariable} | {@code - * visitLocalVariableAnnotation} | {@code visitLineNumber} )* {@code visitMaxs} ] {@code visitEnd}. - * In addition, the {@code visitXInsn} and {@code visitLabel} methods must be called in the - * sequential order of the bytecode instructions of the visited code, {@code visitInsnAnnotation} - * must be called after the annotated instruction, {@code visitTryCatchBlock} must be called - * before the labels passed as arguments have been visited, {@code - * visitTryCatchBlockAnnotation} must be called after the corresponding try catch block has - * been visited, and the {@code visitLocalVariable}, {@code visitLocalVariableAnnotation} and {@code - * visitLineNumber} methods must be called after the labels passed as arguments have been - * visited. + * visitLocalVariableAnnotation} | {@code visitLineNumber} | {@code visitAttribute} )* {@code + * visitMaxs} ] {@code visitEnd}. In addition, the {@code visitXInsn} and {@code visitLabel} + * methods must be called in the sequential order of the bytecode instructions of the visited code, + * {@code visitInsnAnnotation} must be called after the annotated instruction, {@code + * visitTryCatchBlock} must be called before the labels passed as arguments have been + * visited, {@code visitTryCatchBlockAnnotation} must be called after the corresponding try + * catch block has been visited, and the {@code visitLocalVariable}, {@code + * visitLocalVariableAnnotation} and {@code visitLineNumber} methods must be called after the + * labels passed as arguments have been visited. Finally, the {@code visitAttribute} method must be + * called before {@code visitCode} for non-code attributes, and after it for code attributes. * * @author Eric Bruneton */ 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 a047b88c096..69192d1aa75 100644 --- a/spring-core/src/main/java/org/springframework/asm/Opcodes.java +++ b/spring-core/src/main/java/org/springframework/asm/Opcodes.java @@ -288,6 +288,7 @@ public interface Opcodes { int V21 = 0 << 16 | 65; int V22 = 0 << 16 | 66; int V23 = 0 << 16 | 67; + int V24 = 0 << 16 | 68; /** * Version flag indicating that the class is using 'preview' features. diff --git a/spring-core/src/main/java/org/springframework/asm/Symbol.java b/spring-core/src/main/java/org/springframework/asm/Symbol.java index ce67127aa92..00da4c9ea8e 100644 --- a/spring-core/src/main/java/org/springframework/asm/Symbol.java +++ b/spring-core/src/main/java/org/springframework/asm/Symbol.java @@ -178,7 +178,9 @@ abstract class Symbol { *

  • the symbol's value for {@link #CONSTANT_INTEGER_TAG},{@link #CONSTANT_FLOAT_TAG}, {@link * #CONSTANT_LONG_TAG}, {@link #CONSTANT_DOUBLE_TAG}, *
  • the CONSTANT_MethodHandle_info reference_kind field value for {@link - * #CONSTANT_METHOD_HANDLE_TAG} symbols, + * #CONSTANT_METHOD_HANDLE_TAG} symbols (or this value left shifted by 8 bits for + * reference_kind values larger than or equal to H_INVOKEVIRTUAL and if the method owner is + * an interface), *
  • the CONSTANT_InvokeDynamic_info bootstrap_method_attr_index field value for {@link * #CONSTANT_INVOKE_DYNAMIC_TAG} symbols, *
  • the offset of a bootstrap method in the BootstrapMethods boostrap_methods array, for 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 7e0e7f82fe7..a4e0cf7f23e 100644 --- a/spring-core/src/main/java/org/springframework/asm/SymbolTable.java +++ b/spring-core/src/main/java/org/springframework/asm/SymbolTable.java @@ -221,7 +221,9 @@ final class SymbolTable { classReader.readByte(itemOffset), classReader.readClass(memberRefItemOffset, charBuffer), classReader.readUTF8(nameAndTypeItemOffset, charBuffer), - classReader.readUTF8(nameAndTypeItemOffset + 2, charBuffer)); + classReader.readUTF8(nameAndTypeItemOffset + 2, charBuffer), + classReader.readByte(memberRefItemOffset - 1) + == Symbol.CONSTANT_INTERFACE_METHODREF_TAG); break; case Symbol.CONSTANT_DYNAMIC_TAG: case Symbol.CONSTANT_INVOKE_DYNAMIC_TAG: @@ -830,14 +832,15 @@ final class SymbolTable { final String descriptor, final boolean isInterface) { final int tag = Symbol.CONSTANT_METHOD_HANDLE_TAG; + final int data = getConstantMethodHandleSymbolData(referenceKind, isInterface); // Note that we don't need to include isInterface in the hash computation, because it is // redundant with owner (we can't have the same owner with different isInterface values). - int hashCode = hash(tag, owner, name, descriptor, referenceKind); + int hashCode = hash(tag, owner, name, descriptor, data); Entry entry = get(hashCode); while (entry != null) { if (entry.tag == tag && entry.hashCode == hashCode - && entry.data == referenceKind + && entry.data == data && entry.owner.equals(owner) && entry.name.equals(name) && entry.value.equals(descriptor)) { @@ -851,8 +854,7 @@ final class SymbolTable { constantPool.put112( tag, referenceKind, addConstantMethodref(owner, name, descriptor, isInterface).index); } - return put( - new Entry(constantPoolCount++, tag, owner, name, descriptor, referenceKind, hashCode)); + return put(new Entry(constantPoolCount++, tag, owner, name, descriptor, data, hashCode)); } /** @@ -866,16 +868,36 @@ final class SymbolTable { * @param owner the internal name of a class of interface. * @param name a field or method name. * @param descriptor a field or method descriptor. + * @param isInterface whether owner is an interface or not. */ private void addConstantMethodHandle( final int index, final int referenceKind, final String owner, final String name, - final String descriptor) { + final String descriptor, + final boolean isInterface) { final int tag = Symbol.CONSTANT_METHOD_HANDLE_TAG; - int hashCode = hash(tag, owner, name, descriptor, referenceKind); - add(new Entry(index, tag, owner, name, descriptor, referenceKind, hashCode)); + final int data = getConstantMethodHandleSymbolData(referenceKind, isInterface); + int hashCode = hash(tag, owner, name, descriptor, data); + add(new Entry(index, tag, owner, name, descriptor, data, hashCode)); + } + + /** + * Returns the {@link Symbol#data} field for a CONSTANT_MethodHandle_info Symbol. + * + * @param referenceKind one of {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link + * Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link + * Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, {@link + * Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}. + * @param isInterface whether owner is an interface or not. + */ + private static int getConstantMethodHandleSymbolData( + final int referenceKind, final boolean isInterface) { + if (referenceKind > Opcodes.H_PUTSTATIC && isInterface) { + return referenceKind << 8; + } + return referenceKind; } /**