diff --git a/build.xml b/build.xml index 1464b69ed3..c2ab3dadb0 100644 --- a/build.xml +++ b/build.xml @@ -41,7 +41,7 @@ description="Compile Java sources."> + debug="true" release="17"/> + debug="true" release="17" includeantruntime="no"/> Direct linking = ${directlinking} maven-compiler-plugin 3.13.0 - 1.8 - 1.8 + 17 UTF-8 diff --git a/src/jvm/clojure/asm/AnnotationVisitor.java b/src/jvm/clojure/asm/AnnotationVisitor.java index 940bfb77c6..dd1a221650 100644 --- a/src/jvm/clojure/asm/AnnotationVisitor.java +++ b/src/jvm/clojure/asm/AnnotationVisitor.java @@ -29,8 +29,8 @@ /** * A visitor to visit a Java annotation. The methods of this class must be called in the following - * order: ( visit | visitEnum | visitAnnotation | visitArray )* - * visitEnd. + * order: ( {@code visit} | {@code visitEnum} | {@code visitAnnotation} | {@code visitArray} )* + * {@code visitEnd}. * * @author Eric Bruneton * @author Eugene Kuleshov @@ -38,45 +38,63 @@ public abstract class AnnotationVisitor { /** - * The ASM API version implemented by this visitor. The value of this field must be one of {@link - * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7_EXPERIMENTAL}. + * The ASM API version implemented by this visitor. The value of this field must be one of the + * {@code ASM}x values in {@link Opcodes}. */ 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; /** * Constructs a new {@link AnnotationVisitor}. * - * @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_EXPERIMENTAL}. + * @param api the ASM API version implemented by this visitor. Must be one of the {@code + * ASM}x values in {@link Opcodes}. */ - public AnnotationVisitor(final int api) { + protected AnnotationVisitor(final int api) { this(api, null); } /** * Constructs a new {@link AnnotationVisitor}. * - * @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_EXPERIMENTAL}. + * @param api the ASM API version implemented by this visitor. Must be one of the {@code + * ASM}x values in {@link Opcodes}. * @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 + protected AnnotationVisitor(final int api, final AnnotationVisitor annotationVisitor) { + if (api != Opcodes.ASM9 + && api != Opcodes.ASM8 + && api != Opcodes.ASM7 + && api != Opcodes.ASM6 && api != Opcodes.ASM5 && api != Opcodes.ASM4 - && api != Opcodes.ASM7_EXPERIMENTAL) { - throw new IllegalArgumentException(); + && api != Opcodes.ASM10_EXPERIMENTAL) { + throw new IllegalArgumentException("Unsupported api " + api); + } + if (api == Opcodes.ASM10_EXPERIMENTAL) { + Constants.checkAsmExperimental(this); } this.api = api; this.av = annotationVisitor; } + /** + * The annotation visitor to which this visitor must delegate method calls. May be {@literal + * null}. + * + * @return the annotation visitor to which this visitor must delegate method calls, or {@literal + * null}. + */ + public AnnotationVisitor getDelegate() { + return av; + } + /** * Visits a primitive value of the annotation. * @@ -112,9 +130,9 @@ public void visitEnum(final String name, final String descriptor, final String v * * @param name the value name. * @param descriptor the class descriptor of the nested annotation class. - * @return a visitor to visit the actual nested annotation value, or null if this visitor - * is not interested in visiting this nested annotation. The nested annotation value must - * be fully visited before calling other methods on this annotation visitor. + * @return a visitor to visit the actual nested annotation value, or {@literal null} if this + * visitor is not interested in visiting this nested annotation. The nested annotation + * value must be fully visited before calling other methods on this annotation visitor. */ public AnnotationVisitor visitAnnotation(final String name, final String descriptor) { if (av != null) { @@ -124,13 +142,13 @@ public AnnotationVisitor visitAnnotation(final String name, final String descrip } /** - * Visits an array value of the annotation. Note that arrays of primitive types (such as byte, + * Visits an array value of the annotation. Note that arrays of primitive values (such as byte, * boolean, short, char, int, long, float or double) can be passed as value to {@link #visit - * visit}. This is what {@link ClassReader} does. + * visit}. This is what {@link ClassReader} does for non empty arrays of primitive values. * * @param name the value name. - * @return a visitor to visit the actual array value elements, or null if this visitor is - * not interested in visiting these values. The 'name' parameters passed to the methods of + * @return a visitor to visit the actual array value elements, or {@literal null} if this visitor + * is not interested in visiting these values. The 'name' parameters passed to the methods of * this visitor are ignored. All the array values must be visited before calling other * methods on this annotation visitor. */ diff --git a/src/jvm/clojure/asm/AnnotationWriter.java b/src/jvm/clojure/asm/AnnotationWriter.java index 4ee16510a3..26f240fa84 100644 --- a/src/jvm/clojure/asm/AnnotationWriter.java +++ b/src/jvm/clojure/asm/AnnotationWriter.java @@ -91,7 +91,7 @@ final class AnnotationWriter extends AnnotationVisitor { private AnnotationWriter nextAnnotation; // ----------------------------------------------------------------------------------------------- - // Constructors + // Constructors and factories // ----------------------------------------------------------------------------------------------- /** @@ -104,15 +104,15 @@ 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, final boolean useNamedValues, final ByteVector annotation, final AnnotationWriter previousAnnotation) { - super(Opcodes.ASM6); + super(/* latest api = */ Opcodes.ASM9); this.symbolTable = symbolTable; this.useNamedValues = useNamedValues; this.annotation = annotation; @@ -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 @@ -263,7 +303,7 @@ public AnnotationVisitor visitArray(final String name) { // visit the array elements. Its num_element_value_pairs will correspond to the number of array // elements and will be stored in what is in fact num_values. annotation.put12('[', 0); - return new AnnotationWriter(symbolTable, /* useNamedValues = */ false, annotation, null); + return new AnnotationWriter(symbolTable, /* useNamedValues= */ false, 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/src/jvm/clojure/asm/Attribute.java b/src/jvm/clojure/asm/Attribute.java index dcbe935cd7..32a86fe377 100644 --- a/src/jvm/clojure/asm/Attribute.java +++ b/src/jvm/clojure/asm/Attribute.java @@ -28,12 +28,12 @@ package clojure.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 + * @see JVMS * 4.7 - * @see JVMS + * @see JVMS * 4.7.3 * @author Eric Bruneton * @author Eugene Kuleshov @@ -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 null. + * store a list of class, field, method or Code attributes). May be {@literal null}. */ Attribute nextAttribute; @@ -66,23 +66,23 @@ protected Attribute(final String type) { } /** - * Returns true if this type of attribute is unknown. This means that the attribute + * Returns {@literal true} if this type of attribute is unknown. This means that the attribute * content can't be parsed to extract constant pool references, labels, etc. Instead, the * attribute content is read as an opaque byte array, and written back as is. This can lead to * invalid attributes, if the content actually contains constant pool references, labels, or other * symbolic references that need to be updated when there are changes to the constant pool, the - * method bytecode, etc. The default implementation of this method always returns true. + * method bytecode, etc. The default implementation of this method always returns {@literal true}. * - * @return true if this type of attribute is unknown. + * @return {@literal true} if this type of attribute is unknown. */ public boolean isUnknown() { return true; } /** - * Returns true if this type of attribute is a code attribute. + * Returns {@literal true} if this type of attribute is a Code attribute. * - * @return 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; @@ -91,8 +91,8 @@ public boolean isCodeAttribute() { /** * Returns the labels corresponding to this attribute. * - * @return the labels corresponding to this attribute, or null if this attribute is not a - * code attribute that contains labels. + * @return the labels corresponding to this attribute, or {@literal null} if this attribute is not + * 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 null if the attribute to be read is - * not a code attribute. + * @param labels the labels of the method's code, or {@literal null} if the attribute to be read + * 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 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,15 +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 null - * if they are not code attributes. Corresponds to the 'code' field of the Code attribute. + * @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. */ @@ -226,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 @@ -248,15 +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 null - * if they are not code attributes. Corresponds to the 'code' field of the Code attribute. + * @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( @@ -278,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/src/jvm/clojure/asm/ByteVector.java b/src/jvm/clojure/asm/ByteVector.java index 066dec8a62..43005cb331 100644 --- a/src/jvm/clojure/asm/ByteVector.java +++ b/src/jvm/clojure/asm/ByteVector.java @@ -65,6 +65,15 @@ public ByteVector(final int initialCapacity) { this.length = data.length; } + /** + * Returns the actual number of bytes in this vector. + * + * @return the actual number of bytes in this vector. + */ + public int size() { + return length; + } + /** * Puts a byte into this byte vector. The byte vector is automatically enlarged if necessary. * @@ -239,10 +248,11 @@ public ByteVector putLong(final long longValue) { * @param stringValue a String whose UTF8 encoded length must be less than 65536. * @return this byte vector. */ + // DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility). public ByteVector putUTF8(final String stringValue) { int charLength = stringValue.length(); if (charLength > 65535) { - throw new IllegalArgumentException(); + throw new IllegalArgumentException("UTF8 string too large"); } int currentLength = length; if (currentLength + 2 + charLength > data.length) { @@ -261,7 +271,7 @@ public ByteVector putUTF8(final String stringValue) { currentData[currentLength++] = (byte) charValue; } else { length = currentLength; - return encodeUTF8(stringValue, i, 65535); + return encodeUtf8(stringValue, i, 65535); } } length = currentLength; @@ -280,21 +290,21 @@ public ByteVector putUTF8(final String stringValue) { * encoded characters. * @return this byte vector. */ - final ByteVector encodeUTF8(final String stringValue, final int offset, final int maxByteLength) { + final ByteVector encodeUtf8(final String stringValue, final int offset, final int maxByteLength) { int charLength = stringValue.length(); int byteLength = offset; for (int i = offset; i < charLength; ++i) { char charValue = stringValue.charAt(i); - if (charValue >= '\u0001' && charValue <= '\u007F') { + if (charValue >= 0x0001 && charValue <= 0x007F) { byteLength++; - } else if (charValue <= '\u07FF') { + } else if (charValue <= 0x07FF) { byteLength += 2; } else { byteLength += 3; } } if (byteLength > maxByteLength) { - throw new IllegalArgumentException(); + throw new IllegalArgumentException("UTF8 string too large"); } // Compute where 'byteLength' must be stored in 'data', and store it at this location. int byteLengthOffset = length - offset - 2; @@ -308,9 +318,9 @@ final ByteVector encodeUTF8(final String stringValue, final int offset, final in int currentLength = length; for (int i = offset; i < charLength; ++i) { char charValue = stringValue.charAt(i); - if (charValue >= '\u0001' && charValue <= '\u007F') { + if (charValue >= 0x0001 && charValue <= 0x007F) { data[currentLength++] = (byte) charValue; - } else if (charValue <= '\u07FF') { + } else if (charValue <= 0x07FF) { data[currentLength++] = (byte) (0xC0 | charValue >> 6 & 0x1F); data[currentLength++] = (byte) (0x80 | charValue & 0x3F); } else { @@ -327,7 +337,7 @@ final ByteVector encodeUTF8(final String stringValue, final int offset, final in * Puts an array of bytes into this byte vector. The byte vector is automatically enlarged if * necessary. * - * @param byteArrayValue an array of bytes. May be null to put byteLength null + * @param byteArrayValue an array of bytes. May be {@literal null} to put {@code byteLength} null * bytes into this byte vector. * @param byteOffset index of the first byte of byteArrayValue that must be copied. * @param byteLength number of bytes of byteArrayValue that must be copied. @@ -351,6 +361,9 @@ public ByteVector putByteArray( * @param size number of additional bytes that this byte vector should be able to receive. */ private void enlarge(final int size) { + if (length > data.length) { + throw new AssertionError("Internal error"); + } int doubleCapacity = 2 * data.length; int minimalCapacity = length + size; byte[] newData = new byte[doubleCapacity > minimalCapacity ? doubleCapacity : minimalCapacity]; diff --git a/src/jvm/clojure/asm/ClassReader.java b/src/jvm/clojure/asm/ClassReader.java index 6502e86d3a..7681435438 100644 --- a/src/jvm/clojure/asm/ClassReader.java +++ b/src/jvm/clojure/asm/ClassReader.java @@ -50,10 +50,11 @@ public class ClassReader { public static final int SKIP_CODE = 1; /** - * A flag to skip the SourceFile, SourceDebugExtension, LocalVariableTable, LocalVariableTypeTable - * and LineNumberTable attributes. If this flag is set these attributes are neither parsed nor - * visited (i.e. {@link ClassVisitor#visitSource}, {@link MethodVisitor#visitLocalVariable} and - * {@link MethodVisitor#visitLineNumber} are not called). + * A flag to skip the SourceFile, SourceDebugExtension, LocalVariableTable, + * LocalVariableTypeTable, LineNumberTable and MethodParameters attributes. If this flag is set + * these attributes are neither parsed nor visited (i.e. {@link ClassVisitor#visitSource}, {@link + * MethodVisitor#visitLocalVariable}, {@link MethodVisitor#visitLineNumber} and {@link + * MethodVisitor#visitParameter} are not called). */ public static final int SKIP_DEBUG = 2; @@ -87,9 +88,25 @@ public class ClassReader { */ static final int EXPAND_ASM_INSNS = 256; + /** The maximum size of array to allocate. */ + private static final int MAX_BUFFER_SIZE = 1024 * 1024; + /** 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; + + /** The offset in bytes of the ClassFile's access_flags field. */ + public final int header; + /** * 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,25 +116,31 @@ public class ClassReader { * necessarily start at offset 0. Use {@link #getItem} and {@link #header} to get correct * ClassFile element offsets within this byte array. */ - 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; /** - * The value of each cp_info entry of the ClassFile's constant_pool array, for Constant_Utf8 - * and Constant_Dynamic constants only. The value of constant pool entry i is given by - * cpInfoValues[i]. This cache avoids multiple parsing of those constant pool items. + * The String objects corresponding to the CONSTANT_Utf8 constant pool items. This cache avoids + * multiple parsing of a given CONSTANT_Utf8 constant pool item. + */ + private final String[] constantUtf8Values; + + /** + * The ConstantDynamic objects corresponding to the CONSTANT_Dynamic constant pool items. This + * cache avoids multiple parsing of a given CONSTANT_Dynamic constant pool item. */ - private final Object[] cpInfoValues; + 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 @@ -130,9 +153,6 @@ public class ClassReader { */ private final int maxStringLength; - /** The offset in bytes, in {@link #b}, of the ClassFile's access_flags field. */ - public final int header; - // ----------------------------------------------------------------------------------------------- // Constructors // ----------------------------------------------------------------------------------------------- @@ -154,8 +174,10 @@ public ClassReader(final byte[] classFile) { * @param classFileLength the length in bytes of the ClassFile to be read. */ public ClassReader( - final byte[] classFileBuffer, final int classFileOffset, final int classFileLength) { - this(classFileBuffer, classFileOffset, /* checkClassVersion = */ true); + final byte[] classFileBuffer, + final int classFileOffset, + final int classFileLength) { // NOPMD(UnusedFormalParameter) used for backward compatibility. + this(classFileBuffer, classFileOffset, /* checkClassVersion= */ true); } /** @@ -166,12 +188,14 @@ public 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.V11) { + if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V23) { throw new IllegalArgumentException( "Unsupported class file major version " + readShort(classFileOffset + 6)); } @@ -179,7 +203,7 @@ public ClassReader( // minor_version and major_version fields, which use 4, 2 and 2 bytes respectively. int constantPoolCount = readUnsignedShort(classFileOffset + 8); cpInfoOffsets = new int[constantPoolCount]; - cpInfoValues = new Object[constantPoolCount]; + constantUtf8Values = new String[constantPoolCount]; // Compute the offset of each constant pool entry, as well as a conservative estimate of the // maximum length of the constant pool strings. The first constant pool entry is after the // magic, minor_version, major_version and constant_pool_count fields, which use 4, 2, 2 and 2 @@ -187,6 +211,8 @@ public ClassReader( int currentCpInfoIndex = 1; int currentCpInfoOffset = classFileOffset + 10; int currentMaxStringLength = 0; + boolean hasBootstrapMethods = false; + boolean hasConstantDynamic = false; // The offset of the other entries depend on the total size of all the previous entries. while (currentCpInfoIndex < constantPoolCount) { cpInfoOffsets[currentCpInfoIndex++] = currentCpInfoOffset + 1; @@ -198,9 +224,16 @@ public ClassReader( case Symbol.CONSTANT_INTEGER_TAG: case Symbol.CONSTANT_FLOAT_TAG: case Symbol.CONSTANT_NAME_AND_TYPE_TAG: - case Symbol.CONSTANT_INVOKE_DYNAMIC_TAG: + cpInfoSize = 5; + break; case Symbol.CONSTANT_DYNAMIC_TAG: cpInfoSize = 5; + hasBootstrapMethods = true; + hasConstantDynamic = true; + break; + case Symbol.CONSTANT_INVOKE_DYNAMIC_TAG: + cpInfoSize = 5; + hasBootstrapMethods = true; break; case Symbol.CONSTANT_LONG_TAG: case Symbol.CONSTANT_DOUBLE_TAG: @@ -231,34 +264,16 @@ public ClassReader( } currentCpInfoOffset += cpInfoSize; } - this.maxStringLength = currentMaxStringLength; + maxStringLength = currentMaxStringLength; // The Classfile's access_flags field is just after the last constant pool entry. - this.header = currentCpInfoOffset; + header = currentCpInfoOffset; + + // Allocate the cache of ConstantDynamic values, if there is at least one. + constantDynamicValues = hasConstantDynamic ? new ConstantDynamic[constantPoolCount] : null; // Read the BootstrapMethods attribute, if any (only get the offset of each method). - int currentAttributeOffset = getFirstAttributeOffset(); - int[] currentBootstrapMethodOffsets = null; - for (int i = readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) { - // Read the attribute_info's attribute_name and attribute_length fields. - String attributeName = readUTF8(currentAttributeOffset, new char[maxStringLength]); - int attributeLength = readInt(currentAttributeOffset + 2); - currentAttributeOffset += 6; - if (Constants.BOOTSTRAP_METHODS.equals(attributeName)) { - // Read the num_bootstrap_methods field and create an array of this size. - currentBootstrapMethodOffsets = new int[readUnsignedShort(currentAttributeOffset)]; - // Compute and store the offset of each 'bootstrap_methods' array field entry. - int currentBootstrapMethodOffset = currentAttributeOffset + 2; - for (int j = 0; j < currentBootstrapMethodOffsets.length; ++j) { - currentBootstrapMethodOffsets[j] = currentBootstrapMethodOffset; - // Skip the bootstrap_method_ref and num_bootstrap_arguments fields (2 bytes each), - // as well as the bootstrap_arguments array field (of size num_bootstrap_arguments * 2). - currentBootstrapMethodOffset += - 4 + readUnsignedShort(currentBootstrapMethodOffset + 2) * 2; - } - } - currentAttributeOffset += attributeLength; - } - this.bootstrapMethodOffsets = currentBootstrapMethodOffsets; + bootstrapMethodOffsets = + hasBootstrapMethods ? readBootstrapMethodsAttribute(currentMaxStringLength) : null; } /** @@ -294,19 +309,25 @@ public ClassReader(final String className) throws IOException { * @return the content of the given input stream. * @throws IOException if a problem occurs during reading. */ + @SuppressWarnings("PMD.UseTryWithResources") private static byte[] readStream(final InputStream inputStream, final boolean close) throws IOException { if (inputStream == null) { throw new IOException("Class not found"); } - try { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - byte[] data = new byte[INPUT_STREAM_DATA_CHUNK_SIZE]; + int bufferSize = computeBufferSize(inputStream); + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + byte[] data = new byte[bufferSize]; int bytesRead; - while ((bytesRead = inputStream.read(data, 0, data.length)) != -1) { + int readCount = 0; + while ((bytesRead = inputStream.read(data, 0, bufferSize)) != -1) { outputStream.write(data, 0, bytesRead); + readCount++; } outputStream.flush(); + if (readCount == 1) { + return data; + } return outputStream.toByteArray(); } finally { if (close) { @@ -315,6 +336,19 @@ private static byte[] readStream(final InputStream inputStream, final boolean cl } } + private static int computeBufferSize(final InputStream inputStream) throws IOException { + int expectedLength = inputStream.available(); + /* + * 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. + */ + if (expectedLength < 256) { + return INPUT_STREAM_DATA_CHUNK_SIZE; + } + return Math.min(expectedLength, MAX_BUFFER_SIZE); + } + // ----------------------------------------------------------------------------------------------- // Accessors // ----------------------------------------------------------------------------------------------- @@ -342,10 +376,10 @@ public String getClassName() { } /** - * Returns the internal of name of the super class (see {@link Type#getInternalName()}). For + * Returns the internal name of the super class (see {@link Type#getInternalName()}). For * interfaces, the super class is {@link Object}. * - * @return the internal name of the super class, or null for {@link Object} class. + * @return the internal name of the super class, or {@literal null} for {@link Object} class. * @see ClassVisitor#visit(int, int, String, String, String, String[]) */ public String getSuperName() { @@ -457,6 +491,10 @@ public void accept( String nestHostClass = null; // - The offset of the NestMembers attribute, or 0. int nestMembersOffset = 0; + // - The offset of the PermittedSubclasses attribute, or 0 + int permittedSubclassesOffset = 0; + // - The offset of the Record attribute, or 0. + int recordOffset = 0; // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field). // This list in the reverse order or their order in the ClassFile structure. Attribute attributes = null; @@ -479,6 +517,8 @@ public void accept( nestHostClass = readClass(currentAttributeOffset, charBuffer); } else if (Constants.NEST_MEMBERS.equals(attributeName)) { nestMembersOffset = currentAttributeOffset; + } else if (Constants.PERMITTED_SUBCLASSES.equals(attributeName)) { + permittedSubclassesOffset = currentAttributeOffset; } else if (Constants.SIGNATURE.equals(attributeName)) { signature = readUTF8(currentAttributeOffset, charBuffer); } else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) { @@ -490,21 +530,26 @@ public void accept( } else if (Constants.SYNTHETIC.equals(attributeName)) { accessFlags |= Opcodes.ACC_SYNTHETIC; } else if (Constants.SOURCE_DEBUG_EXTENSION.equals(attributeName)) { + if (attributeLength > classFileBuffer.length - currentAttributeOffset) { + throw new IllegalArgumentException(); + } sourceDebugExtension = - readUTF(currentAttributeOffset, attributeLength, new char[attributeLength]); + readUtf(currentAttributeOffset, attributeLength, new char[attributeLength]); } else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) { runtimeInvisibleAnnotationsOffset = currentAttributeOffset; } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { runtimeInvisibleTypeAnnotationsOffset = currentAttributeOffset; + } else if (Constants.RECORD.equals(attributeName)) { + recordOffset = currentAttributeOffset; + accessFlags |= Opcodes.ACC_RECORD; } else if (Constants.MODULE.equals(attributeName)) { moduleOffset = currentAttributeOffset; } else if (Constants.MODULE_MAIN_CLASS.equals(attributeName)) { moduleMainClass = readClass(currentAttributeOffset, charBuffer); } else if (Constants.MODULE_PACKAGES.equals(attributeName)) { modulePackagesOffset = currentAttributeOffset; - } else if (Constants.BOOTSTRAP_METHODS.equals(attributeName)) { - // This attribute is read in the constructor. - } else { + } else if (!Constants.BOOTSTRAP_METHODS.equals(attributeName)) { + // The BootstrapMethods attribute is read in the constructor. Attribute attribute = readAttribute( attributePrototypes, @@ -533,12 +578,13 @@ public void accept( // Visit the Module, ModulePackages and ModuleMainClass attributes. if (moduleOffset != 0) { - readModule(classVisitor, context, moduleOffset, modulePackagesOffset, moduleMainClass); + readModuleAttributes( + classVisitor, context, moduleOffset, modulePackagesOffset, moduleMainClass); } // Visit the NestHost attribute. if (nestHostClass != null) { - classVisitor.visitNestHostExperimental(nestHostClass); + classVisitor.visitNestHost(nestHostClass); } // Visit the EnclosingMethod attribute. @@ -561,9 +607,9 @@ public void accept( // Parse num_element_value_pairs and element_value_pairs and visit these values. currentAnnotationOffset = readElementValues( - classVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true), + classVisitor.visitAnnotation(annotationDescriptor, /* visible= */ true), currentAnnotationOffset, - /* named = */ true, + /* named= */ true, charBuffer); } } @@ -579,9 +625,9 @@ public void accept( // Parse num_element_value_pairs and element_value_pairs and visit these values. currentAnnotationOffset = readElementValues( - classVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false), + classVisitor.visitAnnotation(annotationDescriptor, /* visible= */ false), currentAnnotationOffset, - /* named = */ true, + /* named= */ true, charBuffer); } } @@ -603,9 +649,9 @@ public void accept( context.currentTypeAnnotationTarget, context.currentTypeAnnotationTargetPath, annotationDescriptor, - /* visible = */ true), + /* visible= */ true), currentAnnotationOffset, - /* named = */ true, + /* named= */ true, charBuffer); } } @@ -627,9 +673,9 @@ public void accept( context.currentTypeAnnotationTarget, context.currentTypeAnnotationTargetPath, annotationDescriptor, - /* visible = */ false), + /* visible= */ false), currentAnnotationOffset, - /* named = */ true, + /* named= */ true, charBuffer); } } @@ -648,11 +694,22 @@ public void accept( int numberOfNestMembers = readUnsignedShort(nestMembersOffset); int currentNestMemberOffset = nestMembersOffset + 2; while (numberOfNestMembers-- > 0) { - classVisitor.visitNestMemberExperimental(readClass(currentNestMemberOffset, charBuffer)); + classVisitor.visitNestMember(readClass(currentNestMemberOffset, charBuffer)); currentNestMemberOffset += 2; } } + // Visit the PermittedSubclasses attribute. + if (permittedSubclassesOffset != 0) { + int numberOfPermittedSubclasses = readUnsignedShort(permittedSubclassesOffset); + int currentPermittedSubclassesOffset = permittedSubclassesOffset + 2; + while (numberOfPermittedSubclasses-- > 0) { + classVisitor.visitPermittedSubclass( + readClass(currentPermittedSubclassesOffset, charBuffer)); + currentPermittedSubclassesOffset += 2; + } + } + // Visit the InnerClasses attribute. if (innerClassesOffset != 0) { int numberOfClasses = readUnsignedShort(innerClassesOffset); @@ -667,6 +724,15 @@ public void accept( } } + // Visit Record components. + if (recordOffset != 0) { + int recordComponentsCount = readUnsignedShort(recordOffset); + recordOffset += 2; + while (recordComponentsCount-- > 0) { + recordOffset = readRecordComponent(classVisitor, context, recordOffset); + } + } + // Visit the fields and methods. int fieldsCount = readUnsignedShort(currentOffset); currentOffset += 2; @@ -688,7 +754,7 @@ public void accept( // ---------------------------------------------------------------------------------------------- /** - * Reads the module attribute and visit it. + * Reads the Module, ModulePackages and ModuleMainClass attributes and visit them. * * @param classVisitor the current class visitor * @param context information about the class being parsed. @@ -696,9 +762,10 @@ 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 readModule( + private void readModuleAttributes( final ClassVisitor classVisitor, final Context context, final int moduleOffset, @@ -793,7 +860,7 @@ private void readModule( currentOffset += 2; } - // Read the 'provides_count' and 'provides' fields. + // Read the 'provides_count' and 'provides' fields. int providesCount = readUnsignedShort(currentOffset); currentOffset += 2; while (providesCount-- > 0) { @@ -813,6 +880,180 @@ private void readModule( moduleVisitor.visitEnd(); } + /** + * Reads a record component and visit it. + * + * @param classVisitor the current class visitor + * @param context information about the class being parsed. + * @param recordComponentOffset the offset of the current record component. + * @return the offset of the first byte following the record component. + */ + private int readRecordComponent( + final ClassVisitor classVisitor, final Context context, final int recordComponentOffset) { + char[] charBuffer = context.charBuffer; + + int currentOffset = recordComponentOffset; + String name = readUTF8(currentOffset, charBuffer); + String descriptor = readUTF8(currentOffset + 2, charBuffer); + currentOffset += 4; + + // Read the record component attributes (the variables are ordered as in Section 4.7 of the + // JVMS). + + // Attribute offsets exclude the attribute_name_index and attribute_length fields. + // - The string corresponding to the Signature attribute, or null. + String signature = null; + // - The offset of the RuntimeVisibleAnnotations attribute, or 0. + int runtimeVisibleAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleAnnotations attribute, or 0. + int runtimeInvisibleAnnotationsOffset = 0; + // - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0. + int runtimeVisibleTypeAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0. + int runtimeInvisibleTypeAnnotationsOffset = 0; + // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field). + // This list in the reverse order or their order in the ClassFile structure. + Attribute attributes = null; + + int attributesCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (attributesCount-- > 0) { + // Read the attribute_info's attribute_name and attribute_length fields. + String attributeName = readUTF8(currentOffset, charBuffer); + int attributeLength = readInt(currentOffset + 2); + currentOffset += 6; + // The tests are sorted in decreasing frequency order (based on frequencies observed on + // typical classes). + if (Constants.SIGNATURE.equals(attributeName)) { + signature = readUTF8(currentOffset, charBuffer); + } else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleTypeAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleTypeAnnotationsOffset = currentOffset; + } else { + Attribute attribute = + readAttribute( + context.attributePrototypes, + attributeName, + currentOffset, + attributeLength, + charBuffer, + -1, + null); + attribute.nextAttribute = attributes; + attributes = attribute; + } + currentOffset += attributeLength; + } + + RecordComponentVisitor recordComponentVisitor = + classVisitor.visitRecordComponent(name, descriptor, signature); + if (recordComponentVisitor == null) { + return currentOffset; + } + + // Visit the RuntimeVisibleAnnotations attribute. + if (runtimeVisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + recordComponentVisitor.visitAnnotation(annotationDescriptor, /* visible= */ true), + currentAnnotationOffset, + /* named= */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleAnnotations attribute. + if (runtimeInvisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + recordComponentVisitor.visitAnnotation(annotationDescriptor, /* visible= */ false), + currentAnnotationOffset, + /* named= */ true, + charBuffer); + } + } + + // Visit the RuntimeVisibleTypeAnnotations attribute. + if (runtimeVisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + recordComponentVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible= */ true), + currentAnnotationOffset, + /* named= */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleTypeAnnotations attribute. + if (runtimeInvisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + recordComponentVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible= */ false), + currentAnnotationOffset, + /* named= */ true, + charBuffer); + } + } + + // Visit the non standard attributes. + while (attributes != null) { + // Copy and reset the nextAttribute field so that it can also be used in FieldWriter. + Attribute nextAttribute = attributes.nextAttribute; + attributes.nextAttribute = null; + recordComponentVisitor.visitAttribute(attributes); + attributes = nextAttribute; + } + + // Visit the end of the field. + recordComponentVisitor.visitEnd(); + return currentOffset; + } + /** * Reads a JVMS field_info structure and makes the given visitor visit it. * @@ -910,9 +1151,9 @@ private int readField( // Parse num_element_value_pairs and element_value_pairs and visit these values. currentAnnotationOffset = readElementValues( - fieldVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true), + fieldVisitor.visitAnnotation(annotationDescriptor, /* visible= */ true), currentAnnotationOffset, - /* named = */ true, + /* named= */ true, charBuffer); } } @@ -928,9 +1169,9 @@ private int readField( // Parse num_element_value_pairs and element_value_pairs and visit these values. currentAnnotationOffset = readElementValues( - fieldVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false), + fieldVisitor.visitAnnotation(annotationDescriptor, /* visible= */ false), currentAnnotationOffset, - /* named = */ true, + /* named= */ true, charBuffer); } } @@ -952,9 +1193,9 @@ private int readField( context.currentTypeAnnotationTarget, context.currentTypeAnnotationTargetPath, annotationDescriptor, - /* visible = */ true), + /* visible= */ true), currentAnnotationOffset, - /* named = */ true, + /* named= */ true, charBuffer); } } @@ -976,9 +1217,9 @@ private int readField( context.currentTypeAnnotationTarget, context.currentTypeAnnotationTargetPath, annotationDescriptor, - /* visible = */ false), + /* visible= */ false), currentAnnotationOffset, - /* named = */ true, + /* named= */ true, charBuffer); } } @@ -1114,7 +1355,7 @@ private int readMethod( context.currentMethodAccessFlags, context.currentMethodName, context.currentMethodDescriptor, - signatureIndex == 0 ? null : readUTF(signatureIndex, charBuffer), + signatureIndex == 0 ? null : readUtf(signatureIndex, charBuffer), exceptions); if (methodVisitor == null) { return currentOffset; @@ -1128,18 +1369,18 @@ 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; } } // Visit the MethodParameters attribute. - if (methodParametersOffset != 0) { + if (methodParametersOffset != 0 && (context.parsingOptions & SKIP_DEBUG) == 0) { int parametersCount = readByte(methodParametersOffset); int currentParameterOffset = methodParametersOffset + 1; while (parametersCount-- > 0) { @@ -1171,9 +1412,9 @@ private int readMethod( // Parse num_element_value_pairs and element_value_pairs and visit these values. currentAnnotationOffset = readElementValues( - methodVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true), + methodVisitor.visitAnnotation(annotationDescriptor, /* visible= */ true), currentAnnotationOffset, - /* named = */ true, + /* named= */ true, charBuffer); } } @@ -1189,9 +1430,9 @@ private int readMethod( // Parse num_element_value_pairs and element_value_pairs and visit these values. currentAnnotationOffset = readElementValues( - methodVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false), + methodVisitor.visitAnnotation(annotationDescriptor, /* visible= */ false), currentAnnotationOffset, - /* named = */ true, + /* named= */ true, charBuffer); } } @@ -1213,9 +1454,9 @@ private int readMethod( context.currentTypeAnnotationTarget, context.currentTypeAnnotationTargetPath, annotationDescriptor, - /* visible = */ true), + /* visible= */ true), currentAnnotationOffset, - /* named = */ true, + /* named= */ true, charBuffer); } } @@ -1237,9 +1478,9 @@ private int readMethod( context.currentTypeAnnotationTarget, context.currentTypeAnnotationTargetPath, annotationDescriptor, - /* visible = */ false), + /* visible= */ false), currentAnnotationOffset, - /* named = */ true, + /* named= */ true, charBuffer); } } @@ -1247,16 +1488,13 @@ private int readMethod( // Visit the RuntimeVisibleParameterAnnotations attribute. if (runtimeVisibleParameterAnnotationsOffset != 0) { readParameterAnnotations( - methodVisitor, context, runtimeVisibleParameterAnnotationsOffset, /* visible = */ true); + methodVisitor, context, runtimeVisibleParameterAnnotationsOffset, /* visible= */ true); } // Visit the RuntimeInvisibleParameterAnnotations attribute. if (runtimeInvisibleParameterAnnotationsOffset != 0) { readParameterAnnotations( - methodVisitor, - context, - runtimeInvisibleParameterAnnotationsOffset, - /* visible = */ false); + methodVisitor, context, runtimeInvisibleParameterAnnotationsOffset, /* visible= */ false); } // Visit the non standard attributes. @@ -1288,20 +1526,23 @@ 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); final int codeLength = readInt(currentOffset + 4); currentOffset += 8; + if (codeLength > classFileBuffer.length - currentOffset) { + throw new IllegalArgumentException(); + } // Read the bytecode 'code' array to create a label for each referenced instruction. final int bytecodeStartOffset = currentOffset; @@ -1309,115 +1550,115 @@ 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: - case Constants.ICONST_M1: - case Constants.ICONST_0: - case Constants.ICONST_1: - case Constants.ICONST_2: - case Constants.ICONST_3: - case Constants.ICONST_4: - case Constants.ICONST_5: - case Constants.LCONST_0: - case Constants.LCONST_1: - case Constants.FCONST_0: - case Constants.FCONST_1: - case Constants.FCONST_2: - case Constants.DCONST_0: - case Constants.DCONST_1: - case Constants.IALOAD: - case Constants.LALOAD: - case Constants.FALOAD: - case Constants.DALOAD: - case Constants.AALOAD: - case Constants.BALOAD: - case Constants.CALOAD: - case Constants.SALOAD: - case Constants.IASTORE: - case Constants.LASTORE: - case Constants.FASTORE: - case Constants.DASTORE: - case Constants.AASTORE: - case Constants.BASTORE: - case Constants.CASTORE: - case Constants.SASTORE: - case Constants.POP: - case Constants.POP2: - case Constants.DUP: - case Constants.DUP_X1: - case Constants.DUP_X2: - case Constants.DUP2: - case Constants.DUP2_X1: - case Constants.DUP2_X2: - case Constants.SWAP: - case Constants.IADD: - case Constants.LADD: - case Constants.FADD: - case Constants.DADD: - case Constants.ISUB: - case Constants.LSUB: - case Constants.FSUB: - case Constants.DSUB: - case Constants.IMUL: - case Constants.LMUL: - case Constants.FMUL: - case Constants.DMUL: - case Constants.IDIV: - case Constants.LDIV: - case Constants.FDIV: - case Constants.DDIV: - case Constants.IREM: - case Constants.LREM: - case Constants.FREM: - case Constants.DREM: - case Constants.INEG: - case Constants.LNEG: - case Constants.FNEG: - case Constants.DNEG: - case Constants.ISHL: - case Constants.LSHL: - case Constants.ISHR: - case Constants.LSHR: - case Constants.IUSHR: - case Constants.LUSHR: - case Constants.IAND: - case Constants.LAND: - case Constants.IOR: - case Constants.LOR: - case Constants.IXOR: - case Constants.LXOR: - case Constants.I2L: - case Constants.I2F: - case Constants.I2D: - case Constants.L2I: - case Constants.L2F: - case Constants.L2D: - case Constants.F2I: - case Constants.F2L: - case Constants.F2D: - case Constants.D2I: - case Constants.D2L: - case Constants.D2F: - case Constants.I2B: - case Constants.I2C: - case Constants.I2S: - case Constants.LCMP: - case Constants.FCMPL: - case Constants.FCMPG: - case Constants.DCMPL: - case Constants.DCMPG: - case Constants.IRETURN: - case Constants.LRETURN: - case Constants.FRETURN: - case Constants.DRETURN: - case Constants.ARETURN: - case Constants.RETURN: - case Constants.ARRAYLENGTH: - case Constants.ATHROW: - case Constants.MONITORENTER: - case Constants.MONITOREXIT: + case Opcodes.NOP: + case Opcodes.ACONST_NULL: + case Opcodes.ICONST_M1: + case Opcodes.ICONST_0: + case Opcodes.ICONST_1: + case Opcodes.ICONST_2: + case Opcodes.ICONST_3: + case Opcodes.ICONST_4: + case Opcodes.ICONST_5: + case Opcodes.LCONST_0: + case Opcodes.LCONST_1: + case Opcodes.FCONST_0: + case Opcodes.FCONST_1: + case Opcodes.FCONST_2: + case Opcodes.DCONST_0: + case Opcodes.DCONST_1: + case Opcodes.IALOAD: + case Opcodes.LALOAD: + case Opcodes.FALOAD: + case Opcodes.DALOAD: + case Opcodes.AALOAD: + case Opcodes.BALOAD: + case Opcodes.CALOAD: + case Opcodes.SALOAD: + case Opcodes.IASTORE: + case Opcodes.LASTORE: + case Opcodes.FASTORE: + case Opcodes.DASTORE: + case Opcodes.AASTORE: + case Opcodes.BASTORE: + case Opcodes.CASTORE: + case Opcodes.SASTORE: + case Opcodes.POP: + case Opcodes.POP2: + case Opcodes.DUP: + case Opcodes.DUP_X1: + case Opcodes.DUP_X2: + case Opcodes.DUP2: + case Opcodes.DUP2_X1: + case Opcodes.DUP2_X2: + case Opcodes.SWAP: + case Opcodes.IADD: + case Opcodes.LADD: + case Opcodes.FADD: + case Opcodes.DADD: + case Opcodes.ISUB: + case Opcodes.LSUB: + case Opcodes.FSUB: + case Opcodes.DSUB: + case Opcodes.IMUL: + case Opcodes.LMUL: + case Opcodes.FMUL: + case Opcodes.DMUL: + case Opcodes.IDIV: + case Opcodes.LDIV: + case Opcodes.FDIV: + case Opcodes.DDIV: + case Opcodes.IREM: + case Opcodes.LREM: + case Opcodes.FREM: + case Opcodes.DREM: + case Opcodes.INEG: + case Opcodes.LNEG: + case Opcodes.FNEG: + case Opcodes.DNEG: + case Opcodes.ISHL: + case Opcodes.LSHL: + case Opcodes.ISHR: + case Opcodes.LSHR: + case Opcodes.IUSHR: + case Opcodes.LUSHR: + case Opcodes.IAND: + case Opcodes.LAND: + case Opcodes.IOR: + case Opcodes.LOR: + case Opcodes.IXOR: + case Opcodes.LXOR: + case Opcodes.I2L: + case Opcodes.I2F: + case Opcodes.I2D: + case Opcodes.L2I: + case Opcodes.L2F: + case Opcodes.L2D: + case Opcodes.F2I: + case Opcodes.F2L: + case Opcodes.F2D: + case Opcodes.D2I: + case Opcodes.D2L: + case Opcodes.D2F: + case Opcodes.I2B: + case Opcodes.I2C: + case Opcodes.I2S: + case Opcodes.LCMP: + case Opcodes.FCMPL: + case Opcodes.FCMPG: + case Opcodes.DCMPL: + case Opcodes.DCMPG: + case Opcodes.IRETURN: + case Opcodes.LRETURN: + case Opcodes.FRETURN: + case Opcodes.DRETURN: + case Opcodes.ARETURN: + case Opcodes.RETURN: + case Opcodes.ARRAYLENGTH: + case Opcodes.ATHROW: + case Opcodes.MONITORENTER: + case Opcodes.MONITOREXIT: case Constants.ILOAD_0: case Constants.ILOAD_1: case Constants.ILOAD_2: @@ -1460,24 +1701,24 @@ private void readCode( case Constants.ASTORE_3: currentOffset += 1; break; - case Constants.IFEQ: - case Constants.IFNE: - case Constants.IFLT: - case Constants.IFGE: - case Constants.IFGT: - case Constants.IFLE: - case Constants.IF_ICMPEQ: - case Constants.IF_ICMPNE: - case Constants.IF_ICMPLT: - case Constants.IF_ICMPGE: - case Constants.IF_ICMPGT: - case Constants.IF_ICMPLE: - case Constants.IF_ACMPEQ: - case Constants.IF_ACMPNE: - case Constants.GOTO: - case Constants.JSR: - case Constants.IFNULL: - case Constants.IFNONNULL: + case Opcodes.IFEQ: + case Opcodes.IFNE: + case Opcodes.IFLT: + case Opcodes.IFGE: + case Opcodes.IFGT: + case Opcodes.IFLE: + case Opcodes.IF_ICMPEQ: + case Opcodes.IF_ICMPNE: + case Opcodes.IF_ICMPLT: + case Opcodes.IF_ICMPGE: + case Opcodes.IF_ICMPGT: + case Opcodes.IF_ICMPLE: + case Opcodes.IF_ACMPEQ: + case Opcodes.IF_ACMPNE: + case Opcodes.GOTO: + case Opcodes.JSR: + case Opcodes.IFNULL: + case Opcodes.IFNONNULL: createLabel(bytecodeOffset + readShort(currentOffset + 1), labels); currentOffset += 3; break; @@ -1509,28 +1750,28 @@ private void readCode( currentOffset += 5; break; case Constants.WIDE: - switch (classFileBuffer[currentOffset + 1] & 0xFF) { - case Constants.ILOAD: - case Constants.FLOAD: - case Constants.ALOAD: - case Constants.LLOAD: - case Constants.DLOAD: - case Constants.ISTORE: - case Constants.FSTORE: - case Constants.ASTORE: - case Constants.LSTORE: - case Constants.DSTORE: - case Constants.RET: + switch (classBuffer[currentOffset + 1] & 0xFF) { + case Opcodes.ILOAD: + case Opcodes.FLOAD: + case Opcodes.ALOAD: + case Opcodes.LLOAD: + case Opcodes.DLOAD: + case Opcodes.ISTORE: + case Opcodes.FSTORE: + case Opcodes.ASTORE: + case Opcodes.LSTORE: + case Opcodes.DSTORE: + case Opcodes.RET: currentOffset += 4; break; - case Constants.IINC: + case Opcodes.IINC: currentOffset += 6; break; default: throw new IllegalArgumentException(); } break; - case Constants.TABLESWITCH: + case Opcodes.TABLESWITCH: // Skip 0 to 3 padding bytes. currentOffset += 4 - (bytecodeOffset & 3); // Read the default label and the number of table entries. @@ -1543,7 +1784,7 @@ private void readCode( currentOffset += 4; } break; - case Constants.LOOKUPSWITCH: + case Opcodes.LOOKUPSWITCH: // Skip 0 to 3 padding bytes. currentOffset += 4 - (bytecodeOffset & 3); // Read the default label and the number of switch cases. @@ -1556,44 +1797,44 @@ private void readCode( currentOffset += 8; } break; - case Constants.ILOAD: - case Constants.LLOAD: - case Constants.FLOAD: - case Constants.DLOAD: - case Constants.ALOAD: - case Constants.ISTORE: - case Constants.LSTORE: - case Constants.FSTORE: - case Constants.DSTORE: - case Constants.ASTORE: - case Constants.RET: - case Constants.BIPUSH: - case Constants.NEWARRAY: - case Constants.LDC: + case Opcodes.ILOAD: + case Opcodes.LLOAD: + case Opcodes.FLOAD: + case Opcodes.DLOAD: + case Opcodes.ALOAD: + case Opcodes.ISTORE: + case Opcodes.LSTORE: + case Opcodes.FSTORE: + case Opcodes.DSTORE: + case Opcodes.ASTORE: + case Opcodes.RET: + case Opcodes.BIPUSH: + case Opcodes.NEWARRAY: + case Opcodes.LDC: currentOffset += 2; break; - case Constants.SIPUSH: + case Opcodes.SIPUSH: case Constants.LDC_W: case Constants.LDC2_W: - case Constants.GETSTATIC: - case Constants.PUTSTATIC: - case Constants.GETFIELD: - case Constants.PUTFIELD: - case Constants.INVOKEVIRTUAL: - case Constants.INVOKESPECIAL: - case Constants.INVOKESTATIC: - case Constants.NEW: - case Constants.ANEWARRAY: - case Constants.CHECKCAST: - case Constants.INSTANCEOF: - case Constants.IINC: + case Opcodes.GETSTATIC: + case Opcodes.PUTSTATIC: + case Opcodes.GETFIELD: + case Opcodes.PUTFIELD: + case Opcodes.INVOKEVIRTUAL: + case Opcodes.INVOKESPECIAL: + case Opcodes.INVOKESTATIC: + case Opcodes.NEW: + case Opcodes.ANEWARRAY: + case Opcodes.CHECKCAST: + case Opcodes.INSTANCEOF: + case Opcodes.IINC: currentOffset += 3; break; - case Constants.INVOKEINTERFACE: - case Constants.INVOKEDYNAMIC: + case Opcodes.INVOKEINTERFACE: + case Opcodes.INVOKEDYNAMIC: currentOffset += 5; break; - case Constants.MULTIANEWARRAY: + case Opcodes.MULTIANEWARRAY: currentOffset += 4; break; default: @@ -1603,18 +1844,15 @@ private void readCode( // Read the 'exception_table_length' and 'exception_table' field to create a label for each // referenced instruction, and to make methodVisitor visit the corresponding try catch blocks. - { - int exceptionTableLength = readUnsignedShort(currentOffset); - currentOffset += 2; - while (exceptionTableLength-- > 0) { - Label start = createLabel(readUnsignedShort(currentOffset), labels); - Label end = createLabel(readUnsignedShort(currentOffset + 2), labels); - Label handler = createLabel(readUnsignedShort(currentOffset + 4), labels); - String catchType = - readUTF8(cpInfoOffsets[readUnsignedShort(currentOffset + 6)], charBuffer); - currentOffset += 8; - methodVisitor.visitTryCatchBlock(start, end, handler, catchType); - } + int exceptionTableLength = readUnsignedShort(currentOffset); + currentOffset += 2; + while (exceptionTableLength-- > 0) { + Label start = createLabel(readUnsignedShort(currentOffset), labels); + Label end = createLabel(readUnsignedShort(currentOffset + 2), labels); + Label handler = createLabel(readUnsignedShort(currentOffset + 4), labels); + String catchType = readUTF8(cpInfoOffsets[readUnsignedShort(currentOffset + 6)], charBuffer); + currentOffset += 8; + methodVisitor.visitTryCatchBlock(start, end, handler, catchType); } // Read the Code attributes to create a label for each referenced instruction (the variables @@ -1685,7 +1923,7 @@ private void readCode( } } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { visibleTypeAnnotationOffsets = - readTypeAnnotations(methodVisitor, context, currentOffset, /* visible = */ true); + readTypeAnnotations(methodVisitor, context, currentOffset, /* visible= */ true); // Here we do not extract the labels corresponding to the attribute content. This would // require a full parsing of the attribute, which would need to be repeated when parsing // the bytecode instructions (see below). Instead, the content of the attribute is read one @@ -1694,7 +1932,7 @@ private void readCode( // time. This assumes that type annotations are ordered by increasing bytecode offset. } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { invisibleTypeAnnotationOffsets = - readTypeAnnotations(methodVisitor, context, currentOffset, /* visible = */ false); + readTypeAnnotations(methodVisitor, context, currentOffset, /* visible= */ false); // Same comment as above for the RuntimeVisibleTypeAnnotations attribute. } else if (Constants.STACK_MAP_TABLE.equals(attributeName)) { if ((context.parsingOptions & SKIP_FRAMES) == 0) { @@ -1761,11 +1999,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); } @@ -1810,6 +2048,7 @@ private void readCode( currentOffset = bytecodeStartOffset; while (currentOffset < bytecodeEndOffset) { final int currentBytecodeOffset = currentOffset - bytecodeStartOffset; + readBytecodeInstructionOffset(currentBytecodeOffset); // Visit the label and the line number(s) for this bytecode offset, if any. Label currentLabel = labels[currentBytecodeOffset]; @@ -1861,115 +2100,115 @@ 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: - case Constants.ICONST_M1: - case Constants.ICONST_0: - case Constants.ICONST_1: - case Constants.ICONST_2: - case Constants.ICONST_3: - case Constants.ICONST_4: - case Constants.ICONST_5: - case Constants.LCONST_0: - case Constants.LCONST_1: - case Constants.FCONST_0: - case Constants.FCONST_1: - case Constants.FCONST_2: - case Constants.DCONST_0: - case Constants.DCONST_1: - case Constants.IALOAD: - case Constants.LALOAD: - case Constants.FALOAD: - case Constants.DALOAD: - case Constants.AALOAD: - case Constants.BALOAD: - case Constants.CALOAD: - case Constants.SALOAD: - case Constants.IASTORE: - case Constants.LASTORE: - case Constants.FASTORE: - case Constants.DASTORE: - case Constants.AASTORE: - case Constants.BASTORE: - case Constants.CASTORE: - case Constants.SASTORE: - case Constants.POP: - case Constants.POP2: - case Constants.DUP: - case Constants.DUP_X1: - case Constants.DUP_X2: - case Constants.DUP2: - case Constants.DUP2_X1: - case Constants.DUP2_X2: - case Constants.SWAP: - case Constants.IADD: - case Constants.LADD: - case Constants.FADD: - case Constants.DADD: - case Constants.ISUB: - case Constants.LSUB: - case Constants.FSUB: - case Constants.DSUB: - case Constants.IMUL: - case Constants.LMUL: - case Constants.FMUL: - case Constants.DMUL: - case Constants.IDIV: - case Constants.LDIV: - case Constants.FDIV: - case Constants.DDIV: - case Constants.IREM: - case Constants.LREM: - case Constants.FREM: - case Constants.DREM: - case Constants.INEG: - case Constants.LNEG: - case Constants.FNEG: - case Constants.DNEG: - case Constants.ISHL: - case Constants.LSHL: - case Constants.ISHR: - case Constants.LSHR: - case Constants.IUSHR: - case Constants.LUSHR: - case Constants.IAND: - case Constants.LAND: - case Constants.IOR: - case Constants.LOR: - case Constants.IXOR: - case Constants.LXOR: - case Constants.I2L: - case Constants.I2F: - case Constants.I2D: - case Constants.L2I: - case Constants.L2F: - case Constants.L2D: - case Constants.F2I: - case Constants.F2L: - case Constants.F2D: - case Constants.D2I: - case Constants.D2L: - case Constants.D2F: - case Constants.I2B: - case Constants.I2C: - case Constants.I2S: - case Constants.LCMP: - case Constants.FCMPL: - case Constants.FCMPG: - case Constants.DCMPL: - case Constants.DCMPG: - case Constants.IRETURN: - case Constants.LRETURN: - case Constants.FRETURN: - case Constants.DRETURN: - case Constants.ARETURN: - case Constants.RETURN: - case Constants.ARRAYLENGTH: - case Constants.ATHROW: - case Constants.MONITORENTER: - case Constants.MONITOREXIT: + case Opcodes.NOP: + case Opcodes.ACONST_NULL: + case Opcodes.ICONST_M1: + case Opcodes.ICONST_0: + case Opcodes.ICONST_1: + case Opcodes.ICONST_2: + case Opcodes.ICONST_3: + case Opcodes.ICONST_4: + case Opcodes.ICONST_5: + case Opcodes.LCONST_0: + case Opcodes.LCONST_1: + case Opcodes.FCONST_0: + case Opcodes.FCONST_1: + case Opcodes.FCONST_2: + case Opcodes.DCONST_0: + case Opcodes.DCONST_1: + case Opcodes.IALOAD: + case Opcodes.LALOAD: + case Opcodes.FALOAD: + case Opcodes.DALOAD: + case Opcodes.AALOAD: + case Opcodes.BALOAD: + case Opcodes.CALOAD: + case Opcodes.SALOAD: + case Opcodes.IASTORE: + case Opcodes.LASTORE: + case Opcodes.FASTORE: + case Opcodes.DASTORE: + case Opcodes.AASTORE: + case Opcodes.BASTORE: + case Opcodes.CASTORE: + case Opcodes.SASTORE: + case Opcodes.POP: + case Opcodes.POP2: + case Opcodes.DUP: + case Opcodes.DUP_X1: + case Opcodes.DUP_X2: + case Opcodes.DUP2: + case Opcodes.DUP2_X1: + case Opcodes.DUP2_X2: + case Opcodes.SWAP: + case Opcodes.IADD: + case Opcodes.LADD: + case Opcodes.FADD: + case Opcodes.DADD: + case Opcodes.ISUB: + case Opcodes.LSUB: + case Opcodes.FSUB: + case Opcodes.DSUB: + case Opcodes.IMUL: + case Opcodes.LMUL: + case Opcodes.FMUL: + case Opcodes.DMUL: + case Opcodes.IDIV: + case Opcodes.LDIV: + case Opcodes.FDIV: + case Opcodes.DDIV: + case Opcodes.IREM: + case Opcodes.LREM: + case Opcodes.FREM: + case Opcodes.DREM: + case Opcodes.INEG: + case Opcodes.LNEG: + case Opcodes.FNEG: + case Opcodes.DNEG: + case Opcodes.ISHL: + case Opcodes.LSHL: + case Opcodes.ISHR: + case Opcodes.LSHR: + case Opcodes.IUSHR: + case Opcodes.LUSHR: + case Opcodes.IAND: + case Opcodes.LAND: + case Opcodes.IOR: + case Opcodes.LOR: + case Opcodes.IXOR: + case Opcodes.LXOR: + case Opcodes.I2L: + case Opcodes.I2F: + case Opcodes.I2D: + case Opcodes.L2I: + case Opcodes.L2F: + case Opcodes.L2D: + case Opcodes.F2I: + case Opcodes.F2L: + case Opcodes.F2D: + case Opcodes.D2I: + case Opcodes.D2L: + case Opcodes.D2F: + case Opcodes.I2B: + case Opcodes.I2C: + case Opcodes.I2S: + case Opcodes.LCMP: + case Opcodes.FCMPL: + case Opcodes.FCMPG: + case Opcodes.DCMPL: + case Opcodes.DCMPG: + case Opcodes.IRETURN: + case Opcodes.LRETURN: + case Opcodes.FRETURN: + case Opcodes.DRETURN: + case Opcodes.ARETURN: + case Opcodes.RETURN: + case Opcodes.ARRAYLENGTH: + case Opcodes.ATHROW: + case Opcodes.MONITORENTER: + case Opcodes.MONITOREXIT: methodVisitor.visitInsn(opcode); currentOffset += 1; break; @@ -2021,24 +2260,24 @@ private void readCode( methodVisitor.visitVarInsn(Opcodes.ISTORE + (opcode >> 2), opcode & 0x3); currentOffset += 1; break; - case Constants.IFEQ: - case Constants.IFNE: - case Constants.IFLT: - case Constants.IFGE: - case Constants.IFGT: - case Constants.IFLE: - case Constants.IF_ICMPEQ: - case Constants.IF_ICMPNE: - case Constants.IF_ICMPLT: - case Constants.IF_ICMPGE: - case Constants.IF_ICMPGT: - case Constants.IF_ICMPLE: - case Constants.IF_ACMPEQ: - case Constants.IF_ACMPNE: - case Constants.GOTO: - case Constants.JSR: - case Constants.IFNULL: - case Constants.IFNONNULL: + case Opcodes.IFEQ: + case Opcodes.IFNE: + case Opcodes.IFLT: + case Opcodes.IFGE: + case Opcodes.IFGT: + case Opcodes.IFLE: + case Opcodes.IF_ICMPEQ: + case Opcodes.IF_ICMPNE: + case Opcodes.IF_ICMPLT: + case Opcodes.IF_ICMPGE: + case Opcodes.IF_ICMPGT: + case Opcodes.IF_ICMPLE: + case Opcodes.IF_ACMPEQ: + case Opcodes.IF_ACMPNE: + case Opcodes.GOTO: + case Opcodes.JSR: + case Opcodes.IFNULL: + case Opcodes.IFNONNULL: methodVisitor.visitJumpInsn( opcode, labels[currentBytecodeOffset + readShort(currentOffset + 1)]); currentOffset += 3; @@ -2099,19 +2338,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)); @@ -2121,7 +2358,7 @@ private void readCode( currentOffset += 4; } break; - case Constants.TABLESWITCH: + case Opcodes.TABLESWITCH: { // Skip 0 to 3 padding bytes. currentOffset += 4 - (currentBytecodeOffset & 3); @@ -2138,17 +2375,17 @@ private void readCode( methodVisitor.visitTableSwitchInsn(low, high, defaultLabel, table); break; } - case Constants.LOOKUPSWITCH: + case Opcodes.LOOKUPSWITCH: { // Skip 0 to 3 padding bytes. currentOffset += 4 - (currentBytecodeOffset & 3); // Read the instruction. Label defaultLabel = labels[currentBytecodeOffset + readInt(currentOffset)]; - int nPairs = readInt(currentOffset + 4); + int numPairs = readInt(currentOffset + 4); currentOffset += 8; - int[] keys = new int[nPairs]; - Label[] values = new Label[nPairs]; - for (int i = 0; i < nPairs; ++i) { + int[] keys = new int[numPairs]; + Label[] values = new Label[numPairs]; + for (int i = 0; i < numPairs; ++i) { keys[i] = readInt(currentOffset); values[i] = labels[currentBytecodeOffset + readInt(currentOffset + 4)]; currentOffset += 8; @@ -2156,32 +2393,31 @@ private void readCode( methodVisitor.visitLookupSwitchInsn(defaultLabel, keys, values); break; } - case Constants.ILOAD: - case Constants.LLOAD: - case Constants.FLOAD: - case Constants.DLOAD: - case Constants.ALOAD: - case Constants.ISTORE: - case Constants.LSTORE: - case Constants.FSTORE: - case Constants.DSTORE: - case Constants.ASTORE: - case Constants.RET: - methodVisitor.visitVarInsn(opcode, classFileBuffer[currentOffset + 1] & 0xFF); + case Opcodes.ILOAD: + case Opcodes.LLOAD: + case Opcodes.FLOAD: + case Opcodes.DLOAD: + case Opcodes.ALOAD: + case Opcodes.ISTORE: + case Opcodes.LSTORE: + case Opcodes.FSTORE: + case Opcodes.DSTORE: + case Opcodes.ASTORE: + case Opcodes.RET: + methodVisitor.visitVarInsn(opcode, classBuffer[currentOffset + 1] & 0xFF); currentOffset += 2; break; - case Constants.BIPUSH: - case Constants.NEWARRAY: - methodVisitor.visitIntInsn(opcode, classFileBuffer[currentOffset + 1]); + case Opcodes.BIPUSH: + case Opcodes.NEWARRAY: + methodVisitor.visitIntInsn(opcode, classBuffer[currentOffset + 1]); currentOffset += 2; break; - case Constants.SIPUSH: + case Opcodes.SIPUSH: methodVisitor.visitIntInsn(opcode, readShort(currentOffset + 1)); currentOffset += 3; break; - case Constants.LDC: - methodVisitor.visitLdcInsn( - readConst(classFileBuffer[currentOffset + 1] & 0xFF, charBuffer)); + case Opcodes.LDC: + methodVisitor.visitLdcInsn(readConst(classBuffer[currentOffset + 1] & 0xFF, charBuffer)); currentOffset += 2; break; case Constants.LDC_W: @@ -2189,14 +2425,14 @@ private void readCode( methodVisitor.visitLdcInsn(readConst(readUnsignedShort(currentOffset + 1), charBuffer)); currentOffset += 3; break; - case Constants.GETSTATIC: - case Constants.PUTSTATIC: - case Constants.GETFIELD: - case Constants.PUTFIELD: - case Constants.INVOKEVIRTUAL: - case Constants.INVOKESPECIAL: - case Constants.INVOKESTATIC: - case Constants.INVOKEINTERFACE: + case Opcodes.GETSTATIC: + case Opcodes.PUTSTATIC: + case Opcodes.GETFIELD: + case Opcodes.PUTFIELD: + case Opcodes.INVOKEVIRTUAL: + case Opcodes.INVOKESPECIAL: + case Opcodes.INVOKESTATIC: + case Opcodes.INVOKEINTERFACE: { int cpInfoOffset = cpInfoOffsets[readUnsignedShort(currentOffset + 1)]; int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 2)]; @@ -2207,7 +2443,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) { @@ -2217,7 +2453,7 @@ private void readCode( } break; } - case Constants.INVOKEDYNAMIC: + case Opcodes.INVOKEDYNAMIC: { int cpInfoOffset = cpInfoOffsets[readUnsignedShort(currentOffset + 1)]; int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 2)]; @@ -2239,21 +2475,21 @@ private void readCode( currentOffset += 5; break; } - case Constants.NEW: - case Constants.ANEWARRAY: - case Constants.CHECKCAST: - case Constants.INSTANCEOF: + case Opcodes.NEW: + case Opcodes.ANEWARRAY: + case Opcodes.CHECKCAST: + case Opcodes.INSTANCEOF: methodVisitor.visitTypeInsn(opcode, readClass(currentOffset + 1, charBuffer)); currentOffset += 3; break; - case Constants.IINC: + case Opcodes.IINC: methodVisitor.visitIincInsn( - classFileBuffer[currentOffset + 1] & 0xFF, classFileBuffer[currentOffset + 2]); + classBuffer[currentOffset + 1] & 0xFF, classBuffer[currentOffset + 2]); currentOffset += 3; break; - case Constants.MULTIANEWARRAY: + case Opcodes.MULTIANEWARRAY: methodVisitor.visitMultiANewArrayInsn( - readClass(currentOffset + 1, charBuffer), classFileBuffer[currentOffset + 3] & 0xFF); + readClass(currentOffset + 1, charBuffer), classBuffer[currentOffset + 3] & 0xFF); currentOffset += 4; break; default: @@ -2278,9 +2514,9 @@ private void readCode( context.currentTypeAnnotationTarget, context.currentTypeAnnotationTargetPath, annotationDescriptor, - /* visible = */ true), + /* visible= */ true), currentAnnotationOffset, - /* named = */ true, + /* named= */ true, charBuffer); } currentVisibleTypeAnnotationBytecodeOffset = @@ -2306,9 +2542,9 @@ private void readCode( context.currentTypeAnnotationTarget, context.currentTypeAnnotationTargetPath, annotationDescriptor, - /* visible = */ false), + /* visible= */ false), currentAnnotationOffset, - /* named = */ true, + /* named= */ true, charBuffer); } currentInvisibleTypeAnnotationBytecodeOffset = @@ -2361,12 +2597,12 @@ private void readCode( // Visit the local variable type annotations of the RuntimeVisibleTypeAnnotations attribute. if (visibleTypeAnnotationOffsets != null) { - for (int i = 0; i < visibleTypeAnnotationOffsets.length; ++i) { - int targetType = readByte(visibleTypeAnnotationOffsets[i]); + for (int typeAnnotationOffset : visibleTypeAnnotationOffsets) { + int targetType = readByte(typeAnnotationOffset); if (targetType == TypeReference.LOCAL_VARIABLE || targetType == TypeReference.RESOURCE_VARIABLE) { // Parse the target_type, target_info and target_path fields. - currentOffset = readTypeAnnotationTarget(context, visibleTypeAnnotationOffsets[i]); + currentOffset = readTypeAnnotationTarget(context, typeAnnotationOffset); // Parse the type_index field. String annotationDescriptor = readUTF8(currentOffset, charBuffer); currentOffset += 2; @@ -2379,9 +2615,9 @@ private void readCode( context.currentLocalVariableAnnotationRangeEnds, context.currentLocalVariableAnnotationRangeIndices, annotationDescriptor, - /* visible = */ true), + /* visible= */ true), currentOffset, - /* named = */ true, + /* named= */ true, charBuffer); } } @@ -2389,12 +2625,12 @@ private void readCode( // Visit the local variable type annotations of the RuntimeInvisibleTypeAnnotations attribute. if (invisibleTypeAnnotationOffsets != null) { - for (int i = 0; i < invisibleTypeAnnotationOffsets.length; ++i) { - int targetType = readByte(invisibleTypeAnnotationOffsets[i]); + for (int typeAnnotationOffset : invisibleTypeAnnotationOffsets) { + int targetType = readByte(typeAnnotationOffset); if (targetType == TypeReference.LOCAL_VARIABLE || targetType == TypeReference.RESOURCE_VARIABLE) { // Parse the target_type, target_info and target_path fields. - currentOffset = readTypeAnnotationTarget(context, invisibleTypeAnnotationOffsets[i]); + currentOffset = readTypeAnnotationTarget(context, typeAnnotationOffset); // Parse the type_index field. String annotationDescriptor = readUTF8(currentOffset, charBuffer); currentOffset += 2; @@ -2407,9 +2643,9 @@ private void readCode( context.currentLocalVariableAnnotationRangeEnds, context.currentLocalVariableAnnotationRangeIndices, annotationDescriptor, - /* visible = */ false), + /* visible= */ false), currentOffset, - /* named = */ true, + /* named= */ true, charBuffer); } } @@ -2428,6 +2664,20 @@ private void readCode( methodVisitor.visitMaxs(maxStack, maxLocals); } + /** + * Handles the bytecode offset of the next instruction to be visited in {@link + * #accept(ClassVisitor,int)}. This method is called just before the instruction and before its + * associated label and stack map frame, if any. The default implementation of this method does + * nothing. Subclasses can override this method to store the argument in a mutable field, for + * instance, so that {@link MethodVisitor} instances can get the bytecode offset of each visited + * instruction (if so, the usual concurrency issues related to mutable data should be addressed). + * + * @param bytecodeOffset the bytecode offset of the next instruction to be visited. + */ + protected void readBytecodeInstructionOffset(final int bytecodeOffset) { + // Do nothing by default. + } + /** * Returns the label corresponding to the given bytecode offset. The default implementation of * this method creates a label for the given offset if it has not been already created. @@ -2557,7 +2807,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); @@ -2568,7 +2818,7 @@ private int[] readTypeAnnotations( methodVisitor.visitTryCatchAnnotation( targetType & 0xFFFFFF00, path, annotationDescriptor, visible), currentOffset, - /* named = */ true, + /* named= */ true, charBuffer); } else { // We don't want to visit the other target_type annotations, so we just skip them (which @@ -2579,7 +2829,7 @@ private int[] readTypeAnnotations( // with a null AnnotationVisitor). currentOffset = readElementValues( - /* annotationVisitor = */ null, currentOffset, /* named = */ true, charBuffer); + /* annotationVisitor= */ null, currentOffset, /* named= */ true, charBuffer); } } return typeAnnotationsOffsets; @@ -2590,7 +2840,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. @@ -2682,7 +2932,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; } @@ -2704,7 +2954,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) { @@ -2719,7 +2969,7 @@ private void readParameterAnnotations( readElementValues( methodVisitor.visitParameterAnnotation(i, annotationDescriptor, visible), currentOffset, - /* named = */ true, + /* named= */ true, charBuffer); } } @@ -2759,7 +3009,7 @@ private int readElementValues( // Parse the array_value array. while (numElementValuePairs-- > 0) { currentOffset = - readElementValue(annotationVisitor, currentOffset, /* named = */ null, charBuffer); + readElementValue(annotationVisitor, currentOffset, /* elementName= */ null, charBuffer); } } if (annotationVisitor != null) { @@ -2772,9 +3022,9 @@ 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 elementName the name of the element_value structure to be read, or null. + * @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. */ @@ -2785,18 +3035,18 @@ 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 - return readElementValues(null, currentOffset + 3, /* named = */ true, charBuffer); + return readElementValues(null, currentOffset + 3, /* named= */ true, charBuffer); case '[': // array_value - return readElementValues(null, currentOffset + 1, /* named = */ false, charBuffer); + return readElementValues(null, currentOffset + 1, /* named= */ false, charBuffer); default: 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)])); @@ -2859,10 +3109,10 @@ private int readElementValue( return readElementValues( annotationVisitor.visitArray(elementName), currentOffset - 2, - /* named = */ false, + /* 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++) { @@ -2936,7 +3186,7 @@ private int readElementValue( readElementValues( annotationVisitor.visitArray(elementName), currentOffset - 2, - /* named = */ false, + /* named= */ false, charBuffer); break; } @@ -2960,12 +3210,12 @@ private int readElementValue( private void computeImplicitFrame(final Context context) { String methodDescriptor = context.currentMethodDescriptor; Object[] locals = context.currentFrameLocalTypes; - int nLocal = 0; + int numLocal = 0; if ((context.currentMethodAccessFlags & Opcodes.ACC_STATIC) == 0) { if ("".equals(context.currentMethodName)) { - locals[nLocal++] = Opcodes.UNINITIALIZED_THIS; + locals[numLocal++] = Opcodes.UNINITIALIZED_THIS; } else { - locals[nLocal++] = readClass(header + 2, context.charBuffer); + locals[numLocal++] = readClass(header + 2, context.charBuffer); } } // Parse the method descriptor, one argument type descriptor at each iteration. Start by @@ -2979,16 +3229,16 @@ private void computeImplicitFrame(final Context context) { case 'B': case 'S': case 'I': - locals[nLocal++] = Opcodes.INTEGER; + locals[numLocal++] = Opcodes.INTEGER; break; case 'F': - locals[nLocal++] = Opcodes.FLOAT; + locals[numLocal++] = Opcodes.FLOAT; break; case 'J': - locals[nLocal++] = Opcodes.LONG; + locals[numLocal++] = Opcodes.LONG; break; case 'D': - locals[nLocal++] = Opcodes.DOUBLE; + locals[numLocal++] = Opcodes.DOUBLE; break; case '[': while (methodDescriptor.charAt(currentMethodDescritorOffset) == '[') { @@ -3000,7 +3250,7 @@ private void computeImplicitFrame(final Context context) { ++currentMethodDescritorOffset; } } - locals[nLocal++] = + locals[numLocal++] = methodDescriptor.substring( currentArgumentDescriptorStartOffset, ++currentMethodDescritorOffset); break; @@ -3008,12 +3258,12 @@ private void computeImplicitFrame(final Context context) { while (methodDescriptor.charAt(currentMethodDescritorOffset) != ';') { ++currentMethodDescritorOffset; } - locals[nLocal++] = + locals[numLocal++] = methodDescriptor.substring( currentArgumentDescriptorStartOffset + 1, currentMethodDescritorOffset++); break; default: - context.currentFrameLocalCount = nLocal; + context.currentFrameLocalCount = numLocal; return; } } @@ -3024,9 +3274,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}. @@ -3044,7 +3294,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; @@ -3139,7 +3389,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; @@ -3180,7 +3430,13 @@ private int readVerificationTypeInfo( // Methods to parse attributes // ---------------------------------------------------------------------------------------------- - /** @return 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 #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 // each), as well as the interfaces array field (2 bytes per interface). @@ -3222,21 +3478,57 @@ final int getFirstAttributeOffset() { } /** - * Reads a non standard JVMS 'attribute' structure in {@link #b}. + * Reads the BootstrapMethods attribute to compute the offset of each bootstrap method. + * + * @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. + */ + private int[] readBootstrapMethodsAttribute(final int maxStringLength) { + char[] charBuffer = new char[maxStringLength]; + int currentAttributeOffset = getFirstAttributeOffset(); + for (int i = readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) { + // Read the attribute_info's attribute_name and attribute_length fields. + String attributeName = readUTF8(currentAttributeOffset, charBuffer); + int attributeLength = readInt(currentAttributeOffset + 2); + currentAttributeOffset += 6; + if (Constants.BOOTSTRAP_METHODS.equals(attributeName)) { + // Read the num_bootstrap_methods field and create an array of this size. + int[] result = new int[readUnsignedShort(currentAttributeOffset)]; + // Compute and store the offset of each 'bootstrap_methods' array field entry. + int currentBootstrapMethodOffset = currentAttributeOffset + 2; + for (int j = 0; j < result.length; ++j) { + result[j] = currentBootstrapMethodOffset; + // Skip the bootstrap_method_ref and num_bootstrap_arguments fields (2 bytes each), + // as well as the bootstrap_arguments array field (of size num_bootstrap_arguments * 2). + currentBootstrapMethodOffset += + 4 + readUnsignedShort(currentBootstrapMethodOffset + 2) * 2; + } + return result; + } + currentAttributeOffset += attributeLength; + } + throw new IllegalArgumentException(); + } + + /** + * 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 labels the labels of the method's code, or null if the attribute to be read is - * not a code attribute. + * @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. */ private Attribute readAttribute( @@ -3247,9 +3539,9 @@ private Attribute readAttribute( final char[] charBuffer, final int codeAttributeOffset, final Label[] labels) { - for (int i = 0; i < attributePrototypes.length; ++i) { - if (attributePrototypes[i].type.equals(type)) { - return attributePrototypes[i].read( + for (Attribute attributePrototype : attributePrototypes) { + if (attributePrototype.type.equals(type)) { + return attributePrototype.read( this, offset, length, charBuffer, codeAttributeOffset, labels); } } @@ -3270,13 +3562,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]; @@ -3294,60 +3587,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) { @@ -3357,25 +3650,27 @@ 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. */ + // DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility). public String readUTF8(final int offset, final char[] charBuffer) { int constantPoolEntryIndex = readUnsignedShort(offset); if (offset == 0 || constantPoolEntryIndex == 0) { return null; } - return readUTF(constantPoolEntryIndex, charBuffer); + return readUtf(constantPoolEntryIndex, 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. @@ -3383,19 +3678,18 @@ public String readUTF8(final int offset, final char[] charBuffer) { * large. It is not automatically resized. * @return the String corresponding to the specified CONSTANT_Utf8 entry. */ - final String readUTF(final int constantPoolEntryIndex, final char[] charBuffer) { - String value = (String) cpInfoValues[constantPoolEntryIndex]; + final String readUtf(final int constantPoolEntryIndex, final char[] charBuffer) { + String value = constantUtf8Values[constantPoolEntryIndex]; if (value != null) { return value; } int cpInfoOffset = cpInfoOffsets[constantPoolEntryIndex]; - value = readUTF(cpInfoOffset + 2, readUnsignedShort(cpInfoOffset), charBuffer); - cpInfoValues[constantPoolEntryIndex] = value; - return value; + return constantUtf8Values[constantPoolEntryIndex] = + readUtf(cpInfoOffset + 2, readUnsignedShort(cpInfoOffset), 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. @@ -3403,24 +3697,24 @@ final String readUTF(final int constantPoolEntryIndex, final char[] charBuffer) * large. It is not automatically resized. * @return the String corresponding to the specified UTF8 string. */ - private String readUTF(final int utfOffset, final int utfLength, final char[] charBuffer) { + private String readUtf(final int utfOffset, final int utfLength, final char[] charBuffer) { 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); @@ -3428,12 +3722,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. @@ -3445,11 +3740,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. @@ -3459,11 +3755,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. @@ -3473,11 +3770,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. @@ -3487,7 +3785,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. @@ -3497,7 +3795,7 @@ public String readPackage(final int offset, final char[] charBuffer) { */ private ConstantDynamic readConstantDynamic( final int constantPoolEntryIndex, final char[] charBuffer) { - ConstantDynamic constantDynamic = (ConstantDynamic) cpInfoValues[constantPoolEntryIndex]; + ConstantDynamic constantDynamic = constantDynamicValues[constantPoolEntryIndex]; if (constantDynamic != null) { return constantDynamic; } @@ -3513,14 +3811,14 @@ private ConstantDynamic readConstantDynamic( bootstrapMethodArguments[i] = readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer); bootstrapMethodOffset += 2; } - constantDynamic = new ConstantDynamic(name, descriptor, handle, bootstrapMethodArguments); - cpInfoValues[constantPoolEntryIndex] = constantDynamic; - return constantDynamic; + return constantDynamicValues[constantPoolEntryIndex] = + new ConstantDynamic(name, descriptor, handle, bootstrapMethodArguments); } /** - * 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, @@ -3533,7 +3831,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: @@ -3556,7 +3854,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/src/jvm/clojure/asm/ClassTooLargeException.java b/src/jvm/clojure/asm/ClassTooLargeException.java new file mode 100644 index 0000000000..7934b4ad55 --- /dev/null +++ b/src/jvm/clojure/asm/ClassTooLargeException.java @@ -0,0 +1,72 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package clojure.asm; + +/** + * Exception thrown when the constant pool of a class produced by a {@link ClassWriter} is too + * large. + * + * @author Jason Zaugg + */ +public final class ClassTooLargeException extends IndexOutOfBoundsException { + private static final long serialVersionUID = 160715609518896765L; + + private final String className; + private final int constantPoolCount; + + /** + * Constructs a new {@link ClassTooLargeException}. + * + * @param className the internal name of the class (see {@link + * clojure.asm.Type#getInternalName()}). + * @param constantPoolCount the number of constant pool items of the class. + */ + public ClassTooLargeException(final String className, final int constantPoolCount) { + super("Class too large: " + className); + this.className = className; + this.constantPoolCount = constantPoolCount; + } + + /** + * Returns the internal name of the class (see {@link clojure.asm.Type#getInternalName()}). + * + * @return the internal name of the class. + */ + public String getClassName() { + return className; + } + + /** + * Returns the number of constant pool items of the class. + * + * @return the number of constant pool items of the class. + */ + public int getConstantPoolCount() { + return constantPoolCount; + } +} diff --git a/src/jvm/clojure/asm/ClassVisitor.java b/src/jvm/clojure/asm/ClassVisitor.java index dc20a8107a..5e1f1c9931 100644 --- a/src/jvm/clojure/asm/ClassVisitor.java +++ b/src/jvm/clojure/asm/ClassVisitor.java @@ -29,70 +29,85 @@ /** * A visitor to visit a Java class. The methods of this class must be called in the following order: - * visit [ visitSource ] [ visitModule ][ visitNestHost ][ - * visitOuterClass ] ( visitAnnotation | visitTypeAnnotation | - * visitAttribute )* ( visitNestMember | visitInnerClass | - * visitField | visitMethod )* visitEnd. + * {@code visit} [ {@code visitSource} ] [ {@code visitModule} ][ {@code visitNestHost} ][ {@code + * visitOuterClass} ] ( {@code visitAnnotation} | {@code visitTypeAnnotation} | {@code + * visitAttribute} )* ( {@code visitNestMember} | [ {@code * visitPermittedSubclass} ] | {@code + * visitInnerClass} | {@code visitRecordComponent} | {@code visitField} | {@code visitMethod} )* + * {@code visitEnd}. * * @author Eric Bruneton */ public abstract class ClassVisitor { /** - * The ASM API version implemented by this visitor. The value of this field must be one of {@link - * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7_EXPERIMENTAL}. + * The ASM API version implemented by this visitor. The value of this field must be one of the + * {@code ASM}x values in {@link Opcodes}. */ 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; /** * Constructs a new {@link ClassVisitor}. * - * @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_EXPERIMENTAL}. + * @param api the ASM API version implemented by this visitor. Must be one of the {@code + * ASM}x values in {@link Opcodes}. */ - public ClassVisitor(final int api) { + protected ClassVisitor(final int api) { this(api, null); } /** * Constructs a new {@link ClassVisitor}. * - * @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_EXPERIMENTAL}. + * @param api the ASM API version implemented by this visitor. Must be one of the {@code + * ASM}x values in {@link Opcodes}. * @param classVisitor the class visitor to which this visitor must delegate method calls. May be * null. */ - public ClassVisitor(final int api, final ClassVisitor classVisitor) { - if (api != Opcodes.ASM6 + protected ClassVisitor(final int api, final ClassVisitor classVisitor) { + if (api != Opcodes.ASM9 + && api != Opcodes.ASM8 + && api != Opcodes.ASM7 + && api != Opcodes.ASM6 && api != Opcodes.ASM5 && api != Opcodes.ASM4 - && api != Opcodes.ASM7_EXPERIMENTAL) { - throw new IllegalArgumentException(); + && api != Opcodes.ASM10_EXPERIMENTAL) { + throw new IllegalArgumentException("Unsupported api " + api); + } + if (api == Opcodes.ASM10_EXPERIMENTAL) { + Constants.checkAsmExperimental(this); } this.api = api; this.cv = classVisitor; } + /** + * The class visitor to which this visitor must delegate method calls. May be {@literal null}. + * + * @return the class visitor to which this visitor must delegate method calls, or {@literal null}. + */ + public ClassVisitor getDelegate() { + return cv; + } + /** * Visits the header of the class. * * @param version the class version. The minor version is stored in the 16 most significant bits, * and the major version in the 16 least significant bits. * @param access the class's access flags (see {@link Opcodes}). This parameter also indicates if - * the class is deprecated. + * the class is deprecated {@link Opcodes#ACC_DEPRECATED} or a record {@link + * Opcodes#ACC_RECORD}. * @param name the internal name of the class (see {@link Type#getInternalName()}). - * @param signature the signature of this class. May be null if the class is not a + * @param signature the signature of this class. May be {@literal null} if the class is not a * generic one, and does not extend or implement generic classes or interfaces. * @param superName the internal of name of the super class (see {@link Type#getInternalName()}). - * For interfaces, the super class is {@link Object}. May be null, but only for the + * For interfaces, the super class is {@link Object}. May be {@literal null}, but only for the * {@link Object} class. * @param interfaces the internal names of the class's interfaces (see {@link - * Type#getInternalName()}). May be null. + * Type#getInternalName()}). May be {@literal null}. */ public void visit( final int version, @@ -101,6 +116,9 @@ public void visit( final String signature, final String superName, final String[] interfaces) { + if (api < Opcodes.ASM8 && (access & Opcodes.ACC_RECORD) != 0) { + throw new UnsupportedOperationException("Records requires ASM8"); + } if (cv != null) { cv.visit(version, access, name, signature, superName, interfaces); } @@ -109,10 +127,10 @@ public void visit( /** * Visits the source of the class. * - * @param source the name of the source file from which the class was compiled. May be - * null. + * @param source the name of the source file from which the class was compiled. May be {@literal + * null}. * @param debug additional debug information to compute the correspondence between source and - * compiled elements of the class. May be null. + * compiled elements of the class. May be {@literal null}. */ public void visitSource(final String source, final String debug) { if (cv != null) { @@ -126,13 +144,13 @@ public void visitSource(final String source, final String debug) { * @param name the fully qualified name (using dots) of the module. * @param access the module access flags, among {@code ACC_OPEN}, {@code ACC_SYNTHETIC} and {@code * ACC_MANDATED}. - * @param version the module version, or null. - * @return a visitor to visit the module values, or null if this visitor is not + * @param version the module version, or {@literal null}. + * @return a visitor to visit the module values, or {@literal null} if this visitor is not * interested in visiting this module. */ public ModuleVisitor visitModule(final String name, final int access, final String version) { if (api < Opcodes.ASM6) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Module requires ASM6"); } if (cv != null) { return cv.visitModule(name, access, version); @@ -141,36 +159,39 @@ public ModuleVisitor visitModule(final String name, final int access, final Stri } /** - * Experimental, use at your own risk. This method will be renamed when it becomes stable, this - * will break existing code using it. Visits the nest host class of the class. A nest is a set - * of classes of the same package that share access to their private members. One of these - * classes, called the host, lists the other members of the nest, which in turn should link to the - * host of their nest. This method must be called only once and only if the visited class is a - * non-host member of a nest. A class is implicitly its own nest, so it's invalid to call this - * method with the visited class name as argument. + * Visits the nest host class of the class. A nest is a set of classes of the same package that + * share access to their private members. One of these classes, called the host, lists the other + * members of the nest, which in turn should link to the host of their nest. This method must be + * called only once and only if the visited class is a non-host member of a nest. A class is + * implicitly its own nest, so it's invalid to call this method with the visited class name as + * argument. * - * @param nestHost the internal name of the host class of the nest. - * @deprecated This API is experimental. + * @param nestHost the internal name of the host class of the nest (see {@link + * Type#getInternalName()}). */ - @Deprecated - public void visitNestHostExperimental(final String nestHost) { - if (api < Opcodes.ASM7_EXPERIMENTAL) { - throw new UnsupportedOperationException(); + public void visitNestHost(final String nestHost) { + if (api < Opcodes.ASM7) { + throw new UnsupportedOperationException("NestHost requires ASM7"); } if (cv != null) { - cv.visitNestHostExperimental(nestHost); + cv.visitNestHost(nestHost); } } /** - * Visits the enclosing class of the class. This method must be called only if the class has an - * enclosing class. + * Visits the enclosing class of the class. This method must be called only if this class is a + * local or anonymous class. See the JVMS 4.7.7 section for more details. * - * @param owner internal name of the enclosing class of the class. - * @param name the name of the method that contains the class, or null if the class is - * not enclosed in a method of its enclosing class. - * @param descriptor the descriptor of the method that contains the class, or null if the - * class is not enclosed in a method of its enclosing class. + * @param owner internal name of the enclosing class of the class (see {@link + * Type#getInternalName()}). + * @param name the name of the method that contains the class, or {@literal null} if the class is + * not enclosed in a method or constructor of its enclosing class (e.g. if it is enclosed in + * an instance initializer, static initializer, instance variable initializer, or class + * variable initializer). + * @param descriptor the descriptor of the method that contains the class, or {@literal null} if + * the class is not enclosed in a method or constructor of its enclosing class (e.g. if it is + * enclosed in an instance initializer, static initializer, instance variable initializer, or + * class variable initializer). */ public void visitOuterClass(final String owner, final String name, final String descriptor) { if (cv != null) { @@ -182,8 +203,8 @@ public void visitOuterClass(final String owner, final String name, final String * Visits an annotation of the class. * * @param descriptor the class descriptor of the annotation class. - * @param visible true if the annotation is visible at runtime. - * @return a visitor to visit the annotation values, or null if this visitor is not + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not * interested in visiting this annotation. */ public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { @@ -201,17 +222,17 @@ public AnnotationVisitor visitAnnotation(final String descriptor, final boolean * 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 null if the annotation targets + * static inner type within 'typeRef'. May be {@literal null} if the annotation targets * 'typeRef' as a whole. * @param descriptor the class descriptor of the annotation class. - * @param visible true if the annotation is visible at runtime. - * @return a visitor to visit the annotation values, or null if this visitor is not + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not * interested in visiting this annotation. */ public AnnotationVisitor visitTypeAnnotation( final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { if (api < Opcodes.ASM5) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("TypeAnnotation requires ASM5"); } if (cv != null) { return cv.visitTypeAnnotation(typeRef, typePath, descriptor, visible); @@ -231,38 +252,53 @@ public void visitAttribute(final Attribute attribute) { } /** - * Experimental, use at your own risk. This method will be renamed when it becomes stable, this - * will break existing code using it. Visits a member of the nest. A nest is a set of classes - * of the same package that share access to their private members. One of these classes, called - * the host, lists the other members of the nest, which in turn should link to the host of their - * nest. This method must be called only if the visited class is the host of a nest. A nest host - * is implicitly a member of its own nest, so it's invalid to call this method with the visited - * class name as argument. + * Visits a member of the nest. A nest is a set of classes of the same package that share access + * to their private members. One of these classes, called the host, lists the other members of the + * nest, which in turn should link to the host of their nest. This method must be called only if + * the visited class is the host of a nest. A nest host is implicitly a member of its own nest, so + * it's invalid to call this method with the visited class name as argument. + * + * @param nestMember the internal name of a nest member (see {@link Type#getInternalName()}). + */ + public void visitNestMember(final String nestMember) { + if (api < Opcodes.ASM7) { + throw new UnsupportedOperationException("NestMember requires ASM7"); + } + if (cv != null) { + cv.visitNestMember(nestMember); + } + } + + /** + * Visits a permitted subclasses. A permitted subclass is one of the allowed subclasses of the + * current class. * - * @param nestMember the internal name of a nest member. - * @deprecated This API is experimental. + * @param permittedSubclass the internal name of a permitted subclass (see {@link + * Type#getInternalName()}). */ - @Deprecated - public void visitNestMemberExperimental(final String nestMember) { - if (api < Opcodes.ASM7_EXPERIMENTAL) { - throw new UnsupportedOperationException(); + public void visitPermittedSubclass(final String permittedSubclass) { + if (api < Opcodes.ASM9) { + throw new UnsupportedOperationException("PermittedSubclasses requires ASM9"); } if (cv != null) { - cv.visitNestMemberExperimental(nestMember); + cv.visitPermittedSubclass(permittedSubclass); } } /** * Visits information about an inner class. This inner class is not necessarily a member of the - * class being visited. + * class being visited. More precisely, every class or interface C which is referenced by this + * class and which is not a package member must be visited with this method. This class must + * reference its nested class or interface members, and its enclosing class, if any. See the JVMS + * 4.7.6 section for more details. * - * @param name the internal name of an inner class (see {@link Type#getInternalName()}). - * @param outerName the internal name of the class to which the inner class belongs (see {@link - * Type#getInternalName()}). May be null for not member classes. - * @param innerName the (simple) name of the inner class inside its enclosing class. May be - * null for anonymous inner classes. - * @param access the access flags of the inner class as originally declared in the enclosing - * class. + * @param name the internal name of C (see {@link Type#getInternalName()}). + * @param outerName the internal name of the class or interface C is a member of (see {@link + * Type#getInternalName()}). Must be {@literal null} if C is not the member of a class or + * interface (e.g. for local or anonymous classes). + * @param innerName the (simple) name of C. Must be {@literal null} for anonymous inner classes. + * @param access the access flags of C originally declared in the source code from which this + * class was compiled. */ public void visitInnerClass( final String name, final String outerName, final String innerName, final int access) { @@ -271,6 +307,27 @@ public void visitInnerClass( } } + /** + * Visits a record component of the class. + * + * @param name the record component name. + * @param descriptor the record component descriptor (see {@link Type}). + * @param signature the record component signature. May be {@literal null} if the record component + * type does not use generic types. + * @return a visitor to visit this record component annotations and attributes, or {@literal null} + * if this class visitor is not interested in visiting these annotations and attributes. + */ + public RecordComponentVisitor visitRecordComponent( + final String name, final String descriptor, final String signature) { + if (api < Opcodes.ASM8) { + throw new UnsupportedOperationException("Record requires ASM8"); + } + if (cv != null) { + return cv.visitRecordComponent(name, descriptor, signature); + } + return null; + } + /** * Visits a field of the class. * @@ -278,15 +335,15 @@ public void visitInnerClass( * the field is synthetic and/or deprecated. * @param name the field's name. * @param descriptor the field's descriptor (see {@link Type}). - * @param signature the field's signature. May be null if the field's type does not use + * @param signature the field's signature. May be {@literal null} if the field's type does not use * generic types. - * @param value the field's initial value. This parameter, which may be null if the field - * does not have an initial value, must be an {@link Integer}, a {@link Float}, a {@link - * Long}, a {@link Double} or a {@link String} (for int, float, - * long or String fields respectively). This parameter is only used for - * static fields. Its value is ignored for non static fields, which must be initialized - * through bytecode instructions in constructors or methods. - * @return a visitor to visit field annotations and attributes, or null if this class + * @param value the field's initial value. This parameter, which may be {@literal null} if the + * field does not have an initial value, must be an {@link Integer}, a {@link Float}, a {@link + * Long}, a {@link Double} or a {@link String} (for {@code int}, {@code float}, {@code long} + * or {@code String} fields respectively). This parameter is only used for static + * fields. Its value is ignored for non static fields, which must be initialized through + * bytecode instructions in constructors or methods. + * @return a visitor to visit field annotations and attributes, or {@literal null} if this class * visitor is not interested in visiting these annotations and attributes. */ public FieldVisitor visitField( @@ -303,19 +360,19 @@ public FieldVisitor visitField( /** * Visits a method of the class. This method must return a new {@link MethodVisitor} - * instance (or null) each time it is called, i.e., it should not return a previously + * instance (or {@literal null}) each time it is called, i.e., it should not return a previously * returned visitor. * * @param access the method's access flags (see {@link Opcodes}). This parameter also indicates if * the method is synthetic and/or deprecated. * @param name the method's name. * @param descriptor the method's descriptor (see {@link Type}). - * @param signature the method's signature. May be null if the method parameters, return - * type and exceptions do not use generic types. + * @param signature the method's signature. May be {@literal null} if the method parameters, + * return type and exceptions do not use generic types. * @param exceptions the internal names of the method's exception classes (see {@link - * Type#getInternalName()}). May be null. - * @return an object to visit the byte code of the method, or null if this class visitor - * is not interested in visiting the code of this method. + * Type#getInternalName()}). May be {@literal null}. + * @return an object to visit the byte code of the method, or {@literal null} if this class + * visitor is not interested in visiting the code of this method. */ public MethodVisitor visitMethod( final int access, diff --git a/src/jvm/clojure/asm/ClassWriter.java b/src/jvm/clojure/asm/ClassWriter.java index 4727fe376c..57876a8786 100644 --- a/src/jvm/clojure/asm/ClassWriter.java +++ b/src/jvm/clojure/asm/ClassWriter.java @@ -65,6 +65,12 @@ public class ClassWriter extends ClassVisitor { */ public static final int COMPUTE_FRAMES = 2; + /** + * The flags passed to the constructor. Must be zero or more of {@link #COMPUTE_MAXS} and {@link + * #COMPUTE_FRAMES}. + */ + private final int flags; + // Note: fields are ordered as in the ClassFile structure, and those related to attributes are // ordered as in Section 4.7 of the JVMS. @@ -79,8 +85,8 @@ public class ClassWriter extends ClassVisitor { /** * The access_flags field of the JVMS ClassFile structure. This field can contain ASM specific - * access flags, such as {@link Opcodes#ACC_DEPRECATED}, which are removed when generating the - * ClassFile structure. + * access flags, such as {@link Opcodes#ACC_DEPRECATED} or {@link Opcodes#ACC_RECORD}, which are + * removed when generating the ClassFile structure. */ private int accessFlags; @@ -123,7 +129,7 @@ public class ClassWriter extends ClassVisitor { /** The number_of_classes field of the InnerClasses attribute, or 0. */ private int numberOfInnerClasses; - /** The 'classes' array of the InnerClasses attribute, or null. */ + /** The 'classes' array of the InnerClasses attribute, or {@literal null}. */ private ByteVector innerClasses; /** The class_index field of the EnclosingMethod attribute, or 0. */ @@ -138,34 +144,34 @@ public class ClassWriter extends ClassVisitor { /** The source_file_index field of the SourceFile attribute, or 0. */ private int sourceFileIndex; - /** The debug_extension field of the SourceDebugExtension attribute, or null. */ + /** The debug_extension field of the SourceDebugExtension attribute, or {@literal null}. */ private ByteVector debugExtension; /** * The last runtime visible annotation of this class. The previous ones can be accessed with the - * {@link AnnotationWriter#previousAnnotation} field. May be null. + * {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. */ private AnnotationWriter lastRuntimeVisibleAnnotation; /** * The last runtime invisible annotation of this class. The previous ones can be accessed with the - * {@link AnnotationWriter#previousAnnotation} field. May be null. + * {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. */ private AnnotationWriter lastRuntimeInvisibleAnnotation; /** * The last runtime visible type annotation of this class. The previous ones can be accessed with - * the {@link AnnotationWriter#previousAnnotation} field. May be null. + * the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. */ private AnnotationWriter lastRuntimeVisibleTypeAnnotation; /** * The last runtime invisible type annotation of this class. The previous ones can be accessed - * with the {@link AnnotationWriter#previousAnnotation} field. May be null. + * with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. */ private AnnotationWriter lastRuntimeInvisibleTypeAnnotation; - /** The Module attribute of this class, or null. */ + /** The Module attribute of this class, or {@literal null}. */ private ModuleWriter moduleWriter; /** The host_class_index field of the NestHost attribute, or 0. */ @@ -174,12 +180,32 @@ public class ClassWriter extends ClassVisitor { /** The number_of_classes field of the NestMembers attribute, or 0. */ private int numberOfNestMemberClasses; - /** The 'classes' array of the NestMembers attribute, or null. */ + /** The 'classes' array of the NestMembers attribute, or {@literal null}. */ private ByteVector nestMemberClasses; + /** The number_of_classes field of the PermittedSubclasses attribute, or 0. */ + private int numberOfPermittedSubclasses; + + /** The 'classes' array of the PermittedSubclasses attribute, or {@literal null}. */ + private ByteVector permittedSubclasses; + + /** + * The record components of this class, stored in a linked list of {@link RecordComponentWriter} + * linked via their {@link RecordComponentWriter#delegate} field. This field stores the first + * element of this list. + */ + private RecordComponentWriter firstRecordComponent; + + /** + * The record components of this class, stored in a linked list of {@link RecordComponentWriter} + * linked via their {@link RecordComponentWriter#delegate} field. This field stores the last + * element of this list. + */ + private RecordComponentWriter lastRecordComponent; + /** * The first non standard attribute of this class. The next ones can be accessed with the {@link - * Attribute#nextAttribute} field. May be null. + * Attribute#nextAttribute} field. May be {@literal null}. * *

WARNING: this list stores the attributes in the reverse order of their visit. * firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link @@ -191,6 +217,7 @@ public class ClassWriter extends ClassVisitor { /** * Indicates what must be automatically computed in {@link MethodWriter}. Must be one of {@link * MethodWriter#COMPUTE_NOTHING}, {@link MethodWriter#COMPUTE_MAX_STACK_AND_LOCAL}, {@link + * MethodWriter#COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES}, {@link * MethodWriter#COMPUTE_INSERTED_FRAMES}, or {@link MethodWriter#COMPUTE_ALL_FRAMES}. */ private int compute; @@ -228,23 +255,39 @@ public ClassWriter(final int flags) { * @param classReader the {@link ClassReader} used to read the original class. It will be used to * copy the entire constant pool and bootstrap methods from the original class and also to * copy other fragments of original bytecode where applicable. - * @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}. These option flags do - * not affect methods that are copied as is in the new class. This means that neither the + * @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}. These option flags + * do not affect methods that are copied as is in the new class. This means that neither the * maximum stack size nor the stack frames will be computed for these methods. */ public ClassWriter(final ClassReader classReader, final int flags) { - super(Opcodes.ASM6); + super(/* latest api = */ Opcodes.ASM9); + this.flags = flags; symbolTable = classReader == null ? new SymbolTable(this) : new SymbolTable(this, classReader); if ((flags & COMPUTE_FRAMES) != 0) { - this.compute = MethodWriter.COMPUTE_ALL_FRAMES; + compute = MethodWriter.COMPUTE_ALL_FRAMES; } else if ((flags & COMPUTE_MAXS) != 0) { - this.compute = MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL; + compute = MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL; } else { - this.compute = MethodWriter.COMPUTE_NOTHING; + compute = MethodWriter.COMPUTE_NOTHING; } } + // ----------------------------------------------------------------------------------------------- + // Accessors + // ----------------------------------------------------------------------------------------------- + + /** + * Returns true if all the given flags were passed to the constructor. + * + * @param flags some option flags. Must be zero or more of {@link #COMPUTE_MAXS} and {@link + * #COMPUTE_FRAMES}. + * @return true if all the given flags, or more, were passed to the constructor. + */ + public boolean hasFlags(final int flags) { + return (this.flags & flags) == flags; + } + // ----------------------------------------------------------------------------------------------- // Implementation of the ClassVisitor abstract class // ----------------------------------------------------------------------------------------------- @@ -282,7 +325,7 @@ public final void visitSource(final String file, final String debug) { sourceFileIndex = symbolTable.addConstantUtf8(file); } if (debug != null) { - debugExtension = new ByteVector().encodeUTF8(debug, 0, Integer.MAX_VALUE); + debugExtension = new ByteVector().encodeUtf8(debug, 0, Integer.MAX_VALUE); } } @@ -298,7 +341,7 @@ public final ModuleVisitor visitModule( } @Override - public void visitNestHostExperimental(final String nestHost) { + public final void visitNestHost(final String nestHost) { nestHostClassIndex = symbolTable.addConstantClass(nestHost).index; } @@ -313,37 +356,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); } } @@ -355,7 +387,7 @@ public final void visitAttribute(final Attribute attribute) { } @Override - public void visitNestMemberExperimental(final String nestMember) { + public final void visitNestMember(final String nestMember) { if (nestMemberClasses == null) { nestMemberClasses = new ByteVector(); } @@ -363,6 +395,15 @@ public void visitNestMemberExperimental(final String nestMember) { nestMemberClasses.putShort(symbolTable.addConstantClass(nestMember).index); } + @Override + public final void visitPermittedSubclass(final String permittedSubclass) { + if (permittedSubclasses == null) { + permittedSubclasses = new ByteVector(); + } + ++numberOfPermittedSubclasses; + permittedSubclasses.putShort(symbolTable.addConstantClass(permittedSubclass).index); + } + @Override public final void visitInnerClass( final String name, final String outerName, final String innerName, final int access) { @@ -383,10 +424,22 @@ public final void visitInnerClass( innerClasses.putShort(innerName == null ? 0 : symbolTable.addConstantUtf8(innerName)); innerClasses.putShort(access); nameSymbol.info = numberOfInnerClasses; + } + // Else, compare the inner classes entry nameSymbol.info - 1 with the arguments of this method + // and throw an exception if there is a difference? + } + + @Override + public final RecordComponentVisitor visitRecordComponent( + final String name, final String descriptor, final String signature) { + RecordComponentWriter recordComponentWriter = + new RecordComponentWriter(symbolTable, name, descriptor, signature); + if (firstRecordComponent == null) { + firstRecordComponent = recordComponentWriter; } else { - // Compare the inner classes entry nameSymbol.info - 1 with the arguments of this method and - // throw an exception if there is a difference? + lastRecordComponent.delegate = recordComponentWriter; } + return lastRecordComponent = recordComponentWriter; } @Override @@ -436,6 +489,8 @@ public final void visitEnd() { * Returns the content of the class file that was built by this ClassWriter. * * @return the binary content of the JVMS ClassFile structure that was built by this ClassWriter. + * @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() { // First step: compute the size in bytes of the ClassFile structure. @@ -457,6 +512,7 @@ public byte[] toByteArray() { size += methodWriter.computeMethodInfoSize(); methodWriter = (MethodWriter) methodWriter.mv; } + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. int attributesCount = 0; if (innerClasses != null) { @@ -536,6 +592,24 @@ public byte[] toByteArray() { size += 8 + nestMemberClasses.length; symbolTable.addConstantUtf8(Constants.NEST_MEMBERS); } + if (permittedSubclasses != null) { + ++attributesCount; + size += 8 + permittedSubclasses.length; + symbolTable.addConstantUtf8(Constants.PERMITTED_SUBCLASSES); + } + int recordComponentCount = 0; + int recordSize = 0; + if ((accessFlags & Opcodes.ACC_RECORD) != 0 || firstRecordComponent != null) { + RecordComponentWriter recordComponentWriter = firstRecordComponent; + while (recordComponentWriter != null) { + ++recordComponentCount; + recordSize += recordComponentWriter.computeRecordComponentInfoSize(); + recordComponentWriter = (RecordComponentWriter) recordComponentWriter.delegate; + } + ++attributesCount; + size += 8 + recordSize; + symbolTable.addConstantUtf8(Constants.RECORD); + } if (firstAttribute != null) { attributesCount += firstAttribute.getAttributeCount(); size += firstAttribute.computeAttributesSize(symbolTable); @@ -543,8 +617,9 @@ public byte[] toByteArray() { // IMPORTANT: this must be the last part of the ClassFile size computation, because the previous // statements can add attribute names to the constant pool, thereby changing its size! size += symbolTable.getConstantPoolLength(); - if (symbolTable.getConstantPoolCount() > 0xFFFF) { - throw new IndexOutOfBoundsException("Class file too large!"); + int constantPoolCount = symbolTable.getConstantPoolCount(); + if (constantPoolCount > 0xFFFF) { + throw new ClassTooLargeException(symbolTable.getClassName(), constantPoolCount); } // Second step: allocate a ByteVector of the correct size (in order to avoid any array copy in @@ -615,22 +690,13 @@ public byte[] toByteArray() { 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); @@ -648,6 +714,24 @@ public byte[] toByteArray() { .putShort(numberOfNestMemberClasses) .putByteArray(nestMemberClasses.data, 0, nestMemberClasses.length); } + if (permittedSubclasses != null) { + result + .putShort(symbolTable.addConstantUtf8(Constants.PERMITTED_SUBCLASSES)) + .putInt(permittedSubclasses.length + 2) + .putShort(numberOfPermittedSubclasses) + .putByteArray(permittedSubclasses.data, 0, permittedSubclasses.length); + } + if ((accessFlags & Opcodes.ACC_RECORD) != 0 || firstRecordComponent != null) { + result + .putShort(symbolTable.addConstantUtf8(Constants.RECORD)) + .putInt(recordSize + 2) + .putShort(recordComponentCount); + RecordComponentWriter recordComponentWriter = firstRecordComponent; + while (recordComponentWriter != null) { + recordComponentWriter.putRecordComponentInfo(result); + recordComponentWriter = (RecordComponentWriter) recordComponentWriter.delegate; + } + } if (firstAttribute != null) { firstAttribute.putAttributes(symbolTable, result); } @@ -671,7 +755,7 @@ public byte[] toByteArray() { * ones. */ private byte[] replaceAsmInstructions(final byte[] classFile, final boolean hasFrames) { - Attribute[] attributes = getAttributePrototypes(); + final Attribute[] attributes = getAttributePrototypes(); firstField = null; lastField = null; firstMethod = null; @@ -684,9 +768,13 @@ private byte[] replaceAsmInstructions(final byte[] classFile, final boolean hasF nestHostClassIndex = 0; numberOfNestMemberClasses = 0; nestMemberClasses = null; + numberOfPermittedSubclasses = 0; + permittedSubclasses = null; + firstRecordComponent = null; + lastRecordComponent = null; firstAttribute = null; compute = hasFrames ? MethodWriter.COMPUTE_INSERTED_FRAMES : MethodWriter.COMPUTE_NOTHING; - new ClassReader(classFile, 0, /* checkClassVersion = */ false) + new ClassReader(classFile, 0, /* checkClassVersion= */ false) .accept( this, attributes, @@ -712,6 +800,11 @@ private Attribute[] getAttributePrototypes() { methodWriter.collectAttributePrototypes(attributePrototypes); methodWriter = (MethodWriter) methodWriter.mv; } + RecordComponentWriter recordComponentWriter = firstRecordComponent; + while (recordComponentWriter != null) { + recordComponentWriter.collectAttributePrototypes(attributePrototypes); + recordComponentWriter = (RecordComponentWriter) recordComponentWriter.delegate; + } return attributePrototypes.toArray(); } @@ -740,6 +833,7 @@ public int newConst(final Object value) { * @param value the String value. * @return the index of a new or already existing UTF8 item. */ + // DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility). public int newUTF8(final String value) { return symbolTable.addConstantUtf8(value); } @@ -749,7 +843,7 @@ public int newUTF8(final String value) { * constant pool already contains a similar item. This method is intended for {@link Attribute} * sub classes, and is normally not needed by class generators or adapters. * - * @param value the internal name of the class. + * @param value the internal name of the class (see {@link Type#getInternalName()}). * @return the index of a new or already existing class reference item. */ public int newClass(final String value) { @@ -801,7 +895,8 @@ public int newPackage(final String packageName) { * 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 owner the internal name of the field or method owner class. + * @param owner the internal name of the field or method owner class (see {@link + * Type#getInternalName()}). * @param name the name of the field or method. * @param descriptor the descriptor of the field or method. * @return the index of a new or already existing method type reference item. @@ -823,7 +918,8 @@ public int newHandle( * 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 owner the internal name of the field or method owner class. + * @param owner the internal name of the field or method owner class (see {@link + * Type#getInternalName()}). * @param name the name of the field or method. * @param descriptor the descriptor of the field or method. * @param isInterface true if the owner is an interface. @@ -885,7 +981,7 @@ public int newInvokeDynamic( * constant pool already contains a similar item. This method is intended for {@link Attribute} * sub classes, and is normally not needed by class generators or adapters. * - * @param owner the internal name of the field's owner class. + * @param owner the internal name of the field's owner class (see {@link Type#getInternalName()}). * @param name the field's name. * @param descriptor the field's descriptor. * @return the index of a new or already existing field reference item. @@ -899,10 +995,11 @@ public int newField(final String owner, final String name, final String descript * constant pool already contains a similar item. This method is intended for {@link Attribute} * sub classes, and is normally not needed by class generators or adapters. * - * @param owner the internal name of the method's owner class. + * @param owner the internal name of the method's owner class (see {@link + * Type#getInternalName()}). * @param name the method's name. * @param descriptor the method's descriptor. - * @param isInterface true if owner is an interface. + * @param isInterface {@literal true} if {@code owner} is an interface. * @return the index of a new or already existing method reference item. */ public int newMethod( @@ -935,22 +1032,23 @@ public int newNameType(final String name, final String descriptor) { * currently being generated by this ClassWriter, which can of course not be loaded since it is * under construction. * - * @param type1 the internal name of a class. - * @param type2 the internal name of another class. - * @return the internal name of the common super class of the two given classes. + * @param type1 the internal name of a class (see {@link Type#getInternalName()}). + * @param type2 the internal name of another class (see {@link Type#getInternalName()}). + * @return the internal name of the common super class of the two given classes (see {@link + * Type#getInternalName()}). */ protected String getCommonSuperClass(final String type1, final String type2) { - ClassLoader classLoader = getClass().getClassLoader(); + ClassLoader classLoader = getClassLoader(); Class class1; try { class1 = Class.forName(type1.replace('/', '.'), false, classLoader); - } catch (Exception e) { + } catch (ClassNotFoundException e) { throw new TypeNotPresentException(type1, e); } Class class2; try { class2 = Class.forName(type2.replace('/', '.'), false, classLoader); - } catch (Exception e) { + } catch (ClassNotFoundException e) { throw new TypeNotPresentException(type2, e); } if (class1.isAssignableFrom(class2)) { @@ -968,4 +1066,15 @@ protected String getCommonSuperClass(final String type1, final String type2) { return class1.getName().replace('.', '/'); } } + + /** + * Returns the {@link ClassLoader} to be used by the default implementation of {@link + * #getCommonSuperClass(String, String)}, that of this {@link ClassWriter}'s runtime type by + * default. + * + * @return ClassLoader + */ + protected ClassLoader getClassLoader() { + return getClass().getClassLoader(); + } } diff --git a/src/jvm/clojure/asm/ConstantDynamic.java b/src/jvm/clojure/asm/ConstantDynamic.java index d5306caf8a..1b5346a927 100644 --- a/src/jvm/clojure/asm/ConstantDynamic.java +++ b/src/jvm/clojure/asm/ConstantDynamic.java @@ -33,9 +33,7 @@ * A constant whose value is computed at runtime, with a bootstrap method. * * @author Remi Forax - * @deprecated This API is experimental. */ -@Deprecated public final class ConstantDynamic { /** The constant name (can be arbitrary). */ @@ -101,16 +99,49 @@ public Handle getBootstrapMethod() { } /** - * Returns the arguments to pass to the bootstrap method, in order to compute the value of this + * Returns the number of arguments passed to the bootstrap method, in order to compute the value + * of this constant. + * + * @return the number of arguments passed to the bootstrap method, in order to compute the value + * of this constant. + */ + public int getBootstrapMethodArgumentCount() { + return bootstrapMethodArguments.length; + } + + /** + * Returns an argument passed to the bootstrap method, in order to compute the value of this * constant. * + * @param index an argument index, between 0 and {@link #getBootstrapMethodArgumentCount()} + * (exclusive). + * @return the argument passed to the bootstrap method, with the given index. + */ + public Object getBootstrapMethodArgument(final int index) { + return bootstrapMethodArguments[index]; + } + + /** + * Returns the arguments to pass to the bootstrap method, in order to compute the value of this + * constant. WARNING: this array must not be modified, and must not be returned to the user. + * * @return the arguments to pass to the bootstrap method, in order to compute the value of this * constant. */ - public Object[] getBootstrapMethodArguments() { + Object[] getBootstrapMethodArgumentsUnsafe() { return bootstrapMethodArguments; } + /** + * Returns the size of this constant. + * + * @return the size of this constant, i.e., 2 for {@code long} and {@code double}, 1 otherwise. + */ + public int getSize() { + char firstCharOfDescriptor = descriptor.charAt(0); + return (firstCharOfDescriptor == 'J' || firstCharOfDescriptor == 'D') ? 2 : 1; + } + @Override public boolean equals(final Object object) { if (object == this) { diff --git a/src/jvm/clojure/asm/Constants.java b/src/jvm/clojure/asm/Constants.java index 38c5b87513..62c139cea2 100644 --- a/src/jvm/clojure/asm/Constants.java +++ b/src/jvm/clojure/asm/Constants.java @@ -27,6 +27,11 @@ // THE POSSIBILITY OF SUCH DAMAGE. package clojure.asm; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.regex.Pattern; + /** * Defines additional JVM opcodes, access flags and constants which are not part of the ASM public * API. @@ -34,9 +39,7 @@ * @see JVMS 6 * @author Eric Bruneton */ -final class Constants implements Opcodes { - - private Constants() {} +final class Constants { // The ClassFile attribute names, in the order they are defined in // https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.7-300. @@ -70,6 +73,8 @@ private Constants() {} static final String MODULE_MAIN_CLASS = "ModuleMainClass"; static final String NEST_HOST = "NestHost"; static final String NEST_MEMBERS = "NestMembers"; + static final String PERMITTED_SUBCLASSES = "PermittedSubclasses"; + static final String RECORD = "Record"; // ASM specific access flags. // WARNING: the 16 least significant bits must NOT be used, to avoid conflicts with standard @@ -142,7 +147,7 @@ private Constants() {} // Constants to convert between normal and wide jump instructions. // The delta between the GOTO_W and JSR_W opcodes and GOTO and JUMP. - static final int WIDE_JUMP_OPCODE_DELTA = GOTO_W - GOTO; + static final int WIDE_JUMP_OPCODE_DELTA = GOTO_W - Opcodes.GOTO; // Constants to convert JVM opcodes to the equivalent ASM specific opcodes, and vice versa. @@ -155,23 +160,62 @@ private Constants() {} // ASM specific opcodes, used for long forward jump instructions. - static final int ASM_IFEQ = IFEQ + ASM_OPCODE_DELTA; - static final int ASM_IFNE = IFNE + ASM_OPCODE_DELTA; - static final int ASM_IFLT = IFLT + ASM_OPCODE_DELTA; - static final int ASM_IFGE = IFGE + ASM_OPCODE_DELTA; - static final int ASM_IFGT = IFGT + ASM_OPCODE_DELTA; - static final int ASM_IFLE = IFLE + ASM_OPCODE_DELTA; - static final int ASM_IF_ICMPEQ = IF_ICMPEQ + ASM_OPCODE_DELTA; - static final int ASM_IF_ICMPNE = IF_ICMPNE + ASM_OPCODE_DELTA; - static final int ASM_IF_ICMPLT = IF_ICMPLT + ASM_OPCODE_DELTA; - static final int ASM_IF_ICMPGE = IF_ICMPGE + ASM_OPCODE_DELTA; - static final int ASM_IF_ICMPGT = IF_ICMPGT + ASM_OPCODE_DELTA; - static final int ASM_IF_ICMPLE = IF_ICMPLE + ASM_OPCODE_DELTA; - static final int ASM_IF_ACMPEQ = IF_ACMPEQ + ASM_OPCODE_DELTA; - static final int ASM_IF_ACMPNE = IF_ACMPNE + ASM_OPCODE_DELTA; - static final int ASM_GOTO = GOTO + ASM_OPCODE_DELTA; - static final int ASM_JSR = JSR + ASM_OPCODE_DELTA; - static final int ASM_IFNULL = IFNULL + ASM_IFNULL_OPCODE_DELTA; - static final int ASM_IFNONNULL = IFNONNULL + ASM_IFNULL_OPCODE_DELTA; + static final int ASM_IFEQ = Opcodes.IFEQ + ASM_OPCODE_DELTA; + static final int ASM_IFNE = Opcodes.IFNE + ASM_OPCODE_DELTA; + static final int ASM_IFLT = Opcodes.IFLT + ASM_OPCODE_DELTA; + static final int ASM_IFGE = Opcodes.IFGE + ASM_OPCODE_DELTA; + static final int ASM_IFGT = Opcodes.IFGT + ASM_OPCODE_DELTA; + static final int ASM_IFLE = Opcodes.IFLE + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPEQ = Opcodes.IF_ICMPEQ + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPNE = Opcodes.IF_ICMPNE + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPLT = Opcodes.IF_ICMPLT + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPGE = Opcodes.IF_ICMPGE + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPGT = Opcodes.IF_ICMPGT + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPLE = Opcodes.IF_ICMPLE + ASM_OPCODE_DELTA; + static final int ASM_IF_ACMPEQ = Opcodes.IF_ACMPEQ + ASM_OPCODE_DELTA; + static final int ASM_IF_ACMPNE = Opcodes.IF_ACMPNE + ASM_OPCODE_DELTA; + static final int ASM_GOTO = Opcodes.GOTO + ASM_OPCODE_DELTA; + static final int ASM_JSR = Opcodes.JSR + ASM_OPCODE_DELTA; + static final int ASM_IFNULL = Opcodes.IFNULL + ASM_IFNULL_OPCODE_DELTA; + static final int ASM_IFNONNULL = Opcodes.IFNONNULL + ASM_IFNULL_OPCODE_DELTA; static final int ASM_GOTO_W = 220; + + private Constants() {} + + static void checkAsmExperimental(final Object caller) { + Class callerClass = caller.getClass(); + String internalName = callerClass.getName().replace('.', '/'); + if (!isWhitelisted(internalName)) { + checkIsPreview(callerClass.getClassLoader().getResourceAsStream(internalName + ".class")); + } + } + + static boolean isWhitelisted(final String internalName) { + if (!internalName.startsWith("clojure.asm/")) { + return false; + } + String member = "(Annotation|Class|Field|Method|Module|RecordComponent|Signature)"; + return internalName.contains("Test$") + || Pattern.matches( + "clojure.asm/util/Trace" + member + "Visitor(\\$.*)?", internalName) + || Pattern.matches( + "clojure.asm/util/Check" + member + "Adapter(\\$.*)?", internalName); + } + + static void checkIsPreview(final InputStream classInputStream) { + if (classInputStream == null) { + throw new IllegalStateException("Bytecode not available, can't check class version"); + } + int minorVersion; + try (DataInputStream callerClassStream = new DataInputStream(classInputStream); ) { + callerClassStream.readInt(); + minorVersion = callerClassStream.readUnsignedShort(); + } catch (IOException ioe) { + throw new IllegalStateException("I/O error, can't check class version", ioe); + } + if (minorVersion != 0xFFFF) { + throw new IllegalStateException( + "ASM9_EXPERIMENTAL can only be used by classes compiled with --enable-preview"); + } + } } diff --git a/src/jvm/clojure/asm/FieldVisitor.java b/src/jvm/clojure/asm/FieldVisitor.java index f2f0e2ce8d..874f2a22ff 100644 --- a/src/jvm/clojure/asm/FieldVisitor.java +++ b/src/jvm/clojure/asm/FieldVisitor.java @@ -29,59 +29,72 @@ /** * A visitor to visit a Java field. The methods of this class must be called in the following order: - * ( visitAnnotation | visitTypeAnnotation | visitAttribute )* - * visitEnd. + * ( {@code visitAnnotation} | {@code visitTypeAnnotation} | {@code visitAttribute} )* {@code + * visitEnd}. * * @author Eric Bruneton */ public abstract class FieldVisitor { /** - * The ASM API version implemented by this visitor. The value of this field must be one of {@link - * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7_EXPERIMENTAL}. + * The ASM API version implemented by this visitor. The value of this field must be one of the + * {@code ASM}x values in {@link Opcodes}. */ 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; /** * Constructs a new {@link FieldVisitor}. * - * @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_EXPERIMENTAL}. + * @param api the ASM API version implemented by this visitor. Must be one of the {@code + * ASM}x values in {@link Opcodes}. */ - public FieldVisitor(final int api) { + protected FieldVisitor(final int api) { this(api, null); } /** * Constructs a new {@link FieldVisitor}. * - * @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_EXPERIMENTAL}. + * @param api the ASM API version implemented by this visitor. Must be one of the {@code + * ASM}x values in {@link Opcodes}. * @param fieldVisitor the field visitor to which this visitor must delegate method calls. May be * null. */ - public FieldVisitor(final int api, final FieldVisitor fieldVisitor) { - if (api != Opcodes.ASM6 + protected FieldVisitor(final int api, final FieldVisitor fieldVisitor) { + if (api != Opcodes.ASM9 + && api != Opcodes.ASM8 + && api != Opcodes.ASM7 + && api != Opcodes.ASM6 && api != Opcodes.ASM5 && api != Opcodes.ASM4 - && api != Opcodes.ASM7_EXPERIMENTAL) { - throw new IllegalArgumentException(); + && api != Opcodes.ASM10_EXPERIMENTAL) { + throw new IllegalArgumentException("Unsupported api " + api); + } + if (api == Opcodes.ASM10_EXPERIMENTAL) { + Constants.checkAsmExperimental(this); } this.api = api; this.fv = fieldVisitor; } + /** + * The field visitor to which this visitor must delegate method calls. May be {@literal null}. + * + * @return the field visitor to which this visitor must delegate method calls, or {@literal null}. + */ + public FieldVisitor getDelegate() { + return fv; + } + /** * Visits an annotation of the field. * * @param descriptor the class descriptor of the annotation class. - * @param visible true if the annotation is visible at runtime. - * @return a visitor to visit the annotation values, or null if this visitor is not + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not * interested in visiting this annotation. */ public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { @@ -97,17 +110,17 @@ public AnnotationVisitor visitAnnotation(final String descriptor, final boolean * @param typeRef a reference to the annotated type. The sort of this type reference must be * {@link TypeReference#FIELD}. 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 null if the annotation targets + * static inner type within 'typeRef'. May be {@literal null} if the annotation targets * 'typeRef' as a whole. * @param descriptor the class descriptor of the annotation class. - * @param visible true if the annotation is visible at runtime. - * @return a visitor to visit the annotation values, or null if this visitor is not + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not * interested in visiting this annotation. */ public AnnotationVisitor visitTypeAnnotation( final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { if (api < Opcodes.ASM5) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("This feature requires ASM5"); } if (fv != null) { return fv.visitTypeAnnotation(typeRef, typePath, descriptor, visible); diff --git a/src/jvm/clojure/asm/FieldWriter.java b/src/jvm/clojure/asm/FieldWriter.java index ed6449bf69..1825fb7d17 100644 --- a/src/jvm/clojure/asm/FieldWriter.java +++ b/src/jvm/clojure/asm/FieldWriter.java @@ -70,31 +70,31 @@ final class FieldWriter extends FieldVisitor { /** * The last runtime visible annotation of this field. The previous ones can be accessed with the - * {@link AnnotationWriter#previousAnnotation} field. May be null. + * {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. */ private AnnotationWriter lastRuntimeVisibleAnnotation; /** * The last runtime invisible annotation of this field. The previous ones can be accessed with the - * {@link AnnotationWriter#previousAnnotation} field. May be null. + * {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. */ private AnnotationWriter lastRuntimeInvisibleAnnotation; /** * The last runtime visible type annotation of this field. The previous ones can be accessed with - * the {@link AnnotationWriter#previousAnnotation} field. May be null. + * the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. */ private AnnotationWriter lastRuntimeVisibleTypeAnnotation; /** * The last runtime invisible type annotation of this field. The previous ones can be accessed - * with the {@link AnnotationWriter#previousAnnotation} field. May be null. + * with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. */ private AnnotationWriter lastRuntimeInvisibleTypeAnnotation; /** * The first non standard attribute of this field. The next ones can be accessed with the {@link - * Attribute#nextAttribute} field. May be null. + * Attribute#nextAttribute} field. May be {@literal null}. * *

WARNING: this list stores the attributes in the reverse order of their visit. * firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link @@ -114,8 +114,8 @@ final class FieldWriter extends FieldVisitor { * @param access the field's access flags (see {@link Opcodes}). * @param name the field's name. * @param descriptor the field's descriptor (see {@link Type}). - * @param signature the field's signature. May be null. - * @param constantValue the field's constant value. May be null. + * @param signature the field's signature. May be {@literal null}. + * @param constantValue the field's constant value. May be {@literal null}. */ FieldWriter( final SymbolTable symbolTable, @@ -124,7 +124,7 @@ final class FieldWriter extends FieldVisitor { final String descriptor, final String signature, final Object constantValue) { - super(Opcodes.ASM6); + super(/* latest api = */ Opcodes.ASM9); this.symbolTable = symbolTable; this.accessFlags = access; this.nameIndex = symbolTable.addConstantUtf8(name); @@ -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/src/jvm/clojure/asm/Frame.java b/src/jvm/clojure/asm/Frame.java index 2c67f8cce2..3225dd790c 100644 --- a/src/jvm/clojure/asm/Frame.java +++ b/src/jvm/clojure/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 + * #CONSTANT_KIND}, {@link #REFERENCE_KIND}, {@link #UNINITIALIZED_KIND}, {@link + * #FORWARD_UNINITIALIZED_KIND},{@link #LOCAL_KIND} or {@link #STACK_KIND}. + *
  • 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 *
      @@ -78,7 +78,10 @@ *
    • the index of a {@link Symbol#TYPE_TAG} {@link Symbol} in the type table of a {@link * SymbolTable}, if KIND is equal to {@link #REFERENCE_KIND}. *
    • the index of an {@link Symbol#UNINITIALIZED_TYPE_TAG} {@link Symbol} in the type - * table of a SymbolTable, if KIND is equal to {@link #UNINITIALIZED_KIND}. + * table of a {@link SymbolTable}, if KIND is equal to {@link #UNINITIALIZED_KIND}. + *
    • the index of a {@link Symbol#FORWARD_UNINITIALIZED_TYPE_TAG} {@link Symbol} in the + * type table of a {@link SymbolTable}, if KIND is equal to {@link + * #FORWARD_UNINITIALIZED_KIND}. *
    • the index of a local variable in the input stack frame, if KIND is equal to {@link * #LOCAL_KIND}. *
    • a position relatively to the top of the stack of the input stack frame, if KIND is @@ -88,10 +91,10 @@ * *

      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). + * abstract type value). Input frames can only contain CONSTANT_KIND, REFERENCE_KIND, + * UNINITIALIZED_KIND or FORWARD_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 +132,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 +159,12 @@ 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 FORWARD_UNINITIALIZED_KIND = 4 << KIND_SHIFT; + private static final int LOCAL_KIND = 5 << KIND_SHIFT; + private static final int STACK_KIND = 6 << KIND_SHIFT; // Possible flags for the FLAGS field of an abstract type. @@ -162,7 +173,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). @@ -213,16 +224,48 @@ class Frame { /** * The abstract types that are initialized in the basic block. A constructor invocation on an - * UNINITIALIZED or UNINITIALIZED_THIS abstract type must replace every occurrence of this - * type in the local variables and in the operand stack. This cannot be done during the first step - * of the algorithm since, during this step, the local variables and the operand stack types are - * still abstract. It is therefore necessary to store the abstract types of the constructors which - * are invoked in the basic block, in order to do this replacement during the second step of the - * algorithm, where the frames are fully computed. Note that this array can contain abstract types - * that are relative to the input locals or to the input stack. + * UNINITIALIZED, FORWARD_UNINITIALIZED or UNINITIALIZED_THIS abstract type must replace every + * occurrence of this type in the local variables and in the operand stack. This cannot be + * done during the first step of the algorithm since, during this step, the local variables and + * the operand stack types are still abstract. It is therefore necessary to store the abstract + * types of the constructors which are invoked in the basic block, in order to do this replacement + * during the second step of the algorithm, where the frames are fully computed. Note that this + * array can contain abstract types that are relative to the input locals or to the input stack. */ private int[] initializations; + // ----------------------------------------------------------------------------------------------- + // Constructor + // ----------------------------------------------------------------------------------------------- + + /** + * Constructs a new Frame. + * + * @param owner the basic block to which these input and output stack map frames correspond. + */ + Frame(final Label owner) { + this.owner = owner; + } + + /** + * Sets this frame to the value of the given frame. + * + *

      WARNING: after this method is called the two frames share the same data structures. It is + * recommended to discard the given frame to avoid unexpected side effects. + * + * @param frame The new frame value. + */ + final void copyFrom(final Frame frame) { + inputLocals = frame.inputLocals; + inputStack = frame.inputStack; + outputStackStart = 0; + outputLocals = frame.outputLocals; + outputStack = frame.outputStack; + outputStackTop = frame.outputStackTop; + initializationCount = frame.initializationCount; + initializations = frame.initializations; + } + // ----------------------------------------------------------------------------------------------- // Static methods to get abstract types from other type formats // ----------------------------------------------------------------------------------------------- @@ -245,8 +288,12 @@ static int getAbstractTypeFromApiFormat(final SymbolTable symbolTable, final Obj String descriptor = Type.getObjectType((String) type).getDescriptor(); return getAbstractTypeFromDescriptor(symbolTable, descriptor, 0); } else { - return UNINITIALIZED_KIND - | symbolTable.addUninitializedType("", ((Label) type).bytecodeOffset); + Label label = (Label) type; + if ((label.flags & Label.FLAG_RESOLVED) != 0) { + return UNINITIALIZED_KIND | symbolTable.addUninitializedType("", label.bytecodeOffset); + } else { + return FORWARD_UNINITIALIZED_KIND | symbolTable.addForwardUninitializedType("", label); + } } } @@ -328,46 +375,15 @@ private static int getAbstractTypeFromDescriptor( typeValue = REFERENCE_KIND | symbolTable.addType(internalName); break; default: - throw new IllegalArgumentException(); + throw new IllegalArgumentException( + "Invalid descriptor fragment: " + buffer.substring(elementDescriptorOffset)); } return ((elementDescriptorOffset - offset) << DIM_SHIFT) | typeValue; default: - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Invalid descriptor: " + buffer.substring(offset)); } } - // ----------------------------------------------------------------------------------------------- - // Constructor - // ----------------------------------------------------------------------------------------------- - - /** - * Constructs a new Frame. - * - * @param owner the basic block to which these input and output stack map frames correspond. - */ - Frame(final Label owner) { - this.owner = owner; - } - - /** - * Sets this frame to the value of the given frame. - * - *

      WARNING: after this method is called the two frames share the same data structures. It is - * recommended to discard the given frame to avoid unexpected side effects. - * - * @param frame The new frame value. - */ - final void copyFrom(final Frame frame) { - inputLocals = frame.inputLocals; - inputStack = frame.inputStack; - outputStackStart = 0; - outputLocals = frame.outputLocals; - outputStack = frame.outputStack; - outputStackTop = frame.outputStackTop; - initializationCount = frame.initializationCount; - initializations = frame.initializations; - } - // ----------------------------------------------------------------------------------------------- // Methods related to the input frame // ----------------------------------------------------------------------------------------------- @@ -415,21 +431,21 @@ final void setInputFrameFromDescriptor( * Sets the input frame from the given public API frame description. * * @param symbolTable the type table to use to lookup and store type {@link Symbol}. - * @param nLocal the number of local variables. + * @param numLocal the number of local variables. * @param local the local variable types, described using the same format as in {@link * MethodVisitor#visitFrame}. - * @param nStack the number of operand stack elements. + * @param numStack the number of operand stack elements. * @param stack the operand stack types, described using the same format as in {@link * MethodVisitor#visitFrame}. */ final void setInputFrameFromApiFormat( final SymbolTable symbolTable, - final int nLocal, + final int numLocal, final Object[] local, - final int nStack, + final int numStack, final Object[] stack) { int inputLocalIndex = 0; - for (int i = 0; i < nLocal; ++i) { + for (int i = 0; i < numLocal; ++i) { inputLocals[inputLocalIndex++] = getAbstractTypeFromApiFormat(symbolTable, local[i]); if (local[i] == Opcodes.LONG || local[i] == Opcodes.DOUBLE) { inputLocals[inputLocalIndex++] = TOP; @@ -438,15 +454,15 @@ final void setInputFrameFromApiFormat( while (inputLocalIndex < inputLocals.length) { inputLocals[inputLocalIndex++] = TOP; } - int nStackTop = 0; - for (int i = 0; i < nStack; ++i) { + int numStackTop = 0; + for (int i = 0; i < numStack; ++i) { if (stack[i] == Opcodes.LONG || stack[i] == Opcodes.DOUBLE) { - ++nStackTop; + ++numStackTop; } } - inputStack = new int[nStack + nStackTop]; + inputStack = new int[numStack + numStackTop]; int inputStackIndex = 0; - for (int i = 0; i < nStack; ++i) { + for (int i = 0; i < numStack; ++i) { inputStack[inputStackIndex++] = getAbstractTypeFromApiFormat(symbolTable, stack[i]); if (stack[i] == Opcodes.LONG || stack[i] == Opcodes.DOUBLE) { inputStack[inputStackIndex++] = TOP; @@ -540,7 +556,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); @@ -628,12 +645,14 @@ private void addInitializedType(final int abstractType) { * @param symbolTable the type table to use to lookup and store type {@link Symbol}. * @param abstractType an abstract type. * @return the REFERENCE_KIND abstract type corresponding to abstractType if it is - * UNINITIALIZED_THIS or an UNINITIALIZED_KIND abstract type for one of the types on which a - * constructor is invoked in the basic block. Otherwise returns abstractType. + * UNINITIALIZED_THIS or an UNINITIALIZED_KIND or FORWARD_UNINITIALIZED_KIND abstract type for + * one of the types on which a constructor is invoked in the basic block. Otherwise returns + * abstractType. */ private int getInitializedType(final SymbolTable symbolTable, final int abstractType) { if (abstractType == UNINITIALIZED_THIS - || (abstractType & (DIM_MASK | KIND_MASK)) == UNINITIALIZED_KIND) { + || (abstractType & (DIM_MASK | KIND_MASK)) == UNINITIALIZED_KIND + || (abstractType & (DIM_MASK | KIND_MASK)) == FORWARD_UNINITIALIZED_KIND) { for (int i = 0; i < initializationCount; ++i) { int initializedType = initializations[i]; int dim = initializedType & DIM_MASK; @@ -1103,17 +1122,53 @@ 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 true if the given frame has been changed by this operation (the - * input and output frames of this {@link Frame} are never changed). + * {@link Frame}. Returns {@literal true} if the given frame has been changed by this operation + * (the input and output frames of this {@link Frame} are never changed). * * @param symbolTable the type table to use to lookup and store type {@link Symbol}. * @param dstFrame the {@link Frame} whose input frame must be updated. This should be the frame * of a successor, in the control flow graph, of the basic block corresponding to this frame. * @param catchTypeIndex if 'frame' corresponds to an exception handler basic block, the type * table index of the caught exception type, otherwise 0. - * @return true if the input frame of 'frame' has been changed by this operation. + * @return {@literal true} if the input frame of 'frame' has been changed by this operation. */ final boolean merge( final SymbolTable symbolTable, final Frame dstFrame, final int catchTypeIndex) { @@ -1122,13 +1177,13 @@ final boolean merge( // Compute the concrete types of the local variables at the end of the basic block corresponding // to this frame, by resolving its abstract output types, and merge these concrete types with // those of the local variables in the input frame of dstFrame. - int nLocal = inputLocals.length; - int nStack = inputStack.length; + int numLocal = inputLocals.length; + int numStack = inputStack.length; if (dstFrame.inputLocals == null) { - dstFrame.inputLocals = new int[nLocal]; + dstFrame.inputLocals = new int[numLocal]; frameChanged = true; } - for (int i = 0; i < nLocal; ++i) { + for (int i = 0; i < numLocal; ++i) { int concreteOutputType; if (outputLocals != null && i < outputLocals.length) { int abstractOutputType = outputLocals[i]; @@ -1137,29 +1192,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[nStack - (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 @@ -1181,7 +1214,7 @@ final boolean merge( // frame (and the input stack of dstFrame should be compatible, i.e. merged, with a one // element stack containing the caught exception type). if (catchTypeIndex > 0) { - for (int i = 0; i < nLocal; ++i) { + for (int i = 0; i < numLocal; ++i) { frameChanged |= merge(symbolTable, inputLocals[i], dstFrame.inputLocals, i); } if (dstFrame.inputStack == null) { @@ -1195,15 +1228,15 @@ final boolean merge( // Compute the concrete types of the stack operands at the end of the basic block corresponding // to this frame, by resolving its abstract output types, and merge these concrete types with // those of the stack operands in the input frame of dstFrame. - int nInputStack = inputStack.length + outputStackStart; + int numInputStack = inputStack.length + outputStackStart; if (dstFrame.inputStack == null) { - dstFrame.inputStack = new int[nInputStack + outputStackTop]; + dstFrame.inputStack = new int[numInputStack + outputStackTop]; frameChanged = true; } // First, do this for the stack operands that have not been popped in the basic block // corresponding to this frame, and which are therefore equal to their value in the input // frame (except for uninitialized types, which may have been initialized). - for (int i = 0; i < nInputStack; ++i) { + for (int i = 0; i < numInputStack; ++i) { int concreteOutputType = inputStack[i]; if (initializations != null) { concreteOutputType = getInitializedType(symbolTable, concreteOutputType); @@ -1213,46 +1246,31 @@ 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[nStack - (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); } - frameChanged |= merge(symbolTable, concreteOutputType, dstFrame.inputStack, nInputStack + i); + frameChanged |= + merge(symbolTable, concreteOutputType, dstFrame.inputStack, numInputStack + i); } return frameChanged; } /** * Merges the type at the given index in the given abstract type array with the given type. - * Returns true if the type array has been modified by this operation. + * Returns {@literal true} if the type array has been modified by this operation. * * @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. + * This type should be of {@link #CONSTANT_KIND}, {@link #REFERENCE_KIND}, {@link + * #UNINITIALIZED_KIND} or {@link #FORWARD_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}, {@link #UNINITIALIZED_KIND} or {@link #FORWARD_UNINITIALIZED_KIND} + * kind, with positive or {@literal null} array dimensions. * @param dstIndex the index of the type that must be merged in dstTypes. - * @return true if the type array has been modified by this operation. + * @return {@literal true} if the type array has been modified by this operation. */ private static boolean merge( final SymbolTable symbolTable, @@ -1348,38 +1366,38 @@ final void accept(final MethodWriter methodWriter) { // Compute the number of locals, ignoring TOP types that are just after a LONG or a DOUBLE, and // all trailing TOP types. int[] localTypes = inputLocals; - int nLocal = 0; - int nTrailingTop = 0; + int numLocal = 0; + int numTrailingTop = 0; int i = 0; while (i < localTypes.length) { int localType = localTypes[i]; i += (localType == LONG || localType == DOUBLE) ? 2 : 1; if (localType == TOP) { - nTrailingTop++; + numTrailingTop++; } else { - nLocal += nTrailingTop + 1; - nTrailingTop = 0; + numLocal += numTrailingTop + 1; + numTrailingTop = 0; } } // Compute the stack size, ignoring TOP types that are just after a LONG or a DOUBLE. int[] stackTypes = inputStack; - int nStack = 0; + int numStack = 0; i = 0; while (i < stackTypes.length) { int stackType = stackTypes[i]; i += (stackType == LONG || stackType == DOUBLE) ? 2 : 1; - nStack++; + numStack++; } // Visit the frame and its content. - int frameIndex = methodWriter.visitFrameStart(owner.bytecodeOffset, nLocal, nStack); + int frameIndex = methodWriter.visitFrameStart(owner.bytecodeOffset, numLocal, numStack); i = 0; - while (nLocal-- > 0) { + while (numLocal-- > 0) { int localType = localTypes[i]; i += (localType == LONG || localType == DOUBLE) ? 2 : 1; methodWriter.visitAbstractType(frameIndex++, localType); } i = 0; - while (nStack-- > 0) { + while (numStack-- > 0) { int stackType = stackTypes[i]; i += (stackType == LONG || stackType == DOUBLE) ? 2 : 1; methodWriter.visitAbstractType(frameIndex++, stackType); @@ -1393,7 +1411,8 @@ final void accept(final MethodWriter methodWriter) { * * @param symbolTable the type table to use to lookup and store type {@link Symbol}. * @param abstractType an abstract type, restricted to {@link Frame#CONSTANT_KIND}, {@link - * Frame#REFERENCE_KIND} or {@link Frame#UNINITIALIZED_KIND} types. + * Frame#REFERENCE_KIND}, {@link Frame#UNINITIALIZED_KIND} or {@link + * Frame#FORWARD_UNINITIALIZED_KIND} types. * @param output where the abstract type must be put. * @see JVMS * 4.7.4 @@ -1415,6 +1434,10 @@ static void putAbstractType( case UNINITIALIZED_KIND: output.putByte(ITEM_UNINITIALIZED).putShort((int) symbolTable.getType(typeValue).data); break; + case FORWARD_UNINITIALIZED_KIND: + output.putByte(ITEM_UNINITIALIZED); + symbolTable.getForwardUninitializedLabel(typeValue).put(output); + break; default: throw new AssertionError(); } diff --git a/src/jvm/clojure/asm/Handle.java b/src/jvm/clojure/asm/Handle.java index 9121744932..420d3a658a 100644 --- a/src/jvm/clojure/asm/Handle.java +++ b/src/jvm/clojure/asm/Handle.java @@ -65,7 +65,7 @@ public final class Handle { * {@link Opcodes#H_INVOKESPECIAL}, {@link Opcodes#H_NEWINVOKESPECIAL} or {@link * Opcodes#H_INVOKEINTERFACE}. * @param owner the internal name of the class that owns the field or method designated by this - * handle. + * handle (see {@link Type#getInternalName()}). * @param name the name of the field or method designated by this handle. * @param descriptor the descriptor of the field or method designated by this handle. * @deprecated this constructor has been superseded by {@link #Handle(int, String, String, String, @@ -85,7 +85,7 @@ public Handle(final int tag, final String owner, final String name, final String * {@link Opcodes#H_INVOKESPECIAL}, {@link Opcodes#H_NEWINVOKESPECIAL} or {@link * Opcodes#H_INVOKEINTERFACE}. * @param owner the internal name of the class that owns the field or method designated by this - * handle. + * handle (see {@link Type#getInternalName()}). * @param name the name of the field or method designated by this handle. * @param descriptor the descriptor of the field or method designated by this handle. * @param isInterface whether the owner is an interface or not. @@ -118,7 +118,8 @@ public int getTag() { /** * Returns the internal name of the class that owns the field or method designated by this handle. * - * @return the internal name of the class that owns the field or method designated by this handle. + * @return the internal name of the class that owns the field or method designated by this handle + * (see {@link Type#getInternalName()}). */ public String getOwner() { return owner; diff --git a/src/jvm/clojure/asm/Handler.java b/src/jvm/clojure/asm/Handler.java index 41002b62be..7e95f08219 100644 --- a/src/jvm/clojure/asm/Handler.java +++ b/src/jvm/clojure/asm/Handler.java @@ -64,8 +64,8 @@ final class Handler { final int catchType; /** - * The internal name of the type of exceptions handled by this handler, or null to catch - * any exceptions. + * The internal name of the type of exceptions handled by this handler, or {@literal null} to + * catch any exceptions. */ final String catchTypeDescriptor; @@ -80,7 +80,7 @@ final class Handler { * @param handlerPc the handler_pc field of this JVMS exception_table entry. * @param catchType The catch_type field of this JVMS exception_table entry. * @param catchTypeDescriptor The internal name of the type of exceptions handled by this handler, - * or null to catch any exceptions. + * or {@literal null} to catch any exceptions. */ Handler( final Label startPc, @@ -111,9 +111,9 @@ final class Handler { * Removes the range between start and end from the Handler list that begins with the given * element. * - * @param firstHandler the beginning of a Handler list. May be null. + * @param firstHandler the beginning of a Handler list. May be {@literal null}. * @param start the start of the range to be removed. - * @param end the end of the range to be removed. Maybe null. + * @param end the end of the range to be removed. Maybe {@literal null}. * @return the exception handler list with the start-end range removed. */ static Handler removeRange(final Handler firstHandler, final Label start, final Label end) { @@ -152,7 +152,7 @@ static Handler removeRange(final Handler firstHandler, final Label start, final /** * Returns the number of elements of the Handler list that begins with the given element. * - * @param firstHandler the beginning of a Handler list. May be null. + * @param firstHandler the beginning of a Handler list. May be {@literal null}. * @return the number of elements of the Handler list that begins with 'handler'. */ static int getExceptionTableLength(final Handler firstHandler) { @@ -169,7 +169,7 @@ static int getExceptionTableLength(final Handler firstHandler) { * Returns the size in bytes of the JVMS exception_table corresponding to the Handler list that * begins with the given element. This includes the exception_table_length field. * - * @param firstHandler the beginning of a Handler list. May be null. + * @param firstHandler the beginning of a Handler list. May be {@literal null}. * @return the size in bytes of the exception_table_length and exception_table structures. */ static int getExceptionTableSize(final Handler firstHandler) { @@ -180,7 +180,7 @@ static int getExceptionTableSize(final Handler firstHandler) { * Puts the JVMS exception_table corresponding to the Handler list that begins with the given * element. This includes the exception_table_length field. * - * @param firstHandler the beginning of a Handler list. May be null. + * @param firstHandler the beginning of a Handler list. May be {@literal null}. * @param output where the exception_table_length and exception_table structures must be put. */ static void putExceptionTable(final Handler firstHandler, final ByteVector output) { diff --git a/src/jvm/clojure/asm/InstructionAdapter.java b/src/jvm/clojure/asm/InstructionAdapter.java new file mode 100644 index 0000000000..7216e0cc79 --- /dev/null +++ b/src/jvm/clojure/asm/InstructionAdapter.java @@ -0,0 +1,1296 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. + +package clojure.asm; + +/** + * A {@link MethodVisitor} providing a more detailed API to generate and transform instructions. + * + * @author Eric Bruneton + */ +public class InstructionAdapter extends MethodVisitor { + + /** The type of the java.lang.Object class. */ + public static final Type OBJECT_TYPE = Type.getType("Ljava/lang/Object;"); + + /** + * Constructs a new {@link InstructionAdapter}. Subclasses must not use this constructor. + * Instead, they must use the {@link #InstructionAdapter(int, MethodVisitor)} version. + * + * @param methodVisitor the method visitor to which this adapter delegates calls. + * @throws IllegalStateException If a subclass calls this constructor. + */ + public InstructionAdapter(final MethodVisitor methodVisitor) { + this(/* latest api = */ Opcodes.ASM9, methodVisitor); + if (getClass() != InstructionAdapter.class) { + throw new IllegalStateException(); + } + } + + /** + * Constructs a new {@link InstructionAdapter}. + * + * @param api the ASM API version implemented by this visitor. Must be one of the {@code + * ASM}x values in {@link Opcodes}. + * @param methodVisitor the method visitor to which this adapter delegates calls. + */ + protected InstructionAdapter(final int api, final MethodVisitor methodVisitor) { + super(api, methodVisitor); + } + + @Override + public void visitInsn(final int opcode) { + switch (opcode) { + case Opcodes.NOP: + nop(); + break; + case Opcodes.ACONST_NULL: + aconst(null); + break; + case Opcodes.ICONST_M1: + case Opcodes.ICONST_0: + case Opcodes.ICONST_1: + case Opcodes.ICONST_2: + case Opcodes.ICONST_3: + case Opcodes.ICONST_4: + case Opcodes.ICONST_5: + iconst(opcode - Opcodes.ICONST_0); + break; + case Opcodes.LCONST_0: + case Opcodes.LCONST_1: + lconst((long) (opcode - Opcodes.LCONST_0)); + break; + case Opcodes.FCONST_0: + case Opcodes.FCONST_1: + case Opcodes.FCONST_2: + fconst((float) (opcode - Opcodes.FCONST_0)); + break; + case Opcodes.DCONST_0: + case Opcodes.DCONST_1: + dconst((double) (opcode - Opcodes.DCONST_0)); + break; + case Opcodes.IALOAD: + aload(Type.INT_TYPE); + break; + case Opcodes.LALOAD: + aload(Type.LONG_TYPE); + break; + case Opcodes.FALOAD: + aload(Type.FLOAT_TYPE); + break; + case Opcodes.DALOAD: + aload(Type.DOUBLE_TYPE); + break; + case Opcodes.AALOAD: + aload(OBJECT_TYPE); + break; + case Opcodes.BALOAD: + aload(Type.BYTE_TYPE); + break; + case Opcodes.CALOAD: + aload(Type.CHAR_TYPE); + break; + case Opcodes.SALOAD: + aload(Type.SHORT_TYPE); + break; + case Opcodes.IASTORE: + astore(Type.INT_TYPE); + break; + case Opcodes.LASTORE: + astore(Type.LONG_TYPE); + break; + case Opcodes.FASTORE: + astore(Type.FLOAT_TYPE); + break; + case Opcodes.DASTORE: + astore(Type.DOUBLE_TYPE); + break; + case Opcodes.AASTORE: + astore(OBJECT_TYPE); + break; + case Opcodes.BASTORE: + astore(Type.BYTE_TYPE); + break; + case Opcodes.CASTORE: + astore(Type.CHAR_TYPE); + break; + case Opcodes.SASTORE: + astore(Type.SHORT_TYPE); + break; + case Opcodes.POP: + pop(); + break; + case Opcodes.POP2: + pop2(); + break; + case Opcodes.DUP: + dup(); + break; + case Opcodes.DUP_X1: + dupX1(); + break; + case Opcodes.DUP_X2: + dupX2(); + break; + case Opcodes.DUP2: + dup2(); + break; + case Opcodes.DUP2_X1: + dup2X1(); + break; + case Opcodes.DUP2_X2: + dup2X2(); + break; + case Opcodes.SWAP: + swap(); + break; + case Opcodes.IADD: + add(Type.INT_TYPE); + break; + case Opcodes.LADD: + add(Type.LONG_TYPE); + break; + case Opcodes.FADD: + add(Type.FLOAT_TYPE); + break; + case Opcodes.DADD: + add(Type.DOUBLE_TYPE); + break; + case Opcodes.ISUB: + sub(Type.INT_TYPE); + break; + case Opcodes.LSUB: + sub(Type.LONG_TYPE); + break; + case Opcodes.FSUB: + sub(Type.FLOAT_TYPE); + break; + case Opcodes.DSUB: + sub(Type.DOUBLE_TYPE); + break; + case Opcodes.IMUL: + mul(Type.INT_TYPE); + break; + case Opcodes.LMUL: + mul(Type.LONG_TYPE); + break; + case Opcodes.FMUL: + mul(Type.FLOAT_TYPE); + break; + case Opcodes.DMUL: + mul(Type.DOUBLE_TYPE); + break; + case Opcodes.IDIV: + div(Type.INT_TYPE); + break; + case Opcodes.LDIV: + div(Type.LONG_TYPE); + break; + case Opcodes.FDIV: + div(Type.FLOAT_TYPE); + break; + case Opcodes.DDIV: + div(Type.DOUBLE_TYPE); + break; + case Opcodes.IREM: + rem(Type.INT_TYPE); + break; + case Opcodes.LREM: + rem(Type.LONG_TYPE); + break; + case Opcodes.FREM: + rem(Type.FLOAT_TYPE); + break; + case Opcodes.DREM: + rem(Type.DOUBLE_TYPE); + break; + case Opcodes.INEG: + neg(Type.INT_TYPE); + break; + case Opcodes.LNEG: + neg(Type.LONG_TYPE); + break; + case Opcodes.FNEG: + neg(Type.FLOAT_TYPE); + break; + case Opcodes.DNEG: + neg(Type.DOUBLE_TYPE); + break; + case Opcodes.ISHL: + shl(Type.INT_TYPE); + break; + case Opcodes.LSHL: + shl(Type.LONG_TYPE); + break; + case Opcodes.ISHR: + shr(Type.INT_TYPE); + break; + case Opcodes.LSHR: + shr(Type.LONG_TYPE); + break; + case Opcodes.IUSHR: + ushr(Type.INT_TYPE); + break; + case Opcodes.LUSHR: + ushr(Type.LONG_TYPE); + break; + case Opcodes.IAND: + and(Type.INT_TYPE); + break; + case Opcodes.LAND: + and(Type.LONG_TYPE); + break; + case Opcodes.IOR: + or(Type.INT_TYPE); + break; + case Opcodes.LOR: + or(Type.LONG_TYPE); + break; + case Opcodes.IXOR: + xor(Type.INT_TYPE); + break; + case Opcodes.LXOR: + xor(Type.LONG_TYPE); + break; + case Opcodes.I2L: + cast(Type.INT_TYPE, Type.LONG_TYPE); + break; + case Opcodes.I2F: + cast(Type.INT_TYPE, Type.FLOAT_TYPE); + break; + case Opcodes.I2D: + cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + break; + case Opcodes.L2I: + cast(Type.LONG_TYPE, Type.INT_TYPE); + break; + case Opcodes.L2F: + cast(Type.LONG_TYPE, Type.FLOAT_TYPE); + break; + case Opcodes.L2D: + cast(Type.LONG_TYPE, Type.DOUBLE_TYPE); + break; + case Opcodes.F2I: + cast(Type.FLOAT_TYPE, Type.INT_TYPE); + break; + case Opcodes.F2L: + cast(Type.FLOAT_TYPE, Type.LONG_TYPE); + break; + case Opcodes.F2D: + cast(Type.FLOAT_TYPE, Type.DOUBLE_TYPE); + break; + case Opcodes.D2I: + cast(Type.DOUBLE_TYPE, Type.INT_TYPE); + break; + case Opcodes.D2L: + cast(Type.DOUBLE_TYPE, Type.LONG_TYPE); + break; + case Opcodes.D2F: + cast(Type.DOUBLE_TYPE, Type.FLOAT_TYPE); + break; + case Opcodes.I2B: + cast(Type.INT_TYPE, Type.BYTE_TYPE); + break; + case Opcodes.I2C: + cast(Type.INT_TYPE, Type.CHAR_TYPE); + break; + case Opcodes.I2S: + cast(Type.INT_TYPE, Type.SHORT_TYPE); + break; + case Opcodes.LCMP: + lcmp(); + break; + case Opcodes.FCMPL: + cmpl(Type.FLOAT_TYPE); + break; + case Opcodes.FCMPG: + cmpg(Type.FLOAT_TYPE); + break; + case Opcodes.DCMPL: + cmpl(Type.DOUBLE_TYPE); + break; + case Opcodes.DCMPG: + cmpg(Type.DOUBLE_TYPE); + break; + case Opcodes.IRETURN: + areturn(Type.INT_TYPE); + break; + case Opcodes.LRETURN: + areturn(Type.LONG_TYPE); + break; + case Opcodes.FRETURN: + areturn(Type.FLOAT_TYPE); + break; + case Opcodes.DRETURN: + areturn(Type.DOUBLE_TYPE); + break; + case Opcodes.ARETURN: + areturn(OBJECT_TYPE); + break; + case Opcodes.RETURN: + areturn(Type.VOID_TYPE); + break; + case Opcodes.ARRAYLENGTH: + arraylength(); + break; + case Opcodes.ATHROW: + athrow(); + break; + case Opcodes.MONITORENTER: + monitorenter(); + break; + case Opcodes.MONITOREXIT: + monitorexit(); + break; + default: + throw new IllegalArgumentException(); + } + } + + @Override + public void visitIntInsn(final int opcode, final int operand) { + switch (opcode) { + case Opcodes.BIPUSH: + iconst(operand); + break; + case Opcodes.SIPUSH: + iconst(operand); + break; + case Opcodes.NEWARRAY: + switch (operand) { + case Opcodes.T_BOOLEAN: + newarray(Type.BOOLEAN_TYPE); + break; + case Opcodes.T_CHAR: + newarray(Type.CHAR_TYPE); + break; + case Opcodes.T_BYTE: + newarray(Type.BYTE_TYPE); + break; + case Opcodes.T_SHORT: + newarray(Type.SHORT_TYPE); + break; + case Opcodes.T_INT: + newarray(Type.INT_TYPE); + break; + case Opcodes.T_FLOAT: + newarray(Type.FLOAT_TYPE); + break; + case Opcodes.T_LONG: + newarray(Type.LONG_TYPE); + break; + case Opcodes.T_DOUBLE: + newarray(Type.DOUBLE_TYPE); + break; + default: + throw new IllegalArgumentException(); + } + break; + default: + throw new IllegalArgumentException(); + } + } + + @Override + public void visitVarInsn(final int opcode, final int varIndex) { + switch (opcode) { + case Opcodes.ILOAD: + load(varIndex, Type.INT_TYPE); + break; + case Opcodes.LLOAD: + load(varIndex, Type.LONG_TYPE); + break; + case Opcodes.FLOAD: + load(varIndex, Type.FLOAT_TYPE); + break; + case Opcodes.DLOAD: + load(varIndex, Type.DOUBLE_TYPE); + break; + case Opcodes.ALOAD: + load(varIndex, OBJECT_TYPE); + break; + case Opcodes.ISTORE: + store(varIndex, Type.INT_TYPE); + break; + case Opcodes.LSTORE: + store(varIndex, Type.LONG_TYPE); + break; + case Opcodes.FSTORE: + store(varIndex, Type.FLOAT_TYPE); + break; + case Opcodes.DSTORE: + store(varIndex, Type.DOUBLE_TYPE); + break; + case Opcodes.ASTORE: + store(varIndex, OBJECT_TYPE); + break; + case Opcodes.RET: + ret(varIndex); + break; + default: + throw new IllegalArgumentException(); + } + } + + @Override + public void visitTypeInsn(final int opcode, final String type) { + Type objectType = Type.getObjectType(type); + switch (opcode) { + case Opcodes.NEW: + anew(objectType); + break; + case Opcodes.ANEWARRAY: + newarray(objectType); + break; + case Opcodes.CHECKCAST: + checkcast(objectType); + break; + case Opcodes.INSTANCEOF: + instanceOf(objectType); + break; + default: + throw new IllegalArgumentException(); + } + } + + @Override + public void visitFieldInsn( + final int opcode, final String owner, final String name, final String descriptor) { + switch (opcode) { + case Opcodes.GETSTATIC: + getstatic(owner, name, descriptor); + break; + case Opcodes.PUTSTATIC: + putstatic(owner, name, descriptor); + break; + case Opcodes.GETFIELD: + getfield(owner, name, descriptor); + break; + case Opcodes.PUTFIELD: + putfield(owner, name, descriptor); + break; + default: + throw new IllegalArgumentException(); + } + } + + @Override + public void visitMethodInsn( + final int opcodeAndSource, + final String owner, + final String name, + final String descriptor, + final boolean isInterface) { + if (api < Opcodes.ASM5 && (opcodeAndSource & Opcodes.SOURCE_DEPRECATED) == 0) { + // Redirect the call to the deprecated version of this method. + super.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface); + return; + } + int opcode = opcodeAndSource & ~Opcodes.SOURCE_MASK; + + switch (opcode) { + case Opcodes.INVOKESPECIAL: + invokespecial(owner, name, descriptor, isInterface); + break; + case Opcodes.INVOKEVIRTUAL: + invokevirtual(owner, name, descriptor, isInterface); + break; + case Opcodes.INVOKESTATIC: + invokestatic(owner, name, descriptor, isInterface); + break; + case Opcodes.INVOKEINTERFACE: + invokeinterface(owner, name, descriptor); + break; + default: + throw new IllegalArgumentException(); + } + } + + @Override + public void visitInvokeDynamicInsn( + final String name, + final String descriptor, + final Handle bootstrapMethodHandle, + final Object... bootstrapMethodArguments) { + invokedynamic(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments); + } + + @Override + public void visitJumpInsn(final int opcode, final Label label) { + switch (opcode) { + case Opcodes.IFEQ: + ifeq(label); + break; + case Opcodes.IFNE: + ifne(label); + break; + case Opcodes.IFLT: + iflt(label); + break; + case Opcodes.IFGE: + ifge(label); + break; + case Opcodes.IFGT: + ifgt(label); + break; + case Opcodes.IFLE: + ifle(label); + break; + case Opcodes.IF_ICMPEQ: + ificmpeq(label); + break; + case Opcodes.IF_ICMPNE: + ificmpne(label); + break; + case Opcodes.IF_ICMPLT: + ificmplt(label); + break; + case Opcodes.IF_ICMPGE: + ificmpge(label); + break; + case Opcodes.IF_ICMPGT: + ificmpgt(label); + break; + case Opcodes.IF_ICMPLE: + ificmple(label); + break; + case Opcodes.IF_ACMPEQ: + ifacmpeq(label); + break; + case Opcodes.IF_ACMPNE: + ifacmpne(label); + break; + case Opcodes.GOTO: + goTo(label); + break; + case Opcodes.JSR: + jsr(label); + break; + case Opcodes.IFNULL: + ifnull(label); + break; + case Opcodes.IFNONNULL: + ifnonnull(label); + break; + default: + throw new IllegalArgumentException(); + } + } + + @Override + public void visitLabel(final Label label) { + mark(label); + } + + @Override + public void visitLdcInsn(final Object value) { + if (api < Opcodes.ASM5 + && (value instanceof Handle + || (value instanceof Type && ((Type) value).getSort() == Type.METHOD))) { + throw new UnsupportedOperationException("This feature requires ASM5"); + } + if (api < Opcodes.ASM7 && value instanceof ConstantDynamic) { + throw new UnsupportedOperationException("This feature requires ASM7"); + } + if (value instanceof Integer) { + iconst((Integer) value); + } else if (value instanceof Byte) { + iconst(((Byte) value).intValue()); + } else if (value instanceof Character) { + iconst(((Character) value).charValue()); + } else if (value instanceof Short) { + iconst(((Short) value).intValue()); + } else if (value instanceof Boolean) { + iconst(((Boolean) value).booleanValue() ? 1 : 0); + } else if (value instanceof Float) { + fconst((Float) value); + } else if (value instanceof Long) { + lconst((Long) value); + } else if (value instanceof Double) { + dconst((Double) value); + } else if (value instanceof String) { + aconst(value); + } else if (value instanceof Type) { + tconst((Type) value); + } else if (value instanceof Handle) { + hconst((Handle) value); + } else if (value instanceof ConstantDynamic) { + cconst((ConstantDynamic) value); + } else { + throw new IllegalArgumentException(); + } + } + + @Override + public void visitIincInsn(final int varIndex, final int increment) { + iinc(varIndex, increment); + } + + @Override + public void visitTableSwitchInsn( + final int min, final int max, final Label dflt, final Label... labels) { + tableswitch(min, max, dflt, labels); + } + + @Override + public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) { + lookupswitch(dflt, keys, labels); + } + + @Override + public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions) { + multianewarray(descriptor, numDimensions); + } + + // ----------------------------------------------------------------------------------------------- + + /** Generates a nop instruction. */ + public void nop() { + mv.visitInsn(Opcodes.NOP); + } + + /** + * Generates the instruction to push the given value on the stack. + * + * @param value the constant to be pushed on the stack. This parameter must be an {@link Integer}, + * a {@link Float}, a {@link Long}, a {@link Double}, a {@link String}, a {@link Type} of + * OBJECT or ARRAY sort for {@code .class} constants, for classes whose version is 49, a + * {@link Type} of METHOD sort for MethodType, a {@link Handle} for MethodHandle constants, + * for classes whose version is 51 or a {@link ConstantDynamic} for a constant dynamic for + * classes whose version is 55. + */ + public void aconst(final Object value) { + if (value == null) { + mv.visitInsn(Opcodes.ACONST_NULL); + } else { + mv.visitLdcInsn(value); + } + } + + /** + * Generates the instruction to push the given value on the stack. + * + * @param intValue the constant to be pushed on the stack. + */ + public void iconst(final int intValue) { + if (intValue >= -1 && intValue <= 5) { + mv.visitInsn(Opcodes.ICONST_0 + intValue); + } else if (intValue >= Byte.MIN_VALUE && intValue <= Byte.MAX_VALUE) { + mv.visitIntInsn(Opcodes.BIPUSH, intValue); + } else if (intValue >= Short.MIN_VALUE && intValue <= Short.MAX_VALUE) { + mv.visitIntInsn(Opcodes.SIPUSH, intValue); + } else { + mv.visitLdcInsn(intValue); + } + } + + /** + * Generates the instruction to push the given value on the stack. + * + * @param longValue the constant to be pushed on the stack. + */ + public void lconst(final long longValue) { + if (longValue == 0L || longValue == 1L) { + mv.visitInsn(Opcodes.LCONST_0 + (int) longValue); + } else { + mv.visitLdcInsn(longValue); + } + } + + /** + * Generates the instruction to push the given value on the stack. + * + * @param floatValue the constant to be pushed on the stack. + */ + public void fconst(final float floatValue) { + int bits = Float.floatToIntBits(floatValue); + if (bits == 0L || bits == 0x3F800000 || bits == 0x40000000) { // 0..2 + mv.visitInsn(Opcodes.FCONST_0 + (int) floatValue); + } else { + mv.visitLdcInsn(floatValue); + } + } + + /** + * Generates the instruction to push the given value on the stack. + * + * @param doubleValue the constant to be pushed on the stack. + */ + public void dconst(final double doubleValue) { + long bits = Double.doubleToLongBits(doubleValue); + if (bits == 0L || bits == 0x3FF0000000000000L) { // +0.0d and 1.0d + mv.visitInsn(Opcodes.DCONST_0 + (int) doubleValue); + } else { + mv.visitLdcInsn(doubleValue); + } + } + + /** + * Generates the instruction to push the given type on the stack. + * + * @param type the type to be pushed on the stack. + */ + public void tconst(final Type type) { + mv.visitLdcInsn(type); + } + + /** + * Generates the instruction to push the given handle on the stack. + * + * @param handle the handle to be pushed on the stack. + */ + public void hconst(final Handle handle) { + mv.visitLdcInsn(handle); + } + + /** + * Generates the instruction to push the given constant dynamic on the stack. + * + * @param constantDynamic the constant dynamic to be pushed on the stack. + */ + public void cconst(final ConstantDynamic constantDynamic) { + mv.visitLdcInsn(constantDynamic); + } + + public void load(final int varIndex, final Type type) { + mv.visitVarInsn(type.getOpcode(Opcodes.ILOAD), varIndex); + } + + public void aload(final Type type) { + mv.visitInsn(type.getOpcode(Opcodes.IALOAD)); + } + + public void store(final int varIndex, final Type type) { + mv.visitVarInsn(type.getOpcode(Opcodes.ISTORE), varIndex); + } + + public void astore(final Type type) { + mv.visitInsn(type.getOpcode(Opcodes.IASTORE)); + } + + public void pop() { + mv.visitInsn(Opcodes.POP); + } + + public void pop2() { + mv.visitInsn(Opcodes.POP2); + } + + public void dup() { + mv.visitInsn(Opcodes.DUP); + } + + public void dup2() { + mv.visitInsn(Opcodes.DUP2); + } + + public void dupX1() { + mv.visitInsn(Opcodes.DUP_X1); + } + + public void dupX2() { + mv.visitInsn(Opcodes.DUP_X2); + } + + public void dup2X1() { + mv.visitInsn(Opcodes.DUP2_X1); + } + + public void dup2X2() { + mv.visitInsn(Opcodes.DUP2_X2); + } + + public void swap() { + mv.visitInsn(Opcodes.SWAP); + } + + public void add(final Type type) { + mv.visitInsn(type.getOpcode(Opcodes.IADD)); + } + + public void sub(final Type type) { + mv.visitInsn(type.getOpcode(Opcodes.ISUB)); + } + + public void mul(final Type type) { + mv.visitInsn(type.getOpcode(Opcodes.IMUL)); + } + + public void div(final Type type) { + mv.visitInsn(type.getOpcode(Opcodes.IDIV)); + } + + public void rem(final Type type) { + mv.visitInsn(type.getOpcode(Opcodes.IREM)); + } + + public void neg(final Type type) { + mv.visitInsn(type.getOpcode(Opcodes.INEG)); + } + + public void shl(final Type type) { + mv.visitInsn(type.getOpcode(Opcodes.ISHL)); + } + + public void shr(final Type type) { + mv.visitInsn(type.getOpcode(Opcodes.ISHR)); + } + + public void ushr(final Type type) { + mv.visitInsn(type.getOpcode(Opcodes.IUSHR)); + } + + public void and(final Type type) { + mv.visitInsn(type.getOpcode(Opcodes.IAND)); + } + + public void or(final Type type) { + mv.visitInsn(type.getOpcode(Opcodes.IOR)); + } + + public void xor(final Type type) { + mv.visitInsn(type.getOpcode(Opcodes.IXOR)); + } + + public void iinc(final int varIndex, final int increment) { + mv.visitIincInsn(varIndex, increment); + } + + /** + * Generates the instruction to cast from the first given type to the other. + * + * @param from a Type. + * @param to a Type. + */ + public void cast(final Type from, final Type to) { + cast(mv, from, to); + } + + /** + * Generates the instruction to cast from the first given type to the other. + * + * @param methodVisitor the method visitor to use to generate the instruction. + * @param from a Type. + * @param to a Type. + */ + public static void cast(final MethodVisitor methodVisitor, final Type from, final Type to) { + if (from != to) { + if (from == Type.DOUBLE_TYPE) { + if (to == Type.FLOAT_TYPE) { + methodVisitor.visitInsn(Opcodes.D2F); + } else if (to == Type.LONG_TYPE) { + methodVisitor.visitInsn(Opcodes.D2L); + } else { + methodVisitor.visitInsn(Opcodes.D2I); + cast(methodVisitor, Type.INT_TYPE, to); + } + } else if (from == Type.FLOAT_TYPE) { + if (to == Type.DOUBLE_TYPE) { + methodVisitor.visitInsn(Opcodes.F2D); + } else if (to == Type.LONG_TYPE) { + methodVisitor.visitInsn(Opcodes.F2L); + } else { + methodVisitor.visitInsn(Opcodes.F2I); + cast(methodVisitor, Type.INT_TYPE, to); + } + } else if (from == Type.LONG_TYPE) { + if (to == Type.DOUBLE_TYPE) { + methodVisitor.visitInsn(Opcodes.L2D); + } else if (to == Type.FLOAT_TYPE) { + methodVisitor.visitInsn(Opcodes.L2F); + } else { + methodVisitor.visitInsn(Opcodes.L2I); + cast(methodVisitor, Type.INT_TYPE, to); + } + } else { + if (to == Type.BYTE_TYPE) { + methodVisitor.visitInsn(Opcodes.I2B); + } else if (to == Type.CHAR_TYPE) { + methodVisitor.visitInsn(Opcodes.I2C); + } else if (to == Type.DOUBLE_TYPE) { + methodVisitor.visitInsn(Opcodes.I2D); + } else if (to == Type.FLOAT_TYPE) { + methodVisitor.visitInsn(Opcodes.I2F); + } else if (to == Type.LONG_TYPE) { + methodVisitor.visitInsn(Opcodes.I2L); + } else if (to == Type.SHORT_TYPE) { + methodVisitor.visitInsn(Opcodes.I2S); + } + } + } + } + + public void lcmp() { + mv.visitInsn(Opcodes.LCMP); + } + + public void cmpl(final Type type) { + mv.visitInsn(type == Type.FLOAT_TYPE ? Opcodes.FCMPL : Opcodes.DCMPL); + } + + public void cmpg(final Type type) { + mv.visitInsn(type == Type.FLOAT_TYPE ? Opcodes.FCMPG : Opcodes.DCMPG); + } + + public void ifeq(final Label label) { + mv.visitJumpInsn(Opcodes.IFEQ, label); + } + + public void ifne(final Label label) { + mv.visitJumpInsn(Opcodes.IFNE, label); + } + + public void iflt(final Label label) { + mv.visitJumpInsn(Opcodes.IFLT, label); + } + + public void ifge(final Label label) { + mv.visitJumpInsn(Opcodes.IFGE, label); + } + + public void ifgt(final Label label) { + mv.visitJumpInsn(Opcodes.IFGT, label); + } + + public void ifle(final Label label) { + mv.visitJumpInsn(Opcodes.IFLE, label); + } + + public void ificmpeq(final Label label) { + mv.visitJumpInsn(Opcodes.IF_ICMPEQ, label); + } + + public void ificmpne(final Label label) { + mv.visitJumpInsn(Opcodes.IF_ICMPNE, label); + } + + public void ificmplt(final Label label) { + mv.visitJumpInsn(Opcodes.IF_ICMPLT, label); + } + + public void ificmpge(final Label label) { + mv.visitJumpInsn(Opcodes.IF_ICMPGE, label); + } + + public void ificmpgt(final Label label) { + mv.visitJumpInsn(Opcodes.IF_ICMPGT, label); + } + + public void ificmple(final Label label) { + mv.visitJumpInsn(Opcodes.IF_ICMPLE, label); + } + + public void ifacmpeq(final Label label) { + mv.visitJumpInsn(Opcodes.IF_ACMPEQ, label); + } + + public void ifacmpne(final Label label) { + mv.visitJumpInsn(Opcodes.IF_ACMPNE, label); + } + + public void goTo(final Label label) { + mv.visitJumpInsn(Opcodes.GOTO, label); + } + + public void jsr(final Label label) { + mv.visitJumpInsn(Opcodes.JSR, label); + } + + public void ret(final int varIndex) { + mv.visitVarInsn(Opcodes.RET, varIndex); + } + + public void tableswitch(final int min, final int max, final Label dflt, final Label... labels) { + mv.visitTableSwitchInsn(min, max, dflt, labels); + } + + public void lookupswitch(final Label dflt, final int[] keys, final Label[] labels) { + mv.visitLookupSwitchInsn(dflt, keys, labels); + } + + public void areturn(final Type type) { + mv.visitInsn(type.getOpcode(Opcodes.IRETURN)); + } + + public void getstatic(final String owner, final String name, final String descriptor) { + mv.visitFieldInsn(Opcodes.GETSTATIC, owner, name, descriptor); + } + + public void putstatic(final String owner, final String name, final String descriptor) { + mv.visitFieldInsn(Opcodes.PUTSTATIC, owner, name, descriptor); + } + + public void getfield(final String owner, final String name, final String descriptor) { + mv.visitFieldInsn(Opcodes.GETFIELD, owner, name, descriptor); + } + + public void putfield(final String owner, final String name, final String descriptor) { + mv.visitFieldInsn(Opcodes.PUTFIELD, owner, name, descriptor); + } + + /** + * Deprecated. + * + * @param owner the internal name of the method's owner class (see {@link + * Type#getInternalName()}). + * @param name the method's name. + * @param descriptor the method's descriptor (see {@link Type}). + * @deprecated use {@link #invokevirtual(String, String, String, boolean)} instead. + */ + @Deprecated + public void invokevirtual(final String owner, final String name, final String descriptor) { + if (api >= Opcodes.ASM5) { + invokevirtual(owner, name, descriptor, false); + return; + } + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, descriptor); + } + + /** + * Generates the instruction to call the given virtual method. + * + * @param owner the internal name of the method's owner class (see {@link + * Type#getInternalName()}). + * @param name the method's name. + * @param descriptor the method's descriptor (see {@link Type}). + * @param isInterface if the method's owner class is an interface. + */ + public void invokevirtual( + final String owner, final String name, final String descriptor, final boolean isInterface) { + if (api < Opcodes.ASM5) { + if (isInterface) { + throw new UnsupportedOperationException("INVOKEVIRTUAL on interfaces require ASM 5"); + } + invokevirtual(owner, name, descriptor); + return; + } + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, descriptor, isInterface); + } + + /** + * Deprecated. + * + * @param owner the internal name of the method's owner class (see {@link + * Type#getInternalName()}). + * @param name the method's name. + * @param descriptor the method's descriptor (see {@link Type}). + * @deprecated use {@link #invokespecial(String, String, String, boolean)} instead. + */ + @Deprecated + public void invokespecial(final String owner, final String name, final String descriptor) { + if (api >= Opcodes.ASM5) { + invokespecial(owner, name, descriptor, false); + return; + } + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, descriptor, false); + } + + /** + * Generates the instruction to call the given special method. + * + * @param owner the internal name of the method's owner class (see {@link + * Type#getInternalName()}). + * @param name the method's name. + * @param descriptor the method's descriptor (see {@link Type}). + * @param isInterface if the method's owner class is an interface. + */ + public void invokespecial( + final String owner, final String name, final String descriptor, final boolean isInterface) { + if (api < Opcodes.ASM5) { + if (isInterface) { + throw new UnsupportedOperationException("INVOKESPECIAL on interfaces require ASM 5"); + } + invokespecial(owner, name, descriptor); + return; + } + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, descriptor, isInterface); + } + + /** + * Deprecated. + * + * @param owner the internal name of the method's owner class (see {@link + * Type#getInternalName()}). + * @param name the method's name. + * @param descriptor the method's descriptor (see {@link Type}). + * @deprecated use {@link #invokestatic(String, String, String, boolean)} instead. + */ + @Deprecated + public void invokestatic(final String owner, final String name, final String descriptor) { + if (api >= Opcodes.ASM5) { + invokestatic(owner, name, descriptor, false); + return; + } + mv.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, descriptor, false); + } + + /** + * Generates the instruction to call the given static method. + * + * @param owner the internal name of the method's owner class (see {@link + * Type#getInternalName()}). + * @param name the method's name. + * @param descriptor the method's descriptor (see {@link Type}). + * @param isInterface if the method's owner class is an interface. + */ + public void invokestatic( + final String owner, final String name, final String descriptor, final boolean isInterface) { + if (api < Opcodes.ASM5) { + if (isInterface) { + throw new UnsupportedOperationException("INVOKESTATIC on interfaces require ASM 5"); + } + invokestatic(owner, name, descriptor); + return; + } + mv.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, descriptor, isInterface); + } + + /** + * Generates the instruction to call the given interface method. + * + * @param owner the internal name of the method's owner class (see {@link + * Type#getInternalName()}). + * @param name the method's name. + * @param descriptor the method's descriptor (see {@link Type}). + */ + public void invokeinterface(final String owner, final String name, final String descriptor) { + mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, owner, name, descriptor, true); + } + + /** + * Generates the instruction to call the given dynamic method. + * + * @param name the method's name. + * @param descriptor the method's descriptor (see {@link Type}). + * @param bootstrapMethodHandle the bootstrap method. + * @param bootstrapMethodArguments the bootstrap method constant arguments. Each argument must be + * an {@link Integer}, {@link Float}, {@link Long}, {@link Double}, {@link String}, {@link + * Type}, {@link Handle} or {@link ConstantDynamic} value. This method is allowed to modify + * the content of the array so a caller should expect that this array may change. + */ + public void invokedynamic( + final String name, + final String descriptor, + final Handle bootstrapMethodHandle, + final Object[] bootstrapMethodArguments) { + mv.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments); + } + + public void anew(final Type type) { + mv.visitTypeInsn(Opcodes.NEW, type.getInternalName()); + } + + /** + * Generates the instruction to create and push on the stack an array of the given type. + * + * @param type an array Type. + */ + public void newarray(final Type type) { + newarray(mv, type); + } + + /** + * Generates the instruction to create and push on the stack an array of the given type. + * + * @param methodVisitor the method visitor to use to generate the instruction. + * @param type an array Type. + */ + public static void newarray(final MethodVisitor methodVisitor, final Type type) { + int arrayType; + switch (type.getSort()) { + case Type.BOOLEAN: + arrayType = Opcodes.T_BOOLEAN; + break; + case Type.CHAR: + arrayType = Opcodes.T_CHAR; + break; + case Type.BYTE: + arrayType = Opcodes.T_BYTE; + break; + case Type.SHORT: + arrayType = Opcodes.T_SHORT; + break; + case Type.INT: + arrayType = Opcodes.T_INT; + break; + case Type.FLOAT: + arrayType = Opcodes.T_FLOAT; + break; + case Type.LONG: + arrayType = Opcodes.T_LONG; + break; + case Type.DOUBLE: + arrayType = Opcodes.T_DOUBLE; + break; + default: + methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, type.getInternalName()); + return; + } + methodVisitor.visitIntInsn(Opcodes.NEWARRAY, arrayType); + } + + public void arraylength() { + mv.visitInsn(Opcodes.ARRAYLENGTH); + } + + public void athrow() { + mv.visitInsn(Opcodes.ATHROW); + } + + public void checkcast(final Type type) { + mv.visitTypeInsn(Opcodes.CHECKCAST, type.getInternalName()); + } + + public void instanceOf(final Type type) { + mv.visitTypeInsn(Opcodes.INSTANCEOF, type.getInternalName()); + } + + public void monitorenter() { + mv.visitInsn(Opcodes.MONITORENTER); + } + + public void monitorexit() { + mv.visitInsn(Opcodes.MONITOREXIT); + } + + public void multianewarray(final String descriptor, final int numDimensions) { + mv.visitMultiANewArrayInsn(descriptor, numDimensions); + } + + public void ifnull(final Label label) { + mv.visitJumpInsn(Opcodes.IFNULL, label); + } + + public void ifnonnull(final Label label) { + mv.visitJumpInsn(Opcodes.IFNONNULL, label); + } + + public void mark(final Label label) { + mv.visitLabel(label); + } +} diff --git a/src/jvm/clojure/asm/Label.java b/src/jvm/clojure/asm/Label.java index 86afe087de..9ccf2117c4 100644 --- a/src/jvm/clojure/asm/Label.java +++ b/src/jvm/clojure/asm/Label.java @@ -81,6 +81,9 @@ public class Label { /** A flag indicating that the basic block corresponding to a label is the end of a subroutine. */ static final int FLAG_SUBROUTINE_END = 64; + /** A flag indicating that this label has at least one associated line number. */ + static final int FLAG_LINE_NUMBER = 128; + /** * The number of elements to add to the {@link #otherLineNumbers} array when it needs to be * resized to store a new source line number. @@ -113,6 +116,13 @@ public class Label { */ static final int FORWARD_REFERENCE_TYPE_WIDE = 0x20000000; + /** + * The type of forward references stored in two bytes in the stack map table. This is the + * case of the labels of {@link Frame#ITEM_UNINITIALIZED} stack map frame elements, when the NEW + * instruction is after the <init> constructor call (in bytecode offset order). + */ + static final int FORWARD_REFERENCE_TYPE_STACK_MAP = 0x30000000; + /** * The bit mask to extract the 'handle' of a forward reference to this label. The extracted handle * is the bytecode offset where the forward reference value is stored (using either 2 or 4 bytes, @@ -145,9 +155,9 @@ public class Label { short flags; /** - * The source line number corresponding to this label, or 0. If there are several source line - * numbers corresponding to this label, the first one is stored in this field, and the remaining - * ones are stored in {@link #otherLineNumbers}. + * The source line number corresponding to this label, if {@link #FLAG_LINE_NUMBER} is set. If + * there are several source line numbers corresponding to this label, the first one is stored in + * this field, and the remaining ones are stored in {@link #otherLineNumbers}. */ private short lineNumber; @@ -178,7 +188,7 @@ public class Label { * and {@link #FORWARD_REFERENCE_HANDLE_MASK}. *

    * - * For instance, for an ifnull instruction at bytecode offset x, 'sourceInsnBytecodeOffset' is + *

    For instance, for an ifnull instruction at bytecode offset x, 'sourceInsnBytecodeOffset' is * equal to x, and 'reference' is of type {@link #FORWARD_REFERENCE_TYPE_SHORT} with value x + 1 * (because the ifnull instruction uses a 2 bytes bytecode offset operand stored one byte after * the start of the instruction itself). For the default case of a lookupswitch instruction at @@ -227,7 +237,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 +275,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, @@ -331,7 +342,8 @@ final Label getCanonicalInstance() { * @param lineNumber a source line number (which should be strictly positive). */ final void addLineNumber(final int lineNumber) { - if (this.lineNumber == 0) { + if ((flags & FLAG_LINE_NUMBER) == 0) { + flags |= FLAG_LINE_NUMBER; this.lineNumber = (short) lineNumber; } else { if (otherLineNumbers == null) { @@ -355,7 +367,7 @@ final void addLineNumber(final int lineNumber) { */ final void accept(final MethodVisitor methodVisitor, final boolean visitLineNumbers) { methodVisitor.visitLabel(this); - if (visitLineNumbers && lineNumber != 0) { + if (visitLineNumbers && (flags & FLAG_LINE_NUMBER) != 0) { methodVisitor.visitLineNumber(lineNumber & 0xFFFF, this); if (otherLineNumbers != null) { for (int i = 1; i <= otherLineNumbers[0]; ++i) { @@ -399,6 +411,20 @@ final void put( } } + /** + * Puts a reference to this label in the stack map table of a method. If the bytecode + * offset of the label is known, it is written directly. Otherwise, a null relative offset is + * written and a new forward reference is declared for this label. + * + * @param stackMapTableEntries the stack map table where the label offset must be added. + */ + final void put(final ByteVector stackMapTableEntries) { + if ((flags & FLAG_RESOLVED) == 0) { + addForwardReference(0, FORWARD_REFERENCE_TYPE_STACK_MAP, stackMapTableEntries.length); + } + stackMapTableEntries.putShort(bytecodeOffset); + } + /** * Adds a forward reference to this label. This method must be called only for a true forward * reference, i.e. only if this label is not resolved yet. For backward references, the relative @@ -431,17 +457,21 @@ private void addForwardReference( * Sets the bytecode offset of this label to the given value and resolves the forward references * to this label, if any. This method must be called when this label is added to the bytecode of * the method, i.e. when its bytecode offset becomes known. This method fills in the blanks that - * where left in the bytecode by each forward reference previously added to this label. + * where left in the bytecode (and optionally in the stack map table) by each forward reference + * previously added to this label. * * @param code the bytecode of the method. + * @param stackMapTableEntries the 'entries' array of the StackMapTable code attribute of the + * method. Maybe {@literal null}. * @param bytecodeOffset the bytecode offset of this label. - * @return true if a blank that was left for this label was too small to store the + * @return {@literal true} if a blank that was left for this label was too small to store the * offset. In such a case the corresponding jump instruction is replaced with an equivalent * ASM specific instruction using an unsigned two bytes offset. These ASM specific * instructions are later replaced with standard bytecode instructions with wider offsets (4 * bytes instead of 2), in ClassReader. */ - final boolean resolve(final byte[] code, final int bytecodeOffset) { + final boolean resolve( + final byte[] code, final ByteVector stackMapTableEntries, final int bytecodeOffset) { this.flags |= FLAG_RESOLVED; this.bytecodeOffset = bytecodeOffset; if (forwardReferences == null) { @@ -471,11 +501,14 @@ final boolean resolve(final byte[] code, final int bytecodeOffset) { } code[handle++] = (byte) (relativeOffset >>> 8); code[handle] = (byte) relativeOffset; - } else { + } else if ((reference & FORWARD_REFERENCE_TYPE_MASK) == FORWARD_REFERENCE_TYPE_WIDE) { code[handle++] = (byte) (relativeOffset >>> 24); code[handle++] = (byte) (relativeOffset >>> 16); code[handle++] = (byte) (relativeOffset >>> 8); code[handle] = (byte) relativeOffset; + } else { + stackMapTableEntries.data[handle++] = (byte) (bytecodeOffset >>> 8); + stackMapTableEntries.data[handle] = (byte) bytecodeOffset; } } return hasAsmInstructions; diff --git a/src/jvm/clojure/asm/MethodTooLargeException.java b/src/jvm/clojure/asm/MethodTooLargeException.java new file mode 100644 index 0000000000..81991844a0 --- /dev/null +++ b/src/jvm/clojure/asm/MethodTooLargeException.java @@ -0,0 +1,99 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package clojure.asm; + +/** + * Exception thrown when the Code attribute of a method produced by a {@link ClassWriter} is too + * large. + * + * @author Jason Zaugg + */ +public final class MethodTooLargeException extends IndexOutOfBoundsException { + private static final long serialVersionUID = 6807380416709738314L; + + private final String className; + private final String methodName; + private final String descriptor; + private final int codeSize; + + /** + * Constructs a new {@link MethodTooLargeException}. + * + * @param className the internal name of the owner class (see {@link Type#getInternalName()}). + * @param methodName the name of the method. + * @param descriptor the descriptor of the method. + * @param codeSize the size of the method's Code attribute, in bytes. + */ + public MethodTooLargeException( + final String className, + final String methodName, + final String descriptor, + final int codeSize) { + super("Method too large: " + className + "." + methodName + " " + descriptor); + this.className = className; + this.methodName = methodName; + this.descriptor = descriptor; + this.codeSize = codeSize; + } + + /** + * Returns the internal name of the owner class. + * + * @return the internal name of the owner class (see {@link Type#getInternalName()}). + */ + public String getClassName() { + return className; + } + + /** + * Returns the name of the method. + * + * @return the name of the method. + */ + public String getMethodName() { + return methodName; + } + + /** + * Returns the descriptor of the method. + * + * @return the descriptor of the method. + */ + public String getDescriptor() { + return descriptor; + } + + /** + * Returns the size of the method's Code attribute, in bytes. + * + * @return the size of the method's Code attribute, in bytes. + */ + public int getCodeSize() { + return codeSize; + } +} diff --git a/src/jvm/clojure/asm/MethodVisitor.java b/src/jvm/clojure/asm/MethodVisitor.java index 4489612134..fc966dc8d1 100644 --- a/src/jvm/clojure/asm/MethodVisitor.java +++ b/src/jvm/clojure/asm/MethodVisitor.java @@ -29,20 +29,20 @@ /** * A visitor to visit a Java method. The methods of this class must be called in the following - * order: ( visitParameter )* [ visitAnnotationDefault ] ( - * visitAnnotation | visitAnnotableParameterCount | - * visitParameterAnnotation visitTypeAnnotation | visitAttribute )* [ - * visitCode ( visitFrame | visitXInsn | visitLabel | - * visitInsnAnnotation | visitTryCatchBlock | visitTryCatchAnnotation | - * visitLocalVariable | visitLocalVariableAnnotation | visitLineNumber )* - * visitMaxs ] visitEnd. In addition, the visitXInsn and - * visitLabel methods must be called in the sequential order of the bytecode instructions - * of the visited code, visitInsnAnnotation must be called after the annotated - * instruction, visitTryCatchBlock must be called before the labels passed as - * arguments have been visited, visitTryCatchBlockAnnotation must be called after - * the corresponding try catch block has been visited, and the visitLocalVariable, - * visitLocalVariableAnnotation and visitLineNumber methods must be called - * after the labels passed as arguments have been visited. + * order: ( {@code visitParameter} )* [ {@code visitAnnotationDefault} ] ( {@code visitAnnotation} | + * {@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. * * @author Eric Bruneton */ @@ -51,45 +51,61 @@ public abstract class MethodVisitor { private static final String REQUIRES_ASM5 = "This feature requires ASM5"; /** - * The ASM API version implemented by this visitor. The value of this field must be one of {@link - * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7_EXPERIMENTAL}. + * The ASM API version implemented by this visitor. The value of this field must be one of the + * {@code ASM}x values in {@link Opcodes}. */ 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; /** * Constructs a new {@link MethodVisitor}. * - * @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_EXPERIMENTAL}. + * @param api the ASM API version implemented by this visitor. Must be one of the {@code + * ASM}x values in {@link Opcodes}. */ - public MethodVisitor(final int api) { + protected MethodVisitor(final int api) { this(api, null); } /** * Constructs a new {@link MethodVisitor}. * - * @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_EXPERIMENTAL}. + * @param api the ASM API version implemented by this visitor. Must be one of the {@code + * ASM}x values in {@link Opcodes}. * @param methodVisitor the method visitor to which this visitor must delegate method calls. May * be null. */ - public MethodVisitor(final int api, final MethodVisitor methodVisitor) { - if (api != Opcodes.ASM6 + protected MethodVisitor(final int api, final MethodVisitor methodVisitor) { + if (api != Opcodes.ASM9 + && api != Opcodes.ASM8 + && api != Opcodes.ASM7 + && api != Opcodes.ASM6 && api != Opcodes.ASM5 && api != Opcodes.ASM4 - && api != Opcodes.ASM7_EXPERIMENTAL) { - throw new IllegalArgumentException(); + && api != Opcodes.ASM10_EXPERIMENTAL) { + throw new IllegalArgumentException("Unsupported api " + api); + } + if (api == Opcodes.ASM10_EXPERIMENTAL) { + Constants.checkAsmExperimental(this); } this.api = api; this.mv = methodVisitor; } + /** + * The method visitor to which this visitor must delegate method calls. May be {@literal null}. + * + * @return the method visitor to which this visitor must delegate method calls, or {@literal + * null}. + */ + public MethodVisitor getDelegate() { + return mv; + } + // ----------------------------------------------------------------------------------------------- // Parameters, annotations and non standard attributes // ----------------------------------------------------------------------------------------------- @@ -97,9 +113,9 @@ 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 access the parameter's access flags, only ACC_FINAL, ACC_SYNTHETIC - * or/and ACC_MANDATED are allowed (see {@link Opcodes}). + * @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}). */ public void visitParameter(final String name, final int access) { if (api < Opcodes.ASM5) { @@ -114,9 +130,9 @@ public void visitParameter(final String name, final int access) { * Visits the default value of this annotation interface method. * * @return a visitor to the visit the actual default value of this annotation interface method, or - * null if this visitor is not interested in visiting this default value. The 'name' - * parameters passed to the methods of this annotation visitor are ignored. Moreover, exacly - * one visit method must be called on this annotation visitor, followed by visitEnd. + * {@literal null} if this visitor is not interested in visiting this default value. The + * 'name' parameters passed to the methods of this annotation visitor are ignored. Moreover, + * exactly one visit method must be called on this annotation visitor, followed by visitEnd. */ public AnnotationVisitor visitAnnotationDefault() { if (mv != null) { @@ -129,8 +145,8 @@ public AnnotationVisitor visitAnnotationDefault() { * Visits an annotation of this method. * * @param descriptor the class descriptor of the annotation class. - * @param visible true if the annotation is visible at runtime. - * @return a visitor to visit the annotation values, or null if this visitor is not + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not * interested in visiting this annotation. */ public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { @@ -149,11 +165,11 @@ public AnnotationVisitor visitAnnotation(final String descriptor, final boolean * TypeReference#METHOD_RECEIVER}, {@link TypeReference#METHOD_FORMAL_PARAMETER} or {@link * TypeReference#THROWS}. 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 null if the annotation targets + * static inner type within 'typeRef'. May be {@literal null} if the annotation targets * 'typeRef' as a whole. * @param descriptor the class descriptor of the annotation class. - * @param visible true if the annotation is visible at runtime. - * @return a visitor to visit the annotation values, or null if this visitor is not + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not * interested in visiting this annotation. */ public AnnotationVisitor visitTypeAnnotation( @@ -177,8 +193,8 @@ public AnnotationVisitor visitTypeAnnotation( * be strictly less when a method has synthetic parameters and when these parameters are * ignored when computing parameter indices for the purpose of parameter annotations (see * https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.18). - * @param visible true to define the number of method parameters that can have - * annotations visible at runtime, false to define the number of method parameters + * @param visible {@literal true} to define the number of method parameters that can have + * annotations visible at runtime, {@literal false} to define the number of method parameters * that can have annotations invisible at runtime. */ public void visitAnnotableParameterCount(final int parameterCount, final boolean visible) { @@ -197,8 +213,8 @@ public void visitAnnotableParameterCount(final int parameterCount, final boolean * descriptor, in particular in case of synthetic parameters (see * https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.18). * @param descriptor the class descriptor of the annotation class. - * @param visible true if the annotation is visible at runtime. - * @return a visitor to visit the annotation values, or null if this visitor is not + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not * interested in visiting this annotation. */ public AnnotationVisitor visitParameterAnnotation( @@ -248,15 +264,15 @@ public void visitCode() { *

  • {@link Opcodes#F_SAME} representing frame with exactly the same locals as the * previous frame and with the empty stack. *
  • {@link Opcodes#F_SAME1} representing frame with exactly the same locals as the - * previous frame and with single value on the stack ( nStack is 1 and + * previous frame and with single value on the stack ( numStack is 1 and * stack[0] contains value for the type of the stack item). *
  • {@link Opcodes#F_APPEND} representing frame with current locals are the same as the * locals in the previous frame, except that additional locals are defined ( - * nLocal is 1, 2 or 3 and local elements contains values + * numLocal is 1, 2 or 3 and local elements contains values * representing added types). *
  • {@link Opcodes#F_CHOP} representing frame with current locals are the same as the * locals in the previous frame, except that the last 1-3 locals are absent and with - * the empty stack (nLocals is 1, 2 or 3). + * the empty stack (numLocal is 1, 2 or 3). *
  • {@link Opcodes#F_FULL} representing complete frame data. *
* @@ -269,15 +285,17 @@ public void visitCode() { * @param type the type of this stack map frame. Must be {@link Opcodes#F_NEW} for expanded * frames, or {@link Opcodes#F_FULL}, {@link Opcodes#F_APPEND}, {@link Opcodes#F_CHOP}, {@link * Opcodes#F_SAME} or {@link Opcodes#F_APPEND}, {@link Opcodes#F_SAME1} for compressed frames. - * @param nLocal the number of local variables in the visited frame. + * @param numLocal the number of local variables in the visited frame. Long and double values + * count for one variable. * @param local the local variable types in this frame. This array must not be modified. Primitive * types are represented by {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, {@link * Opcodes#FLOAT}, {@link Opcodes#LONG}, {@link Opcodes#DOUBLE}, {@link Opcodes#NULL} or * {@link Opcodes#UNINITIALIZED_THIS} (long and double are represented by a single element). - * Reference types are represented by String objects (representing internal names), and - * uninitialized types by Label objects (this label designates the NEW instruction that - * created this uninitialized value). - * @param nStack the number of operand stack elements in the visited frame. + * Reference types are represented by String objects (representing internal names, see {@link + * Type#getInternalName()}), and uninitialized types by Label objects (this label designates + * the NEW instruction that created this uninitialized value). + * @param numStack the number of operand stack elements in the visited frame. Long and double + * values count for one stack element. * @param stack the operand stack types in this frame. This array must not be modified. Its * content has the same format as the "local" array. * @throws IllegalStateException if a frame is visited just after another one, without any @@ -286,12 +304,12 @@ public void visitCode() { */ public void visitFrame( final int type, - final int nLocal, + final int numLocal, final Object[] local, - final int nStack, + final int numStack, final Object[] stack) { if (mv != null) { - mv.visitFrame(type, nLocal, local, nStack, stack); + mv.visitFrame(type, numLocal, local, numStack, stack); } } @@ -345,18 +363,18 @@ public void visitIntInsn(final int opcode, final int operand) { * * @param opcode the opcode of the local variable instruction to be visited. This opcode is either * ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET. - * @param var the operand of the instruction to be visited. This operand is the index of a local - * variable. + * @param varIndex the operand of the instruction to be visited. This operand is the index of a + * local variable. */ - public void visitVarInsn(final int opcode, final int var) { + public void visitVarInsn(final int opcode, final int varIndex) { if (mv != null) { - mv.visitVarInsn(opcode, var); + mv.visitVarInsn(opcode, varIndex); } } /** * Visits a type instruction. A type instruction is an instruction that takes the internal name of - * a class as parameter. + * a class as parameter (see {@link Type#getInternalName()}). * * @param opcode the opcode of the type instruction to be visited. This opcode is either NEW, * ANEWARRAY, CHECKCAST or INSTANCEOF. @@ -395,19 +413,13 @@ public void visitFieldInsn( * Type#getInternalName()}). * @param name the method's name. * @param descriptor the method's descriptor (see {@link Type}). - * @deprecated + * @deprecated use {@link #visitMethodInsn(int, String, String, String, boolean)} instead. */ @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); } /** @@ -427,15 +439,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); } } @@ -523,7 +535,7 @@ public void visitLabel(final Label label) { * } * } else if (cst instanceof Handle) { * // ... - * } else if (cst instanceof Condy) { + * } else if (cst instanceof ConstantDynamic) { * // ... * } else { * // throw an exception @@ -532,7 +544,7 @@ public void visitLabel(final Label label) { * * @param value the constant to be loaded on the stack. This parameter must be a non null {@link * Integer}, a {@link Float}, a {@link Long}, a {@link Double}, a {@link String}, a {@link - * Type} of OBJECT or ARRAY sort for .class constants, for classes whose version is + * Type} of OBJECT or ARRAY sort for {@code .class} constants, for classes whose version is * 49, a {@link Type} of METHOD sort for MethodType, a {@link Handle} for MethodHandle * constants, for classes whose version is 51 or a {@link ConstantDynamic} for a constant * dynamic for classes whose version is 55. @@ -543,7 +555,7 @@ public void visitLdcInsn(final Object value) { || (value instanceof Type && ((Type) value).getSort() == Type.METHOD))) { throw new UnsupportedOperationException(REQUIRES_ASM5); } - if (api != Opcodes.ASM7_EXPERIMENTAL && value instanceof ConstantDynamic) { + if (api < Opcodes.ASM7 && value instanceof ConstantDynamic) { throw new UnsupportedOperationException("This feature requires ASM7"); } if (mv != null) { @@ -554,12 +566,12 @@ public void visitLdcInsn(final Object value) { /** * Visits an IINC instruction. * - * @param var index of the local variable to be incremented. + * @param varIndex index of the local variable to be incremented. * @param increment amount to increment the local variable by. */ - public void visitIincInsn(final int var, final int increment) { + public void visitIincInsn(final int varIndex, final int increment) { if (mv != null) { - mv.visitIincInsn(var, increment); + mv.visitIincInsn(varIndex, increment); } } @@ -569,8 +581,8 @@ public void visitIincInsn(final int var, final int increment) { * @param min the minimum key value. * @param max the maximum key value. * @param dflt beginning of the default handler block. - * @param labels beginnings of the handler blocks. labels[i] is the beginning of the - * handler block for the min + i key. + * @param labels beginnings of the handler blocks. {@code labels[i]} is the beginning of the + * handler block for the {@code min + i} key. */ public void visitTableSwitchInsn( final int min, final int max, final Label dflt, final Label... labels) { @@ -584,8 +596,8 @@ public void visitTableSwitchInsn( * * @param dflt beginning of the default handler block. * @param keys the values of the keys. - * @param labels beginnings of the handler blocks. labels[i] is the beginning of the - * handler block for the keys[i] key. + * @param labels beginnings of the handler blocks. {@code labels[i]} is the beginning of the + * handler block for the {@code keys[i]} key. */ public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) { if (mv != null) { @@ -617,11 +629,11 @@ public void visitMultiANewArrayInsn(final String descriptor, final int numDimens * TypeReference#CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or {@link * TypeReference#METHOD_REFERENCE_TYPE_ARGUMENT}. 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 null if the annotation targets + * static inner type within 'typeRef'. May be {@literal null} if the annotation targets * 'typeRef' as a whole. * @param descriptor the class descriptor of the annotation class. - * @param visible true if the annotation is visible at runtime. - * @return a visitor to visit the annotation values, or null if this visitor is not + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not * interested in visiting this annotation. */ public AnnotationVisitor visitInsnAnnotation( @@ -645,8 +657,9 @@ public AnnotationVisitor visitInsnAnnotation( * @param start the beginning of the exception handler's scope (inclusive). * @param end the end of the exception handler's scope (exclusive). * @param handler the beginning of the exception handler's code. - * @param type the internal name of the type of exceptions handled by the handler, or - * null to catch any exceptions (for "finally" blocks). + * @param type the internal name of the type of exceptions handled by the handler (see {@link + * Type#getInternalName()}), or {@literal null} to catch any exceptions (for "finally" + * blocks). * @throws IllegalArgumentException if one of the labels has already been visited by this visitor * (by the {@link #visitLabel} method). */ @@ -665,11 +678,11 @@ public void visitTryCatchBlock( * @param typeRef a reference to the annotated type. The sort of this type reference must be * {@link TypeReference#EXCEPTION_PARAMETER}. 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 null if the annotation targets + * static inner type within 'typeRef'. May be {@literal null} if the annotation targets * 'typeRef' as a whole. * @param descriptor the class descriptor of the annotation class. - * @param visible true if the annotation is visible at runtime. - * @return a visitor to visit the annotation values, or null if this visitor is not + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not * interested in visiting this annotation. */ public AnnotationVisitor visitTryCatchAnnotation( @@ -688,7 +701,7 @@ public AnnotationVisitor visitTryCatchAnnotation( * * @param name the name of a local variable. * @param descriptor the type descriptor of this local variable. - * @param signature the type signature of this local variable. May be null if the local + * @param signature the type signature of this local variable. May be {@literal null} if the local * variable type does not use generic types. * @param start the first instruction corresponding to the scope of this local variable * (inclusive). @@ -716,7 +729,7 @@ public void visitLocalVariable( * {@link TypeReference#LOCAL_VARIABLE} or {@link TypeReference#RESOURCE_VARIABLE}. 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 null if the annotation targets + * static inner type within 'typeRef'. May be {@literal null} if the annotation targets * 'typeRef' as a whole. * @param start the fist instructions corresponding to the continuous ranges that make the scope * of this local variable (inclusive). @@ -725,8 +738,8 @@ public void visitLocalVariable( * @param index the local variable's index in each range. This array must have the same size as * the 'start' array. * @param descriptor the class descriptor of the annotation class. - * @param visible true if the annotation is visible at runtime. - * @return a visitor to visit the annotation values, or null if this visitor is not + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not * interested in visiting this annotation. */ public AnnotationVisitor visitLocalVariableAnnotation( @@ -753,7 +766,7 @@ public AnnotationVisitor visitLocalVariableAnnotation( * @param line a line number. This number refers to the source file from which the class was * compiled. * @param start the first instruction corresponding to this line number. - * @throws IllegalArgumentException if start has not already been visited by this visitor + * @throws IllegalArgumentException if {@code start} has not already been visited by this visitor * (by the {@link #visitLabel} method). */ public void visitLineNumber(final int line, final Label start) { diff --git a/src/jvm/clojure/asm/MethodWriter.java b/src/jvm/clojure/asm/MethodWriter.java index 2ced84a191..bdb1040acf 100644 --- a/src/jvm/clojure/asm/MethodWriter.java +++ b/src/jvm/clojure/asm/MethodWriter.java @@ -299,6 +299,9 @@ final class MethodWriter extends MethodVisitor { /** The name_index field of the method_info JVMS structure. */ private final int nameIndex; + /** The name of this method. */ + private final String name; + /** The descriptor_index field of the method_info JVMS structure. */ private final int descriptorIndex; @@ -319,35 +322,37 @@ final class MethodWriter extends MethodVisitor { /** * The first element in the exception handler list (used to generate the exception_table of the * Code attribute). The next ones can be accessed with the {@link Handler#nextHandler} field. May - * be null. + * be {@literal null}. */ private Handler firstHandler; /** * The last element in the exception handler list (used to generate the exception_table of the * Code attribute). The next ones can be accessed with the {@link Handler#nextHandler} field. May - * be null. + * be {@literal null}. */ private Handler lastHandler; /** The line_number_table_length field of the LineNumberTable code attribute. */ private int lineNumberTableLength; - /** The line_number_table array of the LineNumberTable code attribute, or null. */ + /** The line_number_table array of the LineNumberTable code attribute, or {@literal null}. */ private ByteVector lineNumberTable; /** The local_variable_table_length field of the LocalVariableTable code attribute. */ private int localVariableTableLength; - /** The local_variable_table array of the LocalVariableTable code attribute, or null. */ + /** + * The local_variable_table array of the LocalVariableTable code attribute, or {@literal null}. + */ private ByteVector localVariableTable; /** The local_variable_type_table_length field of the LocalVariableTypeTable code attribute. */ private int localVariableTypeTableLength; /** - * The local_variable_type_table array of the LocalVariableTypeTable code attribute, or - * null. + * The local_variable_type_table array of the LocalVariableTypeTable code attribute, or {@literal + * null}. */ private ByteVector localVariableTypeTable; @@ -359,19 +364,19 @@ final class MethodWriter extends MethodVisitor { /** * The last runtime visible type annotation of the Code attribute. The previous ones can be - * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be null. + * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. */ private AnnotationWriter lastCodeRuntimeVisibleTypeAnnotation; /** * The last runtime invisible type annotation of the Code attribute. The previous ones can be - * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be null. + * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. */ private AnnotationWriter lastCodeRuntimeInvisibleTypeAnnotation; /** * The first non standard attribute of the Code attribute. The next ones can be accessed with the - * {@link Attribute#nextAttribute} field. May be null. + * {@link Attribute#nextAttribute} field. May be {@literal null}. * *

WARNING: this list stores the attributes in the reverse order of their visit. * firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link @@ -385,7 +390,7 @@ final class MethodWriter extends MethodVisitor { /** The number_of_exceptions field of the Exceptions attribute. */ private final int numberOfExceptions; - /** The exception_index_table array of the Exceptions attribute, or null. */ + /** The exception_index_table array of the Exceptions attribute, or {@literal null}. */ private final int[] exceptionIndexTable; /** The signature_index field of the Signature attribute. */ @@ -393,13 +398,13 @@ final class MethodWriter extends MethodVisitor { /** * The last runtime visible annotation of this method. The previous ones can be accessed with the - * {@link AnnotationWriter#previousAnnotation} field. May be null. + * {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. */ private AnnotationWriter lastRuntimeVisibleAnnotation; /** * The last runtime invisible annotation of this method. The previous ones can be accessed with - * the {@link AnnotationWriter#previousAnnotation} field. May be null. + * the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. */ private AnnotationWriter lastRuntimeInvisibleAnnotation; @@ -408,8 +413,8 @@ final class MethodWriter extends MethodVisitor { /** * The runtime visible parameter annotations of this method. Each array element contains the last - * annotation of a parameter (which can be null - the previous ones can be accessed with - * the {@link AnnotationWriter#previousAnnotation} field). May be null. + * annotation of a parameter (which can be {@literal null} - the previous ones can be accessed + * with the {@link AnnotationWriter#previousAnnotation} field). May be {@literal null}. */ private AnnotationWriter[] lastRuntimeVisibleParameterAnnotations; @@ -418,35 +423,35 @@ final class MethodWriter extends MethodVisitor { /** * The runtime invisible parameter annotations of this method. Each array element contains the - * last annotation of a parameter (which can be null - the previous ones can be accessed - * with the {@link AnnotationWriter#previousAnnotation} field). May be null. + * last annotation of a parameter (which can be {@literal null} - the previous ones can be + * accessed with the {@link AnnotationWriter#previousAnnotation} field). May be {@literal null}. */ private AnnotationWriter[] lastRuntimeInvisibleParameterAnnotations; /** * The last runtime visible type annotation of this method. The previous ones can be accessed with - * the {@link AnnotationWriter#previousAnnotation} field. May be null. + * the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. */ private AnnotationWriter lastRuntimeVisibleTypeAnnotation; /** * The last runtime invisible type annotation of this method. The previous ones can be accessed - * with the {@link AnnotationWriter#previousAnnotation} field. May be null. + * with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. */ private AnnotationWriter lastRuntimeInvisibleTypeAnnotation; - /** The default_value field of the AnnotationDefault attribute, or null. */ + /** The default_value field of the AnnotationDefault attribute, or {@literal null}. */ private ByteVector defaultValue; /** The parameters_count field of the MethodParameters attribute. */ private int parametersCount; - /** The 'parameters' array of the MethodParameters attribute, or null. */ + /** The 'parameters' array of the MethodParameters attribute, or {@literal null}. */ private ByteVector parameters; /** * The first non standard attribute of this method. The next ones can be accessed with the {@link - * Attribute#nextAttribute} field. May be null. + * Attribute#nextAttribute} field. May be {@literal null}. * *

WARNING: this list stores the attributes in the reverse order of their visit. * firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link @@ -461,7 +466,8 @@ final class MethodWriter extends MethodVisitor { /** * Indicates what must be computed. Must be one of {@link #COMPUTE_ALL_FRAMES}, {@link - * #COMPUTE_INSERTED_FRAMES}, {@link #COMPUTE_MAX_STACK_AND_LOCAL} or {@link #COMPUTE_NOTHING}. + * #COMPUTE_INSERTED_FRAMES}, {@link COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES}, {@link + * #COMPUTE_MAX_STACK_AND_LOCAL} or {@link #COMPUTE_NOTHING}. */ private final int compute; @@ -480,7 +486,7 @@ final class MethodWriter extends MethodVisitor { /** * The current basic block, i.e. the basic block of the last visited instruction. When {@link * #compute} is equal to {@link #COMPUTE_MAX_STACK_AND_LOCAL} or {@link #COMPUTE_ALL_FRAMES}, this - * field is null for unreachable code. When {@link #compute} is equal to {@link + * field is {@literal null} for unreachable code. When {@link #compute} is equal to {@link * #COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES} or {@link #COMPUTE_INSERTED_FRAMES}, this field stays * unchanged throughout the whole method (i.e. the whole code is seen as a single basic block; * indeed, the existing frames are sufficient by hypothesis to compute any intermediate frame - @@ -526,11 +532,11 @@ final class MethodWriter extends MethodVisitor { * The current stack map frame. The first element contains the bytecode offset of the instruction * to which the frame corresponds, the second element is the number of locals and the third one is * the number of stack elements. The local variables start at index 3 and are followed by the - * operand stack elements. In summary frame[0] = offset, frame[1] = nLocal, frame[2] = nStack, - * frame[3] = nLocal. Local variables and operand stack entries contain abstract types, as defined - * in {@link Frame}, but restricted to {@link Frame#CONSTANT_KIND}, {@link Frame#REFERENCE_KIND} - * or {@link Frame#UNINITIALIZED_KIND} abstract types. Long and double types use only one array - * entry. + * operand stack elements. In summary frame[0] = offset, frame[1] = numLocal, frame[2] = numStack. + * Local variables and operand stack entries contain abstract types, as defined in {@link Frame}, + * but restricted to {@link Frame#CONSTANT_KIND}, {@link Frame#REFERENCE_KIND}, {@link + * Frame#UNINITIALIZED_KIND} or {@link Frame#FORWARD_UNINITIALIZED_KIND} abstract types. Long and + * double types use only one array entry. */ private int[] currentFrame; @@ -576,8 +582,8 @@ final class MethodWriter extends MethodVisitor { * @param access the method's access flags (see {@link Opcodes}). * @param name the method's name. * @param descriptor the method's descriptor (see {@link Type}). - * @param signature the method's signature. May be null. - * @param exceptions the internal names of the method's exceptions. May be null. + * @param signature the method's signature. May be {@literal null}. + * @param exceptions the internal names of the method's exceptions. May be {@literal null}. * @param compute indicates what must be computed (see #compute). */ MethodWriter( @@ -588,10 +594,11 @@ final class MethodWriter extends MethodVisitor { final String signature, final String[] exceptions, final int compute) { - super(Opcodes.ASM6); + super(/* latest api = */ Opcodes.ASM9); this.symbolTable = symbolTable; this.accessFlags = "".equals(name) ? access | Constants.ACC_CONSTRUCTOR : access; this.nameIndex = symbolTable.addConstantUtf8(name); + this.name = name; this.descriptorIndex = symbolTable.addConstantUtf8(descriptor); this.descriptor = descriptor; this.signatureIndex = signature == null ? 0 : symbolTable.addConstantUtf8(signature); @@ -644,42 +651,31 @@ public void visitParameter(final String name, final int access) { @Override public AnnotationVisitor visitAnnotationDefault() { defaultValue = new ByteVector(); - return new AnnotationWriter(symbolTable, /* useNamedValues = */ false, defaultValue, null); + return new AnnotationWriter(symbolTable, /* useNamedValues= */ false, defaultValue, null); } @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); } } @@ -695,27 +691,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]; + new AnnotationWriter[Type.getArgumentCount(descriptor)]; } 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]; + new AnnotationWriter[Type.getArgumentCount(descriptor)]; } return lastRuntimeInvisibleParameterAnnotations[parameter] = - new AnnotationWriter( - symbolTable, annotation, lastRuntimeInvisibleParameterAnnotations[parameter]); + AnnotationWriter.create( + symbolTable, + annotationDescriptor, + lastRuntimeInvisibleParameterAnnotations[parameter]); } } @@ -739,9 +732,9 @@ public void visitCode() { @Override public void visitFrame( final int type, - final int nLocal, + final int numLocal, final Object[] local, - final int nStack, + final int numStack, final Object[] stack) { if (compute == COMPUTE_ALL_FRAMES) { return; @@ -754,17 +747,16 @@ public void visitFrame( // can't be set if EXPAND_ASM_INSNS is not used). currentBasicBlock.frame = new CurrentFrame(currentBasicBlock); currentBasicBlock.frame.setInputFrameFromDescriptor( - symbolTable, accessFlags, descriptor, nLocal); + symbolTable, accessFlags, descriptor, numLocal); currentBasicBlock.frame.accept(this); } else { if (type == Opcodes.F_NEW) { currentBasicBlock.frame.setInputFrameFromApiFormat( - symbolTable, nLocal, local, nStack, stack); - } else { - // In this case type is equal to F_INSERT by hypothesis, and currentBlock.frame contains - // the stack map frame at the current instruction, computed from the last F_NEW frame - // and the bytecode instructions in between (via calls to CurrentFrame#execute). + symbolTable, numLocal, local, numStack, stack); } + // If type is not F_NEW then it is F_INSERT by hypothesis, and currentBlock.frame contains + // the stack map frame at the current instruction, computed from the last F_NEW frame and + // the bytecode instructions in between (via calls to CurrentFrame#execute). currentBasicBlock.frame.accept(this); } } else if (type == Opcodes.F_NEW) { @@ -775,16 +767,19 @@ public void visitFrame( symbolTable, accessFlags, descriptor, argumentsSize); implicitFirstFrame.accept(this); } - currentLocals = nLocal; - int frameIndex = visitFrameStart(code.length, nLocal, nStack); - for (int i = 0; i < nLocal; ++i) { + currentLocals = numLocal; + int frameIndex = visitFrameStart(code.length, numLocal, numStack); + for (int i = 0; i < numLocal; ++i) { currentFrame[frameIndex++] = Frame.getAbstractTypeFromApiFormat(symbolTable, local[i]); } - for (int i = 0; i < nStack; ++i) { + for (int i = 0; i < numStack; ++i) { currentFrame[frameIndex++] = Frame.getAbstractTypeFromApiFormat(symbolTable, stack[i]); } visitFrameEnd(); } else { + if (symbolTable.getMajorVersion() < Opcodes.V1_6) { + throw new IllegalArgumentException("Class versions V1_5 or less must use F_NEW frames."); + } int offsetDelta; if (stackMapTableEntries == null) { stackMapTableEntries = new ByteVector(); @@ -802,26 +797,26 @@ public void visitFrame( switch (type) { case Opcodes.F_FULL: - currentLocals = nLocal; - stackMapTableEntries.putByte(Frame.FULL_FRAME).putShort(offsetDelta).putShort(nLocal); - for (int i = 0; i < nLocal; ++i) { + currentLocals = numLocal; + stackMapTableEntries.putByte(Frame.FULL_FRAME).putShort(offsetDelta).putShort(numLocal); + for (int i = 0; i < numLocal; ++i) { putFrameType(local[i]); } - stackMapTableEntries.putShort(nStack); - for (int i = 0; i < nStack; ++i) { + stackMapTableEntries.putShort(numStack); + for (int i = 0; i < numStack; ++i) { putFrameType(stack[i]); } break; case Opcodes.F_APPEND: - currentLocals += nLocal; - stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED + nLocal).putShort(offsetDelta); - for (int i = 0; i < nLocal; ++i) { + currentLocals += numLocal; + stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED + numLocal).putShort(offsetDelta); + for (int i = 0; i < numLocal; ++i) { putFrameType(local[i]); } break; case Opcodes.F_CHOP: - currentLocals -= nLocal; - stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED - nLocal).putShort(offsetDelta); + currentLocals -= numLocal; + stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED - numLocal).putShort(offsetDelta); break; case Opcodes.F_SAME: if (offsetDelta < 64) { @@ -849,13 +844,18 @@ public void visitFrame( } if (compute == COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES) { - relativeStackSize = nStack; - if (nStack > maxRelativeStackSize) { + relativeStackSize = numStack; + for (int i = 0; i < numStack; ++i) { + if (stack[i] == Opcodes.LONG || stack[i] == Opcodes.DOUBLE) { + relativeStackSize++; + } + } + if (relativeStackSize > maxRelativeStackSize) { maxRelativeStackSize = relativeStackSize; } } - maxStack = Math.max(maxStack, nStack); + maxStack = Math.max(maxStack, numStack); maxLocals = Math.max(maxLocals, currentLocals); } @@ -906,26 +906,26 @@ public void visitIntInsn(final int opcode, final int operand) { } @Override - public void visitVarInsn(final int opcode, final int var) { + public void visitVarInsn(final int opcode, final int varIndex) { lastBytecodeOffset = code.length; // Add the instruction to the bytecode of the method. - if (var < 4 && opcode != Opcodes.RET) { + if (varIndex < 4 && opcode != Opcodes.RET) { int optimizedOpcode; if (opcode < Opcodes.ISTORE) { - optimizedOpcode = Constants.ILOAD_0 + ((opcode - Opcodes.ILOAD) << 2) + var; + optimizedOpcode = Constants.ILOAD_0 + ((opcode - Opcodes.ILOAD) << 2) + varIndex; } else { - optimizedOpcode = Constants.ISTORE_0 + ((opcode - Opcodes.ISTORE) << 2) + var; + optimizedOpcode = Constants.ISTORE_0 + ((opcode - Opcodes.ISTORE) << 2) + varIndex; } code.putByte(optimizedOpcode); - } else if (var >= 256) { - code.putByte(Constants.WIDE).put12(opcode, var); + } else if (varIndex >= 256) { + code.putByte(Constants.WIDE).put12(opcode, varIndex); } else { - code.put11(opcode, var); + code.put11(opcode, varIndex); } // If needed, update the maximum stack size and number of locals, and stack map frames. if (currentBasicBlock != null) { if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { - currentBasicBlock.frame.execute(opcode, var, null, null); + currentBasicBlock.frame.execute(opcode, varIndex, null, null); } else { if (opcode == Opcodes.RET) { // No stack size delta. @@ -947,9 +947,9 @@ public void visitVarInsn(final int opcode, final int var) { || opcode == Opcodes.DLOAD || opcode == Opcodes.LSTORE || opcode == Opcodes.DSTORE) { - currentMaxLocals = var + 2; + currentMaxLocals = varIndex + 2; } else { - currentMaxLocals = var + 1; + currentMaxLocals = varIndex + 1; } if (currentMaxLocals > maxLocals) { maxLocals = currentMaxLocals; @@ -1200,7 +1200,7 @@ public void visitJumpInsn(final int opcode, final Label label) { @Override public void visitLabel(final Label label) { // Resolve the forward references to this label, if any. - hasAsmInstructions |= label.resolve(code.data, code.length); + hasAsmInstructions |= label.resolve(code.data, stackMapTableEntries, code.length); // visitLabel starts a new basic block (except for debug only labels), so we need to update the // previous and current block references and list of successors. if ((label.flags & Label.FLAG_DEBUG_ONLY) != 0) { @@ -1280,9 +1280,13 @@ public void visitLdcInsn(final Object value) { // Add the instruction to the bytecode of the method. Symbol constantSymbol = symbolTable.addConstant(value); int constantIndex = constantSymbol.index; + char firstDescriptorChar; boolean isLongOrDouble = constantSymbol.tag == Symbol.CONSTANT_LONG_TAG - || constantSymbol.tag == Symbol.CONSTANT_DOUBLE_TAG; + || constantSymbol.tag == Symbol.CONSTANT_DOUBLE_TAG + || (constantSymbol.tag == Symbol.CONSTANT_DYNAMIC_TAG + && ((firstDescriptorChar = constantSymbol.value.charAt(0)) == 'J' + || firstDescriptorChar == 'D')); if (isLongOrDouble) { code.put12(Constants.LDC2_W, constantIndex); } else if (constantIndex >= 256) { @@ -1305,21 +1309,21 @@ public void visitLdcInsn(final Object value) { } @Override - public void visitIincInsn(final int var, final int increment) { + public void visitIincInsn(final int varIndex, final int increment) { lastBytecodeOffset = code.length; // Add the instruction to the bytecode of the method. - if ((var > 255) || (increment > 127) || (increment < -128)) { - code.putByte(Constants.WIDE).put12(Opcodes.IINC, var).putShort(increment); + if ((varIndex > 255) || (increment > 127) || (increment < -128)) { + code.putByte(Constants.WIDE).put12(Opcodes.IINC, varIndex).putShort(increment); } else { - code.putByte(Opcodes.IINC).put11(var, increment); + code.putByte(Opcodes.IINC).put11(varIndex, increment); } // If needed, update the maximum stack size and number of locals, and stack map frames. if (currentBasicBlock != null && (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES)) { - currentBasicBlock.frame.execute(Opcodes.IINC, var, null, null); + currentBasicBlock.frame.execute(Opcodes.IINC, varIndex, null, null); } if (compute != COMPUTE_NOTHING) { - int currentMaxLocals = var + 1; + int currentMaxLocals = varIndex + 1; if (currentMaxLocals > maxLocals) { maxLocals = currentMaxLocals; } @@ -1402,20 +1406,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); } } @@ -1436,20 +1442,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); } } @@ -1517,10 +1517,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); } } @@ -1634,7 +1642,7 @@ private void computeAllFrames() { code.data[endOffset] = (byte) Opcodes.ATHROW; // Emit a frame for this unreachable block, with no local and a Throwable on the stack // (so that the ATHROW could consume this Throwable if it were reachable). - int frameIndex = visitFrameStart(startOffset, /* nLocal = */ 0, /* nStack = */ 1); + int frameIndex = visitFrameStart(startOffset, /* numLocal= */ 0, /* numStack= */ 1); currentFrame[frameIndex] = Frame.getAbstractTypeFromInternalName(symbolTable, "java/lang/Throwable"); visitFrameEnd(); @@ -1788,7 +1796,7 @@ private void endCurrentBasicBlockWithNoSuccessor() { if (compute == COMPUTE_ALL_FRAMES) { Label nextBasicBlock = new Label(); nextBasicBlock.frame = new Frame(nextBasicBlock); - nextBasicBlock.resolve(code.data, code.length); + nextBasicBlock.resolve(code.data, stackMapTableEntries, code.length); lastBasicBlock.nextBasicBlock = nextBasicBlock; lastBasicBlock = nextBasicBlock; currentBasicBlock = null; @@ -1806,18 +1814,18 @@ private void endCurrentBasicBlockWithNoSuccessor() { * Starts the visit of a new stack map frame, stored in {@link #currentFrame}. * * @param offset the bytecode offset of the instruction to which the frame corresponds. - * @param nLocal the number of local variables in the frame. - * @param nStack the number of stack elements in the frame. + * @param numLocal the number of local variables in the frame. + * @param numStack the number of stack elements in the frame. * @return the index of the next element to be written in this frame. */ - int visitFrameStart(final int offset, final int nLocal, final int nStack) { - int frameLength = 3 + nLocal + nStack; + int visitFrameStart(final int offset, final int numLocal, final int numStack) { + int frameLength = 3 + numLocal + numStack; if (currentFrame == null || currentFrame.length < frameLength) { currentFrame = new int[frameLength]; } currentFrame[0] = offset; - currentFrame[1] = nLocal; - currentFrame[2] = nStack; + currentFrame[1] = numLocal; + currentFrame[2] = numStack; return 3; } @@ -1834,7 +1842,7 @@ void visitAbstractType(final int frameIndex, final int abstractType) { /** * Ends the visit of {@link #currentFrame} by writing it in the StackMapTable entries and by * updating the StackMapTable number_of_entries (except if the current frame is the first one, - * which is implicit in StackMapTable). Then resets {@link #currentFrame} to null. + * which is implicit in StackMapTable). Then resets {@link #currentFrame} to {@literal null}. */ void visitFrameEnd() { if (previousFrame != null) { @@ -1850,25 +1858,25 @@ void visitFrameEnd() { /** Compresses and writes {@link #currentFrame} in a new StackMapTable entry. */ private void putFrame() { - final int nLocal = currentFrame[1]; - final int nStack = currentFrame[2]; + final int numLocal = currentFrame[1]; + final int numStack = currentFrame[2]; if (symbolTable.getMajorVersion() < Opcodes.V1_6) { // Generate a StackMap attribute entry, which are always uncompressed. - stackMapTableEntries.putShort(currentFrame[0]).putShort(nLocal); - putAbstractTypes(3, 3 + nLocal); - stackMapTableEntries.putShort(nStack); - putAbstractTypes(3 + nLocal, 3 + nLocal + nStack); + stackMapTableEntries.putShort(currentFrame[0]).putShort(numLocal); + putAbstractTypes(3, 3 + numLocal); + stackMapTableEntries.putShort(numStack); + putAbstractTypes(3 + numLocal, 3 + numLocal + numStack); return; } final int offsetDelta = stackMapTableNumberOfEntries == 0 ? currentFrame[0] : currentFrame[0] - previousFrame[0] - 1; - final int previousNlocal = previousFrame[1]; - final int nLocalDelta = nLocal - previousNlocal; + final int previousNumlocal = previousFrame[1]; + final int numLocalDelta = numLocal - previousNumlocal; int type = Frame.FULL_FRAME; - if (nStack == 0) { - switch (nLocalDelta) { + if (numStack == 0) { + switch (numLocalDelta) { case -3: case -2: case -1: @@ -1886,7 +1894,7 @@ private void putFrame() { // Keep the FULL_FRAME type. break; } - } else if (nLocalDelta == 0 && nStack == 1) { + } else if (numLocalDelta == 0 && numStack == 1) { type = offsetDelta < 63 ? Frame.SAME_LOCALS_1_STACK_ITEM_FRAME @@ -1895,7 +1903,7 @@ private void putFrame() { if (type != Frame.FULL_FRAME) { // Verify if locals are the same as in the previous frame. int frameIndex = 3; - for (int i = 0; i < previousNlocal && i < nLocal; i++) { + for (int i = 0; i < previousNumlocal && i < numLocal; i++) { if (currentFrame[frameIndex] != previousFrame[frameIndex]) { type = Frame.FULL_FRAME; break; @@ -1909,30 +1917,35 @@ private void putFrame() { break; case Frame.SAME_LOCALS_1_STACK_ITEM_FRAME: stackMapTableEntries.putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME + offsetDelta); - putAbstractTypes(3 + nLocal, 4 + nLocal); + putAbstractTypes(3 + numLocal, 4 + numLocal); break; case Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED: stackMapTableEntries .putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) .putShort(offsetDelta); - putAbstractTypes(3 + nLocal, 4 + nLocal); + putAbstractTypes(3 + numLocal, 4 + numLocal); break; case Frame.SAME_FRAME_EXTENDED: stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED).putShort(offsetDelta); break; case Frame.CHOP_FRAME: - stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED + nLocalDelta).putShort(offsetDelta); + stackMapTableEntries + .putByte(Frame.SAME_FRAME_EXTENDED + numLocalDelta) + .putShort(offsetDelta); break; case Frame.APPEND_FRAME: - stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED + nLocalDelta).putShort(offsetDelta); - putAbstractTypes(3 + previousNlocal, 3 + nLocal); + stackMapTableEntries + .putByte(Frame.SAME_FRAME_EXTENDED + numLocalDelta) + .putShort(offsetDelta); + putAbstractTypes(3 + previousNumlocal, 3 + numLocal); break; case Frame.FULL_FRAME: default: - stackMapTableEntries.putByte(Frame.FULL_FRAME).putShort(offsetDelta).putShort(nLocal); - putAbstractTypes(3, 3 + nLocal); - stackMapTableEntries.putShort(nStack); - putAbstractTypes(3 + nLocal, 3 + nLocal + nStack); + stackMapTableEntries.putByte(Frame.FULL_FRAME).putShort(offsetDelta).putShort(numLocal); + putAbstractTypes(3, 3 + numLocal); + stackMapTableEntries.putShort(numStack); + putAbstractTypes(3 + numLocal, 3 + numLocal + numStack); + break; } } @@ -1967,9 +1980,8 @@ private void putFrameType(final Object type) { .putByte(Frame.ITEM_OBJECT) .putShort(symbolTable.addConstantClass((String) type).index); } else { - stackMapTableEntries - .putByte(Frame.ITEM_UNINITIALIZED) - .putShort(((Label) type).bytecodeOffset); + stackMapTableEntries.putByte(Frame.ITEM_UNINITIALIZED); + ((Label) type).put(stackMapTableEntries); } } @@ -1986,14 +1998,12 @@ 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 * of this method might be copied contains a Deprecated attribute. + * @param descriptorIndex the descriptor_index field of the method_info JVMS structure from which + * the attributes of this method might be copied. * @param signatureIndex the constant pool index contained in the Signature attribute of the * method_info JVMS structure from which the attributes of this method might be copied, or 0. * @param exceptionsOffset the offset in 'source.b' of the Exceptions attribute of the method_info @@ -2004,13 +2014,18 @@ 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, final int signatureIndex, final int exceptionsOffset) { + // If the method descriptor has changed, with more locals than the max_locals field of the + // original Code attribute, if any, then the original method attributes can't be copied. A + // conservative check on the descriptor changes alone ensures this (being more precise is not + // worth the additional complexity, because these cases should be rare -- if a transform changes + // a method descriptor, most of the time it needs to change the method's code too). if (source != symbolTable.getSource() + || descriptorIndex != this.descriptorIndex || signatureIndex != this.signatureIndex || hasDeprecatedAttribute != ((accessFlags & Opcodes.ACC_DEPRECATED) != 0)) { return false; @@ -2033,12 +2048,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; } /** @@ -2058,7 +2084,8 @@ int computeMethodInfoSize() { // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. if (code.length > 0) { if (code.length > 65535) { - throw new IndexOutOfBoundsException("Method code too large!"); + throw new MethodTooLargeException( + symbolTable.getClassName(), name, descriptor, code.length); } symbolTable.addConstantUtf8(Constants.CODE); // The Code attribute has 6 header bytes, plus 2, 2, 4 and 2 bytes respectively for max_stack, @@ -2105,29 +2132,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( @@ -2146,16 +2157,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; @@ -2183,7 +2184,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. @@ -2337,26 +2338,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), @@ -2375,14 +2364,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/src/jvm/clojure/asm/ModuleVisitor.java b/src/jvm/clojure/asm/ModuleVisitor.java index 26d2e365cd..e3dddf6f05 100644 --- a/src/jvm/clojure/asm/ModuleVisitor.java +++ b/src/jvm/clojure/asm/ModuleVisitor.java @@ -29,9 +29,8 @@ /** * A visitor to visit a Java module. The methods of this class must be called in the following - * order: visitMainClass | ( visitPackage | visitRequire | - * visitExport | visitOpen | visitUse | visitProvide )* - * visitEnd. + * order: ( {@code visitMainClass} | ( {@code visitPackage} | {@code visitRequire} | {@code + * visitExport} | {@code visitOpen} | {@code visitUse} | {@code visitProvide} )* ) {@code visitEnd}. * * @author Remi Forax * @author Eric Bruneton @@ -39,20 +38,22 @@ public abstract class ModuleVisitor { /** * The ASM API version implemented by this visitor. The value of this field must be one of {@link - * Opcodes#ASM6} or {@link Opcodes#ASM7_EXPERIMENTAL}. + * Opcodes#ASM6} or {@link Opcodes#ASM7}. */ 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; /** * Constructs a new {@link ModuleVisitor}. * * @param api the ASM API version implemented by this visitor. Must be one of {@link Opcodes#ASM6} - * or {@link Opcodes#ASM7_EXPERIMENTAL}. + * or {@link Opcodes#ASM7}. */ - public ModuleVisitor(final int api) { + protected ModuleVisitor(final int api) { this(api, null); } @@ -60,22 +61,42 @@ public ModuleVisitor(final int api) { * Constructs a new {@link ModuleVisitor}. * * @param api the ASM API version implemented by this visitor. Must be one of {@link Opcodes#ASM6} - * or {@link Opcodes#ASM7_EXPERIMENTAL}. + * or {@link Opcodes#ASM7}. * @param moduleVisitor the module visitor to which this visitor must delegate method calls. May * be null. */ - public ModuleVisitor(final int api, final ModuleVisitor moduleVisitor) { - if (api != Opcodes.ASM6 && api != Opcodes.ASM7_EXPERIMENTAL) { - throw new IllegalArgumentException(); + protected ModuleVisitor(final int api, final ModuleVisitor moduleVisitor) { + if (api != Opcodes.ASM9 + && api != Opcodes.ASM8 + && api != Opcodes.ASM7 + && api != Opcodes.ASM6 + && api != Opcodes.ASM5 + && api != Opcodes.ASM4 + && api != Opcodes.ASM10_EXPERIMENTAL) { + throw new IllegalArgumentException("Unsupported api " + api); + } + if (api == Opcodes.ASM10_EXPERIMENTAL) { + Constants.checkAsmExperimental(this); } this.api = api; this.mv = moduleVisitor; } + /** + * The module visitor to which this visitor must delegate method calls. May be {@literal null}. + * + * @return the module visitor to which this visitor must delegate method calls, or {@literal + * null}. + */ + public ModuleVisitor getDelegate() { + return mv; + } + /** * Visit the main class of the current module. * - * @param mainClass the internal name of the main class of the current module. + * @param mainClass the internal name of the main class of the current module (see {@link + * Type#getInternalName()}). */ public void visitMainClass(final String mainClass) { if (mv != null) { @@ -86,7 +107,7 @@ public void visitMainClass(final String mainClass) { /** * Visit a package of the current module. * - * @param packaze the internal name of a package. + * @param packaze the internal name of a package (see {@link Type#getInternalName()}). */ public void visitPackage(final String packaze) { if (mv != null) { @@ -100,7 +121,7 @@ public void visitPackage(final String packaze) { * @param module the fully qualified name (using dots) of the dependence. * @param access the access flag of the dependence among {@code ACC_TRANSITIVE}, {@code * ACC_STATIC_PHASE}, {@code ACC_SYNTHETIC} and {@code ACC_MANDATED}. - * @param version the module version at compile time, or null. + * @param version the module version at compile time, or {@literal null}. */ public void visitRequire(final String module, final int access, final String version) { if (mv != null) { @@ -111,11 +132,11 @@ public void visitRequire(final String module, final int access, final String ver /** * Visit an exported package of the current module. * - * @param packaze the internal name of the exported package. + * @param packaze the internal name of the exported package (see {@link Type#getInternalName()}). * @param access the access flag of the exported package, valid values are among {@code * ACC_SYNTHETIC} and {@code ACC_MANDATED}. * @param modules the fully qualified names (using dots) of the modules that can access the public - * classes of the exported package, or null. + * classes of the exported package, or {@literal null}. */ public void visitExport(final String packaze, final int access, final String... modules) { if (mv != null) { @@ -126,11 +147,11 @@ public void visitExport(final String packaze, final int access, final String... /** * Visit an open package of the current module. * - * @param packaze the internal name of the opened package. + * @param packaze the internal name of the opened package (see {@link Type#getInternalName()}). * @param access the access flag of the opened package, valid values are among {@code * ACC_SYNTHETIC} and {@code ACC_MANDATED}. * @param modules the fully qualified names (using dots) of the modules that can use deep - * reflection to the classes of the open package, or null. + * reflection to the classes of the open package, or {@literal null}. */ public void visitOpen(final String packaze, final int access, final String... modules) { if (mv != null) { @@ -142,7 +163,7 @@ public void visitOpen(final String packaze, final int access, final String... mo * Visit a service used by the current module. The name must be the internal name of an interface * or a class. * - * @param service the internal name of the service. + * @param service the internal name of the service (see {@link Type#getInternalName()}). */ public void visitUse(final String service) { if (mv != null) { @@ -153,9 +174,9 @@ public void visitUse(final String service) { /** * Visit an implementation of a service. * - * @param service the internal name of the service. - * @param providers the internal names of the implementations of the service (there is at least - * one provider). + * @param service the internal name of the service (see {@link Type#getInternalName()}). + * @param providers the internal names (see {@link Type#getInternalName()}) of the implementations + * of the service (there is at least one provider). */ public void visitProvide(final String service, final String... providers) { if (mv != null) { diff --git a/src/jvm/clojure/asm/ModuleWriter.java b/src/jvm/clojure/asm/ModuleWriter.java index 7eb7abda91..a2289c1665 100644 --- a/src/jvm/clojure/asm/ModuleWriter.java +++ b/src/jvm/clojure/asm/ModuleWriter.java @@ -94,7 +94,7 @@ final class ModuleWriter extends ModuleVisitor { private int mainClassIndex; ModuleWriter(final SymbolTable symbolTable, final int name, final int access, final int version) { - super(Opcodes.ASM6); + super(/* latest api = */ Opcodes.ASM9); this.symbolTable = symbolTable; this.moduleNameIndex = name; this.moduleFlags = access; diff --git a/src/jvm/clojure/asm/Opcodes.java b/src/jvm/clojure/asm/Opcodes.java index 5694eb3aa0..73d7d844e8 100644 --- a/src/jvm/clojure/asm/Opcodes.java +++ b/src/jvm/clojure/asm/Opcodes.java @@ -38,6 +38,7 @@ * @author Eric Bruneton * @author Eugene Kuleshov */ +// DontCheck(InterfaceIsType): can't be fixed (for backward binary compatibility). public interface Opcodes { // ASM API versions. @@ -45,17 +46,224 @@ public interface Opcodes { int ASM4 = 4 << 16 | 0 << 8; int ASM5 = 5 << 16 | 0 << 8; int ASM6 = 6 << 16 | 0 << 8; + int ASM7 = 7 << 16 | 0 << 8; + int ASM8 = 8 << 16 | 0 << 8; + int ASM9 = 9 << 16 | 0 << 8; /** - * Experimental, use at your own risk. This field will be renamed when it becomes stable, this - * will break existing code using it. + * Experimental, use at your own risk. This field will be renamed when it becomes stable, this + * will break existing code using it. Only code compiled with --enable-preview can use this. * * @deprecated This API is experimental. */ - @Deprecated int ASM7_EXPERIMENTAL = 1 << 24 | 7 << 16 | 0 << 8; + @Deprecated int ASM10_EXPERIMENTAL = 1 << 24 | 10 << 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, ...) {
+   *     visitNewStuff(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 visitOldStuff: 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; @@ -69,16 +277,26 @@ public interface Opcodes { int V9 = 0 << 16 | 53; int V10 = 0 << 16 | 54; int V11 = 0 << 16 | 55; + int V12 = 0 << 16 | 56; + int V13 = 0 << 16 | 57; + int V14 = 0 << 16 | 58; + int V15 = 0 << 16 | 59; + int V16 = 0 << 16 | 60; + int V17 = 0 << 16 | 61; + int V18 = 0 << 16 | 62; + int V19 = 0 << 16 | 63; + int V20 = 0 << 16 | 64; + int V21 = 0 << 16 | 65; + int V22 = 0 << 16 | 66; + int V23 = 0 << 16 | 67; /** * Version flag indicating that the class is using 'preview' features. * - *

{@code version & V_PREVIEW_EXPERIMENTAL == V_PREVIEW_EXPERIMENTAL} tests if a version is - * flagged with {@code V_PREVIEW_EXPERIMENTAL}. - * - * @deprecated This API is experimental. + *

{@code version & V_PREVIEW == V_PREVIEW} tests if a version is flagged with {@code + * V_PREVIEW}. */ - @Deprecated int V_PREVIEW_EXPERIMENTAL = 0xFFFF0000; + int V_PREVIEW = 0xFFFF0000; // Access flags values, defined in // - https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.1-200-E.1 @@ -107,7 +325,7 @@ public interface Opcodes { int ACC_SYNTHETIC = 0x1000; // class, field, method, parameter, module * int ACC_ANNOTATION = 0x2000; // class int ACC_ENUM = 0x4000; // class(?) field inner - int ACC_MANDATED = 0x8000; // parameter, module, module * + int ACC_MANDATED = 0x8000; // field, method, parameter, module, module * int ACC_MODULE = 0x8000; // class // ASM specific access flags. @@ -115,6 +333,7 @@ public interface Opcodes { // access flags, and also to make sure that these flags are automatically filtered out when // written in class files (because access flags are stored using 16 bits only). + int ACC_RECORD = 0x10000; // class int ACC_DEPRECATED = 0x20000; // class, field, method // Possible values for the type operand of the NEWARRAY instruction. diff --git a/src/jvm/clojure/asm/RecordComponentVisitor.java b/src/jvm/clojure/asm/RecordComponentVisitor.java new file mode 100644 index 0000000000..7251e0e7b9 --- /dev/null +++ b/src/jvm/clojure/asm/RecordComponentVisitor.java @@ -0,0 +1,153 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package clojure.asm; + +/** + * A visitor to visit a record component. The methods of this class must be called in the following + * order: ( {@code visitAnnotation} | {@code visitTypeAnnotation} | {@code visitAttribute} )* {@code + * visitEnd}. + * + * @author Remi Forax + * @author Eric Bruneton + */ +public abstract class RecordComponentVisitor { + /** + * The ASM API version implemented by this visitor. The value of this field must be one of {@link + * Opcodes#ASM8} or {@link Opcodes#ASM9}. + */ + protected final int api; + + /** + * The record visitor to which this visitor must delegate method calls. May be {@literal null}. + */ + protected RecordComponentVisitor delegate; + + /** + * Constructs a new {@link RecordComponentVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of {@link Opcodes#ASM8} + * or {@link Opcodes#ASM9}. + */ + protected RecordComponentVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link RecordComponentVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be {@link Opcodes#ASM8}. + * @param recordComponentVisitor the record component visitor to which this visitor must delegate + * method calls. May be null. + */ + protected RecordComponentVisitor( + final int api, final RecordComponentVisitor recordComponentVisitor) { + if (api != Opcodes.ASM9 + && api != Opcodes.ASM8 + && api != Opcodes.ASM7 + && api != Opcodes.ASM6 + && api != Opcodes.ASM5 + && api != Opcodes.ASM4 + && api != Opcodes.ASM10_EXPERIMENTAL) { + throw new IllegalArgumentException("Unsupported api " + api); + } + if (api == Opcodes.ASM10_EXPERIMENTAL) { + Constants.checkAsmExperimental(this); + } + this.api = api; + this.delegate = recordComponentVisitor; + } + + /** + * The record visitor to which this visitor must delegate method calls. May be {@literal null}. + * + * @return the record visitor to which this visitor must delegate method calls, or {@literal + * null}. + */ + public RecordComponentVisitor getDelegate() { + return delegate; + } + + /** + * Visits an annotation of the record component. + * + * @param descriptor the class descriptor of the annotation class. + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { + if (delegate != null) { + return delegate.visitAnnotation(descriptor, visible); + } + return null; + } + + /** + * Visits an annotation on a type in the record component signature. + * + * @param typeRef a reference to the annotated type. The sort of this type reference must be + * {@link TypeReference#CLASS_TYPE_PARAMETER}, {@link + * TypeReference#CLASS_TYPE_PARAMETER_BOUND} or {@link TypeReference#CLASS_EXTENDS}. See + * {@link TypeReference}. + * @param typePath the path to the annotated type argument, wildcard bound, array element type, or + * static inner type within 'typeRef'. May be {@literal null} if the annotation targets + * 'typeRef' as a whole. + * @param descriptor the class descriptor of the annotation class. + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitTypeAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (delegate != null) { + return delegate.visitTypeAnnotation(typeRef, typePath, descriptor, visible); + } + return null; + } + + /** + * Visits a non standard attribute of the record component. + * + * @param attribute an attribute. + */ + public void visitAttribute(final Attribute attribute) { + if (delegate != null) { + delegate.visitAttribute(attribute); + } + } + + /** + * Visits the end of the record component. This method, which is the last one to be called, is + * used to inform the visitor that everything have been visited. + */ + public void visitEnd() { + if (delegate != null) { + delegate.visitEnd(); + } + } +} diff --git a/src/jvm/clojure/asm/RecordComponentWriter.java b/src/jvm/clojure/asm/RecordComponentWriter.java new file mode 100644 index 0000000000..360b86019f --- /dev/null +++ b/src/jvm/clojure/asm/RecordComponentWriter.java @@ -0,0 +1,225 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package clojure.asm; + +final class RecordComponentWriter extends RecordComponentVisitor { + /** Where the constants used in this RecordComponentWriter must be stored. */ + private final SymbolTable symbolTable; + + // Note: fields are ordered as in the record_component_info structure, and those related to + // attributes are ordered as in Section 4.7 of the JVMS. + + /** The name_index field of the Record attribute. */ + private final int nameIndex; + + /** The descriptor_index field of the Record attribute. */ + private final int descriptorIndex; + + /** + * The signature_index field of the Signature attribute of this record component, or 0 if there is + * no Signature attribute. + */ + private int signatureIndex; + + /** + * The last runtime visible annotation of this record component. The previous ones can be accessed + * with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeVisibleAnnotation; + + /** + * The last runtime invisible annotation of this record component. The previous ones can be + * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeInvisibleAnnotation; + + /** + * The last runtime visible type annotation of this record component. The previous ones can be + * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeVisibleTypeAnnotation; + + /** + * The last runtime invisible type annotation of this record component. The previous ones can be + * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeInvisibleTypeAnnotation; + + /** + * The first non standard attribute of this record component. The next ones can be accessed with + * the {@link Attribute#nextAttribute} field. May be {@literal null}. + * + *

WARNING: this list stores the attributes in the reverse order of their visit. + * firstAttribute is actually the last attribute visited in {@link #visitAttribute(Attribute)}. + * The {@link #putRecordComponentInfo(ByteVector)} method writes the attributes in the order + * defined by this list, i.e. in the reverse order specified by the user. + */ + private Attribute firstAttribute; + + /** + * Constructs a new {@link RecordComponentWriter}. + * + * @param symbolTable where the constants used in this RecordComponentWriter must be stored. + * @param name the record component name. + * @param descriptor the record component descriptor (see {@link Type}). + * @param signature the record component signature. May be {@literal null}. + */ + RecordComponentWriter( + final SymbolTable symbolTable, + final String name, + final String descriptor, + final String signature) { + super(/* latest api = */ Opcodes.ASM9); + this.symbolTable = symbolTable; + this.nameIndex = symbolTable.addConstantUtf8(name); + this.descriptorIndex = symbolTable.addConstantUtf8(descriptor); + if (signature != null) { + this.signatureIndex = symbolTable.addConstantUtf8(signature); + } + } + + // ----------------------------------------------------------------------------------------------- + // Implementation of the FieldVisitor abstract class + // ----------------------------------------------------------------------------------------------- + + @Override + public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { + if (visible) { + return lastRuntimeVisibleAnnotation = + AnnotationWriter.create(symbolTable, descriptor, lastRuntimeVisibleAnnotation); + } else { + return lastRuntimeInvisibleAnnotation = + AnnotationWriter.create(symbolTable, descriptor, lastRuntimeInvisibleAnnotation); + } + } + + @Override + public AnnotationVisitor visitTypeAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (visible) { + return lastRuntimeVisibleTypeAnnotation = + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastRuntimeVisibleTypeAnnotation); + } else { + return lastRuntimeInvisibleTypeAnnotation = + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastRuntimeInvisibleTypeAnnotation); + } + } + + @Override + public void visitAttribute(final Attribute attribute) { + // Store the attributes in the reverse order of their visit by this method. + attribute.nextAttribute = firstAttribute; + firstAttribute = attribute; + } + + @Override + public void visitEnd() { + // Nothing to do. + } + + // ----------------------------------------------------------------------------------------------- + // Utility methods + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the size of the record component JVMS structure generated by this + * RecordComponentWriter. Also adds the names of the attributes of this record component in the + * constant pool. + * + * @return the size in bytes of the record_component_info of the Record attribute. + */ + int computeRecordComponentInfoSize() { + // name_index, descriptor_index and attributes_count fields use 6 bytes. + int size = 6; + size += Attribute.computeAttributesSize(symbolTable, 0, signatureIndex); + size += + AnnotationWriter.computeAnnotationsSize( + lastRuntimeVisibleAnnotation, + lastRuntimeInvisibleAnnotation, + lastRuntimeVisibleTypeAnnotation, + lastRuntimeInvisibleTypeAnnotation); + if (firstAttribute != null) { + size += firstAttribute.computeAttributesSize(symbolTable); + } + return size; + } + + /** + * Puts the content of the record component generated by this RecordComponentWriter into the given + * ByteVector. + * + * @param output where the record_component_info structure must be put. + */ + void putRecordComponentInfo(final ByteVector output) { + output.putShort(nameIndex).putShort(descriptorIndex); + // Compute and put the attributes_count field. + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. + int attributesCount = 0; + if (signatureIndex != 0) { + ++attributesCount; + } + if (lastRuntimeVisibleAnnotation != null) { + ++attributesCount; + } + if (lastRuntimeInvisibleAnnotation != null) { + ++attributesCount; + } + if (lastRuntimeVisibleTypeAnnotation != null) { + ++attributesCount; + } + if (lastRuntimeInvisibleTypeAnnotation != null) { + ++attributesCount; + } + if (firstAttribute != null) { + attributesCount += firstAttribute.getAttributeCount(); + } + output.putShort(attributesCount); + Attribute.putAttributes(symbolTable, 0, signatureIndex, output); + AnnotationWriter.putAnnotations( + symbolTable, + lastRuntimeVisibleAnnotation, + lastRuntimeInvisibleAnnotation, + lastRuntimeVisibleTypeAnnotation, + lastRuntimeInvisibleTypeAnnotation, + output); + if (firstAttribute != null) { + firstAttribute.putAttributes(symbolTable, output); + } + } + + /** + * Collects the attributes of this record component into the given set of attribute prototypes. + * + * @param attributePrototypes a set of attribute prototypes. + */ + final void collectAttributePrototypes(final Attribute.Set attributePrototypes) { + attributePrototypes.addAttributes(firstAttribute); + } +} diff --git a/src/jvm/clojure/asm/Symbol.java b/src/jvm/clojure/asm/Symbol.java index aa56a4d022..3b3022169d 100644 --- a/src/jvm/clojure/asm/Symbol.java +++ b/src/jvm/clojure/asm/Symbol.java @@ -103,12 +103,25 @@ abstract class Symbol { static final int TYPE_TAG = 128; /** - * The tag value of an {@link Frame#ITEM_UNINITIALIZED} type entry in the type table of a class. + * The tag value of an uninitialized type entry in the type table of a class. This type is used + * for the normal case where the NEW instruction is before the <init> constructor call (in + * bytecode offset order), i.e. when the label of the NEW instruction is resolved when the + * constructor call is visited. If the NEW instruction is after the constructor call, use the + * {@link #FORWARD_UNINITIALIZED_TYPE_TAG} tag value instead. */ static final int UNINITIALIZED_TYPE_TAG = 129; + /** + * The tag value of an uninitialized type entry in the type table of a class. This type is used + * for the unusual case where the NEW instruction is after the <init> constructor call (in + * bytecode offset order), i.e. when the label of the NEW instruction is not resolved when the + * constructor call is visited. If the NEW instruction is before the constructor call, use the + * {@link #UNINITIALIZED_TYPE_TAG} tag value instead. + */ + static final int FORWARD_UNINITIALIZED_TYPE_TAG = 130; + /** The tag value of a merged type entry in the (ASM specific) type table of a class. */ - static final int MERGED_TYPE_TAG = 130; + static final int MERGED_TYPE_TAG = 131; // Instance fields. @@ -151,9 +164,9 @@ abstract class Symbol { * #CONSTANT_INVOKE_DYNAMIC_TAG} symbols, *

  • an arbitrary string for {@link #CONSTANT_UTF8_TAG} and {@link #CONSTANT_STRING_TAG} * symbols, - *
  • an internal class name for {@link #CONSTANT_CLASS_TAG}, {@link #TYPE_TAG} and {@link - * #UNINITIALIZED_TYPE_TAG} symbols, - *
  • null for the other types of symbol. + *
  • an internal class name for {@link #CONSTANT_CLASS_TAG}, {@link #TYPE_TAG}, {@link + * #UNINITIALIZED_TYPE_TAG} and {@link #FORWARD_UNINITIALIZED_TYPE_TAG} symbols, + *
  • {@literal null} for the other types of symbol. * */ final String value; @@ -172,6 +185,9 @@ abstract class Symbol { * {@link #CONSTANT_DYNAMIC_TAG} or {@link #BOOTSTRAP_METHOD_TAG} symbols, *
  • the bytecode offset of the NEW instruction that created an {@link * Frame#ITEM_UNINITIALIZED} type for {@link #UNINITIALIZED_TYPE_TAG} symbols, + *
  • the index of the {@link Label} (in the {@link SymbolTable#labelTable} table) of the NEW + * instruction that created an {@link Frame#ITEM_UNINITIALIZED} type for {@link + * #FORWARD_UNINITIALIZED_TYPE_TAG} symbols, *
  • the indices (in the class' type table) of two {@link #TYPE_TAG} source types for {@link * #MERGED_TYPE_TAG} symbols, *
  • 0 for the other types of symbol. @@ -205,9 +221,10 @@ abstract class Symbol { * @param index the symbol index in the constant pool, in the BootstrapMethods attribute, or in * the (ASM specific) type table of a class (depending on 'tag'). * @param tag the symbol type. Must be one of the static tag values defined in this class. - * @param owner The internal name of the symbol's owner class. Maybe null. - * @param name The name of the symbol's corresponding class field or method. Maybe null. - * @param value The string value of this symbol. Maybe null. + * @param owner The internal name of the symbol's owner class. Maybe {@literal null}. + * @param name The name of the symbol's corresponding class field or method. Maybe {@literal + * null}. + * @param value The string value of this symbol. Maybe {@literal null}. * @param data The numeric value of this symbol. */ Symbol( @@ -226,6 +243,8 @@ abstract class Symbol { } /** + * Returns the result {@link Type#getArgumentsAndReturnSizes} on {@link #value}. + * * @return the result {@link Type#getArgumentsAndReturnSizes} on {@link #value} (memoized in * {@link #info} for efficiency). This should only be used for {@link * #CONSTANT_METHODREF_TAG}, {@link #CONSTANT_INTERFACE_METHODREF_TAG} and {@link diff --git a/src/jvm/clojure/asm/SymbolTable.java b/src/jvm/clojure/asm/SymbolTable.java index 30caf77af8..aaf0a15cc1 100644 --- a/src/jvm/clojure/asm/SymbolTable.java +++ b/src/jvm/clojure/asm/SymbolTable.java @@ -31,66 +31,14 @@ * The constant pool entries, the BootstrapMethods attribute entries and the (ASM specific) type * table entries of a class. * + * @author Eric Bruneton * @see JVMS * 4.4 * @see JVMS * 4.7.23 - * @author Eric Bruneton */ final class SymbolTable { - /** - * An entry of a SymbolTable. This concrete and private subclass of {@link Symbol} adds two fields - * which are only used inside SymbolTable, to implement hash sets of symbols (in order to avoid - * duplicate symbols). See {@link #entries}. - * - * @author Eric Bruneton - */ - private static class Entry extends Symbol { - - /** The hash code of this entry. */ - final int hashCode; - - /** - * Another entry (and so on recursively) having the same hash code (modulo the size of {@link - * #entries}) as this one. - */ - Entry next; - - Entry( - final int index, - final int tag, - final String owner, - final String name, - final String value, - final long data, - final int hashCode) { - super(index, tag, owner, name, value, data); - this.hashCode = hashCode; - } - - Entry(final int index, final int tag, final String value, final int hashCode) { - super(index, tag, /* owner = */ null, /* name = */ null, value, /* data = */ 0); - this.hashCode = hashCode; - } - - Entry(final int index, final int tag, final String value, final long data, final int hashCode) { - super(index, tag, /* owner = */ null, /* name = */ null, value, data); - this.hashCode = hashCode; - } - - Entry( - final int index, final int tag, final String name, final String value, final int hashCode) { - super(index, tag, /* owner = */ null, name, value, /* data = */ 0); - this.hashCode = hashCode; - } - - Entry(final int index, final int tag, final long data, final int hashCode) { - super(index, tag, /* owner = */ null, /* name = */ null, /* value = */ null, data); - this.hashCode = hashCode; - } - } - /** * The ClassWriter to which this SymbolTable belongs. This is only used to get access to {@link * ClassWriter#getCommonSuperClass} and to serialize custom attributes with {@link @@ -99,7 +47,7 @@ private static class Entry extends Symbol { final ClassWriter classWriter; /** - * The ClassReader from which this SymbolTable was constructed, or null if it was + * The ClassReader from which this SymbolTable was constructed, or {@literal null} if it was * constructed from scratch. */ private final ClassReader sourceClassReader; @@ -160,11 +108,35 @@ private static class Entry extends Symbol { * An ASM specific type table used to temporarily store internal names that will not necessarily * be stored in the constant pool. This type table is used by the control flow and data flow * analysis algorithm used to compute stack map frames from scratch. This array stores {@link - * Symbol#TYPE_TAG} and {@link Symbol#UNINITIALIZED_TYPE_TAG}) Symbol. The type symbol at index - * i has its {@link Symbol#index} equal to i (and vice versa). + * Symbol#TYPE_TAG}, {@link Symbol#UNINITIALIZED_TYPE_TAG},{@link + * Symbol#FORWARD_UNINITIALIZED_TYPE_TAG} and {@link Symbol#MERGED_TYPE_TAG} entries. The type + * symbol at index {@code i} has its {@link Symbol#index} equal to {@code i} (and vice versa). */ private Entry[] typeTable; + /** + * The actual number of {@link LabelEntry} in {@link #labelTable}. These elements are stored from + * index 0 to labelCount (excluded). The other array entries are empty. These label entries are + * also stored in the {@link #labelEntries} hash set. + */ + private int labelCount; + + /** + * The labels corresponding to the "forward uninitialized" types in the ASM specific {@link + * typeTable} (see {@link Symbol#FORWARD_UNINITIALIZED_TYPE_TAG}). The label entry at index {@code + * i} has its {@link LabelEntry#index} equal to {@code i} (and vice versa). + */ + private LabelEntry[] labelTable; + + /** + * A hash set of all the {@link LabelEntry} elements in the {@link #labelTable}. Each {@link + * LabelEntry} instance is stored at the array index given by its hash code modulo the array size. + * If several entries must be stored at the same array index, they are linked together via their + * {@link LabelEntry#next} field. The {@link #getOrAddLabelEntry(Label)} method ensures that this + * table does not contain duplicated entries. + */ + private LabelEntry[] labelEntries; + /** * Constructs a new, empty SymbolTable for the given ClassWriter. * @@ -191,7 +163,7 @@ private static class Entry extends Symbol { 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(); @@ -203,6 +175,7 @@ private static class Entry extends Symbol { // method calls below), and to account for bootstrap method entries. entries = new Entry[constantPoolCount * 2]; char[] charBuffer = new char[classReader.getMaxStringLength()]; + boolean hasBootstrapMethods = false; int itemIndex = 1; while (itemIndex < constantPoolCount) { int itemOffset = classReader.getItem(itemIndex); @@ -223,7 +196,7 @@ private static class Entry extends Symbol { break; case Symbol.CONSTANT_INTEGER_TAG: case Symbol.CONSTANT_FLOAT_TAG: - addConstantInteger(itemIndex, itemTag, classReader.readInt(itemOffset)); + addConstantIntegerOrFloat(itemIndex, itemTag, classReader.readInt(itemOffset)); break; case Symbol.CONSTANT_NAME_AND_TYPE_TAG: addConstantNameAndType( @@ -233,10 +206,10 @@ private static class Entry extends Symbol { break; case Symbol.CONSTANT_LONG_TAG: case Symbol.CONSTANT_DOUBLE_TAG: - addConstantLong(itemIndex, itemTag, classReader.readLong(itemOffset)); + addConstantLongOrDouble(itemIndex, itemTag, classReader.readLong(itemOffset)); break; case Symbol.CONSTANT_UTF8_TAG: - addConstantUtf8(itemIndex, classReader.readUTF(itemIndex, charBuffer)); + addConstantUtf8(itemIndex, classReader.readUtf(itemIndex, charBuffer)); break; case Symbol.CONSTANT_METHOD_HANDLE_TAG: int memberRefItemOffset = @@ -252,6 +225,7 @@ private static class Entry extends Symbol { break; case Symbol.CONSTANT_DYNAMIC_TAG: case Symbol.CONSTANT_INVOKE_DYNAMIC_TAG: + hasBootstrapMethods = true; nameAndTypeItemOffset = classReader.getItem(classReader.readUnsignedShort(itemOffset + 2)); addConstantDynamicOrInvokeDynamicReference( @@ -276,7 +250,23 @@ private static class Entry extends Symbol { (itemTag == Symbol.CONSTANT_LONG_TAG || itemTag == Symbol.CONSTANT_DOUBLE_TAG) ? 2 : 1; } - // Copy the BootstrapMethods 'bootstrap_methods' array binary content, if any. + // Copy the BootstrapMethods, if any. + if (hasBootstrapMethods) { + copyBootstrapMethods(classReader, charBuffer); + } + } + + /** + * Read the BootstrapMethods 'bootstrap_methods' array binary content and add them as entries of + * the SymbolTable. + * + * @param classReader the ClassReader whose bootstrap methods must be copied to initialize the + * SymbolTable. + * @param charBuffer a buffer used to read strings in the constant pool. + */ + private void copyBootstrapMethods(final ClassReader classReader, final char[] charBuffer) { + // Find attributOffset of the 'bootstrap_methods' array. + byte[] inputBytes = classReader.classFileBuffer; int currentAttributeOffset = classReader.getFirstAttributeOffset(); for (int i = classReader.readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) { String attributeName = classReader.readUTF8(currentAttributeOffset, charBuffer); @@ -313,19 +303,29 @@ private static class Entry extends Symbol { } /** - * @return the ClassReader from which this SymbolTable was constructed, or null if it was - * constructed from scratch. + * Returns the ClassReader from which this SymbolTable was constructed. + * + * @return the ClassReader from which this SymbolTable was constructed, or {@literal null} if it + * was constructed from scratch. */ ClassReader getSource() { return sourceClassReader; } - /** @return the major version of the class to which this symbol table belongs. */ + /** + * Returns the major version of the class to which this symbol table belongs. + * + * @return the major version of the class to which this symbol table belongs. + */ int getMajorVersion() { return majorVersion; } - /** @return the internal name of the class to which this symbol table belongs. */ + /** + * Returns the internal name of the class to which this symbol table belongs. + * + * @return the internal name of the class to which this symbol table belongs. + */ String getClassName() { return className; } @@ -344,12 +344,20 @@ int setMajorVersionAndClassName(final int majorVersion, final String className) return addConstantClass(className).index; } - /** @return the number of items in this symbol table's constant_pool array (plus 1). */ + /** + * Returns the number of items in this symbol table's constant_pool array (plus 1). + * + * @return the number of items in this symbol table's constant_pool array (plus 1). + */ int getConstantPoolCount() { return constantPoolCount; } - /** @return the length in bytes of this symbol table's constant_pool array. */ + /** + * Returns the length in bytes of this symbol table's constant_pool array. + * + * @return the length in bytes of this symbol table's constant_pool array. + */ int getConstantPoolLength() { return constantPool.length; } @@ -400,6 +408,8 @@ void putBootstrapMethods(final ByteVector output) { // ----------------------------------------------------------------------------------------------- /** + * Returns the list of entries which can potentially have the given hash code. + * * @param hashCode a {@link Entry#hashCode} value. * @return the list of entries which can potentially have the given hash code. The list is stored * via the {@link Entry#next} field. @@ -510,7 +520,7 @@ Symbol addConstant(final Object value) { constantDynamic.getName(), constantDynamic.getDescriptor(), constantDynamic.getBootstrapMethod(), - constantDynamic.getBootstrapMethodArguments()); + constantDynamic.getBootstrapMethodArgumentsUnsafe()); } else { throw new IllegalArgumentException("value " + value); } @@ -626,7 +636,7 @@ Symbol addConstantString(final String value) { * @return a new or already existing Symbol with the given value. */ Symbol addConstantInteger(final int value) { - return addConstantInteger(Symbol.CONSTANT_INTEGER_TAG, value); + return addConstantIntegerOrFloat(Symbol.CONSTANT_INTEGER_TAG, value); } /** @@ -637,7 +647,7 @@ Symbol addConstantInteger(final int value) { * @return a new or already existing Symbol with the given value. */ Symbol addConstantFloat(final float value) { - return addConstantInteger(Symbol.CONSTANT_FLOAT_TAG, Float.floatToRawIntBits(value)); + return addConstantIntegerOrFloat(Symbol.CONSTANT_FLOAT_TAG, Float.floatToRawIntBits(value)); } /** @@ -648,7 +658,7 @@ Symbol addConstantFloat(final float value) { * @param value an int or float. * @return a constant pool constant with the given tag and primitive values. */ - private Symbol addConstantInteger(final int tag, final int value) { + private Symbol addConstantIntegerOrFloat(final int tag, final int value) { int hashCode = hash(tag, value); Entry entry = get(hashCode); while (entry != null) { @@ -669,7 +679,7 @@ private Symbol addConstantInteger(final int tag, final int value) { * @param tag one of {@link Symbol#CONSTANT_INTEGER_TAG} or {@link Symbol#CONSTANT_FLOAT_TAG}. * @param value an int or float. */ - private void addConstantInteger(final int index, final int tag, final int value) { + private void addConstantIntegerOrFloat(final int index, final int tag, final int value) { add(new Entry(index, tag, value, hash(tag, value))); } @@ -681,7 +691,7 @@ private void addConstantInteger(final int index, final int tag, final int value) * @return a new or already existing Symbol with the given value. */ Symbol addConstantLong(final long value) { - return addConstantLong(Symbol.CONSTANT_LONG_TAG, value); + return addConstantLongOrDouble(Symbol.CONSTANT_LONG_TAG, value); } /** @@ -692,7 +702,7 @@ Symbol addConstantLong(final long value) { * @return a new or already existing Symbol with the given value. */ Symbol addConstantDouble(final double value) { - return addConstantLong(Symbol.CONSTANT_DOUBLE_TAG, Double.doubleToRawLongBits(value)); + return addConstantLongOrDouble(Symbol.CONSTANT_DOUBLE_TAG, Double.doubleToRawLongBits(value)); } /** @@ -703,7 +713,7 @@ Symbol addConstantDouble(final double value) { * @param value a long or double. * @return a constant pool constant with the given tag and primitive values. */ - private Symbol addConstantLong(final int tag, final long value) { + private Symbol addConstantLongOrDouble(final int tag, final long value) { int hashCode = hash(tag, value); Entry entry = get(hashCode); while (entry != null) { @@ -719,13 +729,14 @@ private Symbol addConstantLong(final int tag, final long value) { } /** - * Adds a new CONSTANT_Double_info to the constant pool of this symbol table. + * Adds a new CONSTANT_Long_info or CONSTANT_Double_info to the constant pool of this symbol + * table. * * @param index the constant pool index of the new Symbol. * @param tag one of {@link Symbol#CONSTANT_LONG_TAG} or {@link Symbol#CONSTANT_DOUBLE_TAG}. * @param value a long or double. */ - private void addConstantLong(final int index, final int tag, final long value) { + private void addConstantLongOrDouble(final int index, final int tag, final long value) { add(new Entry(index, tag, value, hash(tag, value))); } @@ -1059,8 +1070,10 @@ Symbol addBootstrapMethod( // bootstrap methods. We must therefore add the bootstrap method arguments to the constant pool // and BootstrapMethods attribute first, so that the BootstrapMethods attribute is not modified // while adding the given bootstrap method to it, in the rest of this method. - for (Object bootstrapMethodArgument : bootstrapMethodArguments) { - addConstant(bootstrapMethodArgument); + int numBootstrapArguments = bootstrapMethodArguments.length; + int[] bootstrapMethodArgumentIndexes = new int[numBootstrapArguments]; + for (int i = 0; i < numBootstrapArguments; i++) { + bootstrapMethodArgumentIndexes[i] = addConstant(bootstrapMethodArguments[i]).index; } // Write the bootstrap method in the BootstrapMethods table. This is necessary to be able to @@ -1075,10 +1088,10 @@ Symbol addBootstrapMethod( bootstrapMethodHandle.getDesc(), bootstrapMethodHandle.isInterface()) .index); - int numBootstrapArguments = bootstrapMethodArguments.length; + bootstrapMethodsAttribute.putShort(numBootstrapArguments); - for (Object bootstrapMethodArgument : bootstrapMethodArguments) { - bootstrapMethodsAttribute.putShort(addConstant(bootstrapMethodArgument).index); + for (int i = 0; i < numBootstrapArguments; i++) { + bootstrapMethodsAttribute.putShort(bootstrapMethodArgumentIndexes[i]); } // Compute the length and the hash code of the bootstrap method. @@ -1131,6 +1144,8 @@ private Symbol addBootstrapMethod(final int offset, final int length, final int // ----------------------------------------------------------------------------------------------- /** + * Returns the type table element whose index is given. + * * @param typeIndex a type table index. * @return the type table element whose index is given. */ @@ -1138,6 +1153,18 @@ Symbol getType(final int typeIndex) { return typeTable[typeIndex]; } + /** + * Returns the label corresponding to the "forward uninitialized" type table element whose index + * is given. + * + * @param typeIndex the type table index of a "forward uninitialized" type table element. + * @return the label corresponding of the NEW instruction which created this "forward + * uninitialized" type. + */ + Label getForwardUninitializedLabel(final int typeIndex) { + return labelTable[(int) typeTable[typeIndex].data].label; + } + /** * Adds a type in the type table of this symbol table. Does nothing if the type table already * contains a similar type. @@ -1154,17 +1181,17 @@ int addType(final String value) { } entry = entry.next; } - return addType(new Entry(typeCount, Symbol.TYPE_TAG, value, hashCode)); + return addTypeInternal(new Entry(typeCount, Symbol.TYPE_TAG, value, hashCode)); } /** - * Adds an {@link Frame#ITEM_UNINITIALIZED} type in the type table of this symbol table. Does - * nothing if the type table already contains a similar type. + * Adds an uninitialized type in the type table of this symbol table. Does nothing if the type + * table already contains a similar type. * * @param value an internal class name. - * @param bytecodeOffset the bytecode offset of the NEW instruction that created this {@link - * Frame#ITEM_UNINITIALIZED} type value. - * @return the index of a new or already existing type Symbol with the given value. + * @param bytecodeOffset the bytecode offset of the NEW instruction that created this + * uninitialized type value. + * @return the index of a new or already existing type #@link Symbol} with the given value. */ int addUninitializedType(final String value, final int bytecodeOffset) { int hashCode = hash(Symbol.UNINITIALIZED_TYPE_TAG, value, bytecodeOffset); @@ -1178,10 +1205,36 @@ int addUninitializedType(final String value, final int bytecodeOffset) { } entry = entry.next; } - return addType( + return addTypeInternal( new Entry(typeCount, Symbol.UNINITIALIZED_TYPE_TAG, value, bytecodeOffset, hashCode)); } + /** + * Adds a "forward uninitialized" type in the type table of this symbol table. Does nothing if the + * type table already contains a similar type. + * + * @param value an internal class name. + * @param label the label of the NEW instruction that created this uninitialized type value. If + * the label is resolved, use the {@link #addUninitializedType} method instead. + * @return the index of a new or already existing type {@link Symbol} with the given value. + */ + int addForwardUninitializedType(final String value, final Label label) { + int labelIndex = getOrAddLabelEntry(label).index; + int hashCode = hash(Symbol.FORWARD_UNINITIALIZED_TYPE_TAG, value, labelIndex); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == Symbol.FORWARD_UNINITIALIZED_TYPE_TAG + && entry.hashCode == hashCode + && entry.data == labelIndex + && entry.value.equals(value)) { + return entry.index; + } + entry = entry.next; + } + return addTypeInternal( + new Entry(typeCount, Symbol.FORWARD_UNINITIALIZED_TYPE_TAG, value, labelIndex, hashCode)); + } + /** * Adds a merged type in the type table of this symbol table. Does nothing if the type table * already contains a similar type. @@ -1194,8 +1247,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) { @@ -1219,7 +1274,7 @@ int addMergedType(final int typeTableIndex1, final int typeTableIndex2) { * @return the index in {@link #typeTable} where the given type was added, which is also equal to * entry's index by hypothesis. */ - private int addType(final Entry entry) { + private int addTypeInternal(final Entry entry) { if (typeTable == null) { typeTable = new Entry[16]; } @@ -1232,6 +1287,59 @@ private int addType(final Entry entry) { return put(entry).index; } + /** + * Returns the {@link LabelEntry} corresponding to the given label. Creates a new one if there is + * no such entry. + * + * @param label the {@link Label} of a NEW instruction which created an uninitialized type, in the + * case where this NEW instruction is after the <init> constructor call (in bytecode + * offset order). See {@link Symbol#FORWARD_UNINITIALIZED_TYPE_TAG}. + * @return the {@link LabelEntry} corresponding to {@code label}. + */ + private LabelEntry getOrAddLabelEntry(final Label label) { + if (labelEntries == null) { + labelEntries = new LabelEntry[16]; + labelTable = new LabelEntry[16]; + } + int hashCode = System.identityHashCode(label); + LabelEntry labelEntry = labelEntries[hashCode % labelEntries.length]; + while (labelEntry != null && labelEntry.label != label) { + labelEntry = labelEntry.next; + } + if (labelEntry != null) { + return labelEntry; + } + + if (labelCount > (labelEntries.length * 3) / 4) { + int currentCapacity = labelEntries.length; + int newCapacity = currentCapacity * 2 + 1; + LabelEntry[] newLabelEntries = new LabelEntry[newCapacity]; + for (int i = currentCapacity - 1; i >= 0; --i) { + LabelEntry currentEntry = labelEntries[i]; + while (currentEntry != null) { + int newCurrentEntryIndex = System.identityHashCode(currentEntry.label) % newCapacity; + LabelEntry nextEntry = currentEntry.next; + currentEntry.next = newLabelEntries[newCurrentEntryIndex]; + newLabelEntries[newCurrentEntryIndex] = currentEntry; + currentEntry = nextEntry; + } + } + labelEntries = newLabelEntries; + } + if (labelCount == labelTable.length) { + LabelEntry[] newLabelTable = new LabelEntry[2 * labelTable.length]; + System.arraycopy(labelTable, 0, newLabelTable, 0, labelTable.length); + labelTable = newLabelTable; + } + + labelEntry = new LabelEntry(labelCount, label); + int index = hashCode % labelEntries.length; + labelEntry.next = labelEntries[index]; + labelEntries[index] = labelEntry; + labelTable[labelCount++] = labelEntry; + return labelEntry; + } + // ----------------------------------------------------------------------------------------------- // Static helper methods to compute hash codes. // ----------------------------------------------------------------------------------------------- @@ -1274,4 +1382,82 @@ private static int hash( final int value4) { return 0x7FFFFFFF & (tag + value1.hashCode() * value2.hashCode() * value3.hashCode() * value4); } + + /** + * An entry of a SymbolTable. This concrete and private subclass of {@link Symbol} adds two fields + * which are only used inside SymbolTable, to implement hash sets of symbols (in order to avoid + * duplicate symbols). See {@link #entries}. + * + * @author Eric Bruneton + */ + private static final class Entry extends Symbol { + + /** The hash code of this entry. */ + final int hashCode; + + /** + * Another entry (and so on recursively) having the same hash code (modulo the size of {@link + * #entries}) as this one. + */ + Entry next; + + Entry( + final int index, + final int tag, + final String owner, + final String name, + final String value, + final long data, + final int hashCode) { + super(index, tag, owner, name, value, data); + this.hashCode = hashCode; + } + + Entry(final int index, final int tag, final String value, final int hashCode) { + super(index, tag, /* owner= */ null, /* name= */ null, value, /* data= */ 0); + this.hashCode = hashCode; + } + + Entry(final int index, final int tag, final String value, final long data, final int hashCode) { + super(index, tag, /* owner= */ null, /* name= */ null, value, data); + this.hashCode = hashCode; + } + + Entry( + final int index, final int tag, final String name, final String value, final int hashCode) { + super(index, tag, /* owner= */ null, name, value, /* data= */ 0); + this.hashCode = hashCode; + } + + Entry(final int index, final int tag, final long data, final int hashCode) { + super(index, tag, /* owner= */ null, /* name= */ null, /* value= */ null, data); + this.hashCode = hashCode; + } + } + + /** + * A label corresponding to a "forward uninitialized" type in the ASM specific {@link + * SymbolTable#typeTable} (see {@link Symbol#FORWARD_UNINITIALIZED_TYPE_TAG}). + * + * @author Eric Bruneton + */ + private static final class LabelEntry { + + /** The index of this label entry in the {@link SymbolTable#labelTable} array. */ + final int index; + + /** The value of this label entry. */ + final Label label; + + /** + * Another entry (and so on recursively) having the same hash code (modulo the size of {@link + * SymbolTable#labelEntries}}) as this one. + */ + LabelEntry next; + + LabelEntry(final int index, final Label label) { + this.index = index; + this.label = label; + } + } } diff --git a/src/jvm/clojure/asm/Type.java b/src/jvm/clojure/asm/Type.java index bcd173d097..eb59fe6908 100644 --- a/src/jvm/clojure/asm/Type.java +++ b/src/jvm/clojure/asm/Type.java @@ -37,33 +37,33 @@ * @author Eric Bruneton * @author Chris Nokleberg */ -public class Type { +public final class Type { - /** The sort of the void type. See {@link #getSort}. */ + /** The sort of the {@code void} type. See {@link #getSort}. */ public static final int VOID = 0; - /** The sort of the boolean type. See {@link #getSort}. */ + /** The sort of the {@code boolean} type. See {@link #getSort}. */ public static final int BOOLEAN = 1; - /** The sort of the char type. See {@link #getSort}. */ + /** The sort of the {@code char} type. See {@link #getSort}. */ public static final int CHAR = 2; - /** The sort of the byte type. See {@link #getSort}. */ + /** The sort of the {@code byte} type. See {@link #getSort}. */ public static final int BYTE = 3; - /** The sort of the short type. See {@link #getSort}. */ + /** The sort of the {@code short} type. See {@link #getSort}. */ public static final int SHORT = 4; - /** The sort of the int type. See {@link #getSort}. */ + /** The sort of the {@code int} type. See {@link #getSort}. */ public static final int INT = 5; - /** The sort of the float type. See {@link #getSort}. */ + /** The sort of the {@code float} type. See {@link #getSort}. */ public static final int FLOAT = 6; - /** The sort of the long type. See {@link #getSort}. */ + /** The sort of the {@code long} type. See {@link #getSort}. */ public static final int LONG = 7; - /** The sort of the double type. See {@link #getSort}. */ + /** The sort of the {@code double} type. See {@link #getSort}. */ public static final int DOUBLE = 8; /** The sort of array reference types. See {@link #getSort}. */ @@ -81,32 +81,32 @@ public class Type { /** The descriptors of the primitive types. */ private static final String PRIMITIVE_DESCRIPTORS = "VZCBSIFJD"; - /** The void type. */ + /** The {@code void} type. */ public static final Type VOID_TYPE = new Type(VOID, PRIMITIVE_DESCRIPTORS, VOID, VOID + 1); - /** The boolean type. */ + /** The {@code boolean} type. */ public static final Type BOOLEAN_TYPE = new Type(BOOLEAN, PRIMITIVE_DESCRIPTORS, BOOLEAN, BOOLEAN + 1); - /** The char type. */ + /** The {@code char} type. */ public static final Type CHAR_TYPE = new Type(CHAR, PRIMITIVE_DESCRIPTORS, CHAR, CHAR + 1); - /** The byte type. */ + /** The {@code byte} type. */ public static final Type BYTE_TYPE = new Type(BYTE, PRIMITIVE_DESCRIPTORS, BYTE, BYTE + 1); - /** The short type. */ + /** The {@code short} type. */ public static final Type SHORT_TYPE = new Type(SHORT, PRIMITIVE_DESCRIPTORS, SHORT, SHORT + 1); - /** The int type. */ + /** The {@code int} type. */ public static final Type INT_TYPE = new Type(INT, PRIMITIVE_DESCRIPTORS, INT, INT + 1); - /** The float type. */ + /** The {@code float} type. */ public static final Type FLOAT_TYPE = new Type(FLOAT, PRIMITIVE_DESCRIPTORS, FLOAT, FLOAT + 1); - /** The long type. */ + /** The {@code long} type. */ public static final Type LONG_TYPE = new Type(LONG, PRIMITIVE_DESCRIPTORS, LONG, LONG + 1); - /** The double type. */ + /** The {@code double} type. */ public static final Type DOUBLE_TYPE = new Type(DOUBLE, PRIMITIVE_DESCRIPTORS, DOUBLE, DOUBLE + 1); @@ -146,10 +146,6 @@ public class Type { */ private final int valueEnd; - // ----------------------------------------------------------------------------------------------- - // Constructors - // ----------------------------------------------------------------------------------------------- - /** * Constructs a reference type. * @@ -157,7 +153,7 @@ public class Type { * @param valueBuffer a buffer containing the value of this field or method type. * @param valueBegin the beginning index, inclusive, of the value of this field or method type in * valueBuffer. - * @param valueEnd tne end index, exclusive, of the value of this field or method type in + * @param valueEnd the end index, exclusive, of the value of this field or method type in * valueBuffer. */ private Type(final int sort, final String valueBuffer, final int valueBegin, final int valueEnd) { @@ -167,6 +163,10 @@ private Type(final int sort, final String valueBuffer, final int valueBegin, fin this.valueEnd = valueEnd; } + // ----------------------------------------------------------------------------------------------- + // Methods to get Type(s) from a descriptor, a reflected Method or Constructor, other types, etc. + // ----------------------------------------------------------------------------------------------- + /** * Returns the {@link Type} corresponding to the given type descriptor. * @@ -174,40 +174,7 @@ private Type(final int sort, final String valueBuffer, final int valueBegin, fin * @return the {@link Type} corresponding to the given type descriptor. */ public static Type getType(final String typeDescriptor) { - return getType(typeDescriptor, 0, typeDescriptor.length()); - } - - /** - * Returns the {@link Type} corresponding to the given internal name. - * - * @param internalName an internal name. - * @return the {@link Type} corresponding to the given internal name. - */ - public static Type getObjectType(final String internalName) { - return new Type( - internalName.charAt(0) == '[' ? ARRAY : INTERNAL, internalName, 0, internalName.length()); - } - - /** - * Returns the {@link Type} corresponding to the given method descriptor. Equivalent to - * Type.getType(methodDescriptor). - * - * @param methodDescriptor a method descriptor. - * @return the {@link Type} corresponding to the given method descriptor. - */ - public static Type getMethodType(final String methodDescriptor) { - return new Type(METHOD, methodDescriptor, 0, methodDescriptor.length()); - } - - /** - * Returns the method {@link Type} corresponding to the given argument and return types. - * - * @param returnType the return type of the method. - * @param argumentTypes the argument types of the method. - * @return the method {@link Type} corresponding to the given argument and return types. - */ - public static Type getMethodType(final Type returnType, final Type... argumentTypes) { - return getType(getMethodDescriptor(returnType, argumentTypes)); + return getTypeInternal(typeDescriptor, 0, typeDescriptor.length()); } /** @@ -264,6 +231,60 @@ public static Type getType(final Method method) { return getType(getMethodDescriptor(method)); } + /** + * Returns the type of the elements of this array type. This method should only be used for an + * array type. + * + * @return Returns the type of the elements of this array type. + */ + public Type getElementType() { + final int numDimensions = getDimensions(); + return getTypeInternal(valueBuffer, valueBegin + numDimensions, valueEnd); + } + + /** + * Returns the {@link Type} corresponding to the given internal name. + * + * @param internalName an internal name (see {@link Type#getInternalName()}). + * @return the {@link Type} corresponding to the given internal name. + */ + public static Type getObjectType(final String internalName) { + return new Type( + internalName.charAt(0) == '[' ? ARRAY : INTERNAL, internalName, 0, internalName.length()); + } + + /** + * Returns the {@link Type} corresponding to the given method descriptor. Equivalent to + * Type.getType(methodDescriptor). + * + * @param methodDescriptor a method descriptor. + * @return the {@link Type} corresponding to the given method descriptor. + */ + public static Type getMethodType(final String methodDescriptor) { + return new Type(METHOD, methodDescriptor, 0, methodDescriptor.length()); + } + + /** + * Returns the method {@link Type} corresponding to the given argument and return types. + * + * @param returnType the return type of the method. + * @param argumentTypes the argument types of the method. + * @return the method {@link Type} corresponding to the given argument and return types. + */ + public static Type getMethodType(final Type returnType, final Type... argumentTypes) { + return getType(getMethodDescriptor(returnType, argumentTypes)); + } + + /** + * Returns the argument types of methods of this type. This method should only be used for method + * types. + * + * @return the argument types of methods of this type. + */ + public Type[] getArgumentTypes() { + return getArgumentTypes(getDescriptor()); + } + /** * Returns the {@link Type} values corresponding to the argument types of the given method * descriptor. @@ -274,26 +295,12 @@ public static Type getType(final Method method) { */ public static Type[] getArgumentTypes(final String methodDescriptor) { // First step: compute the number of argument types in methodDescriptor. - int numArgumentTypes = 0; - // Skip the first character, which is always a '('. - int currentOffset = 1; - // Parse the argument types, one at a each loop iteration. - while (methodDescriptor.charAt(currentOffset) != ')') { - while (methodDescriptor.charAt(currentOffset) == '[') { - currentOffset++; - } - if (methodDescriptor.charAt(currentOffset++) == 'L') { - while (methodDescriptor.charAt(currentOffset++) != ';') { - // Skip the argument descriptor content. - } - } - ++numArgumentTypes; - } + int numArgumentTypes = getArgumentCount(methodDescriptor); // Second step: create a Type instance for each argument type. Type[] argumentTypes = new Type[numArgumentTypes]; // Skip the first character, which is always a '('. - currentOffset = 1; + int currentOffset = 1; // Parse and create the argument types, one at each loop iteration. int currentArgumentTypeIndex = 0; while (methodDescriptor.charAt(currentOffset) != ')') { @@ -302,12 +309,12 @@ public static Type[] getArgumentTypes(final String methodDescriptor) { currentOffset++; } if (methodDescriptor.charAt(currentOffset++) == 'L') { - while (methodDescriptor.charAt(currentOffset++) != ';') { - // Skip the argument descriptor content. - } + // Skip the argument descriptor content. + int semiColumnOffset = methodDescriptor.indexOf(';', currentOffset); + currentOffset = Math.max(currentOffset, semiColumnOffset + 1); } argumentTypes[currentArgumentTypeIndex++] = - getType(methodDescriptor, currentArgumentTypeOffset, currentOffset); + getTypeInternal(methodDescriptor, currentArgumentTypeOffset, currentOffset); } return argumentTypes; } @@ -327,6 +334,16 @@ public static Type[] getArgumentTypes(final Method method) { return types; } + /** + * Returns the return type of methods of this type. This method should only be used for method + * types. + * + * @return the return type of methods of this type. + */ + public Type getReturnType() { + return getReturnType(getDescriptor()); + } + /** * Returns the {@link Type} corresponding to the return type of the given method descriptor. * @@ -334,20 +351,8 @@ public static Type[] getArgumentTypes(final Method method) { * @return the {@link Type} corresponding to the return type of the given method descriptor. */ public static Type getReturnType(final String methodDescriptor) { - // Skip the first character, which is always a '('. - int currentOffset = 1; - // Skip the argument types, one at a each loop iteration. - while (methodDescriptor.charAt(currentOffset) != ')') { - while (methodDescriptor.charAt(currentOffset) == '[') { - currentOffset++; - } - if (methodDescriptor.charAt(currentOffset++) == 'L') { - while (methodDescriptor.charAt(currentOffset++) != ';') { - // Skip the argument descriptor content. - } - } - } - return getType(methodDescriptor, currentOffset + 1, methodDescriptor.length()); + return getTypeInternal( + methodDescriptor, getReturnTypeOffset(methodDescriptor), methodDescriptor.length()); } /** @@ -361,44 +366,26 @@ public static Type getReturnType(final Method method) { } /** - * Computes the size of the arguments and of the return value of a method. + * Returns the start index of the return type of the given method descriptor. * * @param methodDescriptor a method descriptor. - * @return the size of the arguments of the method (plus one for the implicit this argument), - * argumentsSize, and the size of its return value, returnSize, packed into a single int i = - * (argumentsSize << 2) | returnSize (argumentsSize is therefore equal to i - * >> 2, and returnSize to i & 0x03). + * @return the start index of the return type of the given method descriptor. */ - public static int getArgumentsAndReturnSizes(final String methodDescriptor) { - int argumentsSize = 1; + static int getReturnTypeOffset(final String methodDescriptor) { // Skip the first character, which is always a '('. int currentOffset = 1; - int currentChar = methodDescriptor.charAt(currentOffset); - // Parse the argument types and compute their size, one at a each loop iteration. - while (currentChar != ')') { - if (currentChar == 'J' || currentChar == 'D') { + // Skip the argument types, one at a each loop iteration. + while (methodDescriptor.charAt(currentOffset) != ')') { + while (methodDescriptor.charAt(currentOffset) == '[') { currentOffset++; - argumentsSize += 2; - } else { - while (methodDescriptor.charAt(currentOffset) == '[') { - currentOffset++; - } - if (methodDescriptor.charAt(currentOffset++) == 'L') { - while (methodDescriptor.charAt(currentOffset++) != ';') { - // Skip the argument descriptor content. - } - } - argumentsSize += 1; } - currentChar = methodDescriptor.charAt(currentOffset); - } - currentChar = methodDescriptor.charAt(currentOffset + 1); - if (currentChar == 'V') { - return argumentsSize << 2; - } else { - int returnSize = (currentChar == 'J' || currentChar == 'D') ? 2 : 1; - return argumentsSize << 2 | returnSize; + if (methodDescriptor.charAt(currentOffset++) == 'L') { + // Skip the argument descriptor content. + int semiColumnOffset = methodDescriptor.indexOf(';', currentOffset); + currentOffset = Math.max(currentOffset, semiColumnOffset + 1); + } } + return currentOffset + 1; } /** @@ -411,7 +398,7 @@ public static int getArgumentsAndReturnSizes(final String methodDescriptor) { * descriptorBuffer. * @return the {@link Type} corresponding to the given type descriptor. */ - private static Type getType( + private static Type getTypeInternal( final String descriptorBuffer, final int descriptorBegin, final int descriptorEnd) { switch (descriptorBuffer.charAt(descriptorBegin)) { case 'V': @@ -439,50 +426,14 @@ private static Type getType( case '(': return new Type(METHOD, descriptorBuffer, descriptorBegin, descriptorEnd); default: - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Invalid descriptor: " + descriptorBuffer); } } // ----------------------------------------------------------------------------------------------- - // Accessors + // Methods to get class names, internal names or descriptors. // ----------------------------------------------------------------------------------------------- - /** - * Returns the sort of this type. - * - * @return {@link #VOID}, {@link #BOOLEAN}, {@link #CHAR}, {@link #BYTE}, {@link #SHORT}, {@link - * #INT}, {@link #FLOAT}, {@link #LONG}, {@link #DOUBLE}, {@link #ARRAY}, {@link #OBJECT} or - * {@link #METHOD}. - */ - public int getSort() { - return sort == INTERNAL ? OBJECT : sort; - } - - /** - * Returns the number of dimensions of this array type. This method should only be used for an - * array type. - * - * @return the number of dimensions of this array type. - */ - public int getDimensions() { - int numDimensions = 1; - while (valueBuffer.charAt(valueBegin + numDimensions) == '[') { - numDimensions++; - } - return numDimensions; - } - - /** - * Returns the type of the elements of this array type. This method should only be used for an - * array type. - * - * @return Returns the type of the elements of this array type. - */ - public Type getElementType() { - final int numDimensions = getDimensions(); - return getType(valueBuffer, valueBegin + numDimensions, valueEnd); - } - /** * Returns the binary name of the class corresponding to this type. This method must not be used * on method types. @@ -535,42 +486,16 @@ public String getInternalName() { } /** - * Returns the argument types of methods of this type. This method should only be used for method - * types. - * - * @return the argument types of methods of this type. - */ - public Type[] getArgumentTypes() { - return getArgumentTypes(getDescriptor()); - } - - /** - * Returns the return type of methods of this type. This method should only be used for method - * types. + * Returns the internal name of the given class. The internal name of a class is its fully + * qualified name, as returned by Class.getName(), where '.' are replaced by '/'. * - * @return the return type of methods of this type. + * @param clazz an object or array class. + * @return the internal name of the given class. */ - public Type getReturnType() { - return getReturnType(getDescriptor()); + public static String getInternalName(final Class clazz) { + return clazz.getName().replace('.', '/'); } - /** - * Returns the size of the arguments and of the return value of methods of this type. This method - * should only be used for method types. - * - * @return the size of the arguments of the method (plus one for the implicit this argument), - * argumentsSize, and the size of its return value, returnSize, packed into a single int i = - * (argumentsSize << 2) | returnSize (argumentsSize is therefore equal to i - * >> 2, and returnSize to i & 0x03). - */ - public int getArgumentsAndReturnSizes() { - return getArgumentsAndReturnSizes(getDescriptor()); - } - - // ----------------------------------------------------------------------------------------------- - // Conversion to type descriptors - // ----------------------------------------------------------------------------------------------- - /** * Returns the descriptor corresponding to this type. * @@ -580,67 +505,12 @@ public String getDescriptor() { if (sort == OBJECT) { return valueBuffer.substring(valueBegin - 1, valueEnd + 1); } else if (sort == INTERNAL) { - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append('L'); - stringBuilder.append(valueBuffer, valueBegin, valueEnd); - stringBuilder.append(';'); - return stringBuilder.toString(); + return 'L' + valueBuffer.substring(valueBegin, valueEnd) + ';'; } else { return valueBuffer.substring(valueBegin, valueEnd); } } - /** - * Returns the descriptor corresponding to the given argument and return types. - * - * @param returnType the return type of the method. - * @param argumentTypes the argument types of the method. - * @return the descriptor corresponding to the given argument and return types. - */ - public static String getMethodDescriptor(final Type returnType, final Type... argumentTypes) { - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append('('); - for (int i = 0; i < argumentTypes.length; ++i) { - argumentTypes[i].appendDescriptor(stringBuilder); - } - stringBuilder.append(')'); - returnType.appendDescriptor(stringBuilder); - return stringBuilder.toString(); - } - - /** - * Appends the descriptor corresponding to this type to the given string buffer. - * - * @param stringBuilder the string builder to which the descriptor must be appended. - */ - private void appendDescriptor(final StringBuilder stringBuilder) { - if (sort == OBJECT) { - stringBuilder.append(valueBuffer, valueBegin - 1, valueEnd + 1); - } else if (sort == INTERNAL) { - stringBuilder.append('L'); - stringBuilder.append(valueBuffer, valueBegin, valueEnd); - stringBuilder.append(';'); - } else { - stringBuilder.append(valueBuffer, valueBegin, valueEnd); - } - } - - // ----------------------------------------------------------------------------------------------- - // Direct conversion from classes to type descriptors, - // without intermediate Type objects - // ----------------------------------------------------------------------------------------------- - - /** - * Returns the internal name of the given class. The internal name of a class is its fully - * qualified name, as returned by Class.getName(), where '.' are replaced by '/'. - * - * @param clazz an object or array class. - * @return the internal name of the given class. - */ - public static String getInternalName(final Class clazz) { - return clazz.getName().replace('.', '/'); - } - /** * Returns the descriptor corresponding to the given class. * @@ -649,7 +519,7 @@ public static String getInternalName(final Class clazz) { */ public static String getDescriptor(final Class clazz) { StringBuilder stringBuilder = new StringBuilder(); - appendDescriptor(stringBuilder, clazz); + appendDescriptor(clazz, stringBuilder); return stringBuilder.toString(); } @@ -663,12 +533,30 @@ public static String getConstructorDescriptor(final Constructor constructor) StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append('('); Class[] parameters = constructor.getParameterTypes(); - for (int i = 0; i < parameters.length; ++i) { - appendDescriptor(stringBuilder, parameters[i]); + for (Class parameter : parameters) { + appendDescriptor(parameter, stringBuilder); } return stringBuilder.append(")V").toString(); } + /** + * Returns the descriptor corresponding to the given argument and return types. + * + * @param returnType the return type of the method. + * @param argumentTypes the argument types of the method. + * @return the descriptor corresponding to the given argument and return types. + */ + public static String getMethodDescriptor(final Type returnType, final Type... argumentTypes) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append('('); + for (Type argumentType : argumentTypes) { + argumentType.appendDescriptor(stringBuilder); + } + stringBuilder.append(')'); + returnType.appendDescriptor(stringBuilder); + return stringBuilder.toString(); + } + /** * Returns the descriptor corresponding to the given method. * @@ -679,21 +567,36 @@ public static String getMethodDescriptor(final Method method) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append('('); Class[] parameters = method.getParameterTypes(); - for (int i = 0; i < parameters.length; ++i) { - appendDescriptor(stringBuilder, parameters[i]); + for (Class parameter : parameters) { + appendDescriptor(parameter, stringBuilder); } stringBuilder.append(')'); - appendDescriptor(stringBuilder, method.getReturnType()); + appendDescriptor(method.getReturnType(), stringBuilder); return stringBuilder.toString(); } /** - * Appends the descriptor of the given class to the given string builder. + * Appends the descriptor corresponding to this type to the given string buffer. * * @param stringBuilder the string builder to which the descriptor must be appended. + */ + private void appendDescriptor(final StringBuilder stringBuilder) { + if (sort == OBJECT) { + stringBuilder.append(valueBuffer, valueBegin - 1, valueEnd + 1); + } else if (sort == INTERNAL) { + stringBuilder.append('L').append(valueBuffer, valueBegin, valueEnd).append(';'); + } else { + stringBuilder.append(valueBuffer, valueBegin, valueEnd); + } + } + + /** + * Appends the descriptor of the given class to the given string builder. + * * @param clazz the class whose descriptor must be computed. + * @param stringBuilder the string builder to which the descriptor must be appended. */ - private static void appendDescriptor(final StringBuilder stringBuilder, final Class clazz) { + private static void appendDescriptor(final Class clazz, final StringBuilder stringBuilder) { Class currentClass = clazz; while (currentClass.isArray()) { stringBuilder.append('['); @@ -724,26 +627,44 @@ private static void appendDescriptor(final StringBuilder stringBuilder, final Cl } 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(';'); } } // ----------------------------------------------------------------------------------------------- - // Corresponding size and opcodes + // Methods to get the sort, dimension, size, and opcodes corresponding to a Type or descriptor. // ----------------------------------------------------------------------------------------------- + /** + * Returns the sort of this type. + * + * @return {@link #VOID}, {@link #BOOLEAN}, {@link #CHAR}, {@link #BYTE}, {@link #SHORT}, {@link + * #INT}, {@link #FLOAT}, {@link #LONG}, {@link #DOUBLE}, {@link #ARRAY}, {@link #OBJECT} or + * {@link #METHOD}. + */ + public int getSort() { + return sort == INTERNAL ? OBJECT : sort; + } + + /** + * Returns the number of dimensions of this array type. This method should only be used for an + * array type. + * + * @return the number of dimensions of this array type. + */ + public int getDimensions() { + int numDimensions = 1; + while (valueBuffer.charAt(valueBegin + numDimensions) == '[') { + numDimensions++; + } + return numDimensions; + } + /** * Returns the size of values of this type. This method must not be used for method types. * - * @return the size of values of this type, i.e., 2 for long and double, 0 for - * void and 1 otherwise. + * @return the size of values of this type, i.e., 2 for {@code long} and {@code double}, 0 for + * {@code void} and 1 otherwise. */ public int getSize() { switch (sort) { @@ -767,6 +688,99 @@ public int getSize() { } } + /** + * Returns the number of arguments of this method type. This method should only be used for method + * types. + * + * @return the number of arguments of this method type. Each argument counts for 1, even long and + * double ones. The implicit @literal{this} argument is not counted. + */ + public int getArgumentCount() { + return getArgumentCount(getDescriptor()); + } + + /** + * Returns the number of arguments in the given method descriptor. + * + * @param methodDescriptor a method descriptor. + * @return the number of arguments in the given method descriptor. Each argument counts for 1, + * even long and double ones. The implicit @literal{this} argument is not counted. + */ + public static int getArgumentCount(final String methodDescriptor) { + int argumentCount = 0; + // Skip the first character, which is always a '('. + int currentOffset = 1; + // Parse the argument types, one at a each loop iteration. + while (methodDescriptor.charAt(currentOffset) != ')') { + while (methodDescriptor.charAt(currentOffset) == '[') { + currentOffset++; + } + if (methodDescriptor.charAt(currentOffset++) == 'L') { + // Skip the argument descriptor content. + int semiColumnOffset = methodDescriptor.indexOf(';', currentOffset); + currentOffset = Math.max(currentOffset, semiColumnOffset + 1); + } + ++argumentCount; + } + return argumentCount; + } + + /** + * Returns the size of the arguments and of the return value of methods of this type. This method + * should only be used for method types. + * + * @return the size of the arguments of the method (plus one for the implicit this argument), + * argumentsSize, and the size of its return value, returnSize, packed into a single int i = + * {@code (argumentsSize << 2) | returnSize} (argumentsSize is therefore equal to {@code + * i >> 2}, and returnSize to {@code i & 0x03}). Long and double values have size 2, + * the others have size 1. + */ + public int getArgumentsAndReturnSizes() { + return getArgumentsAndReturnSizes(getDescriptor()); + } + + /** + * Computes the size of the arguments and of the return value of a method. + * + * @param methodDescriptor a method descriptor. + * @return the size of the arguments of the method (plus one for the implicit this argument), + * argumentsSize, and the size of its return value, returnSize, packed into a single int i = + * {@code (argumentsSize << 2) | returnSize} (argumentsSize is therefore equal to {@code + * i >> 2}, and returnSize to {@code i & 0x03}). Long and double values have size 2, + * the others have size 1. + */ + public static int getArgumentsAndReturnSizes(final String methodDescriptor) { + int argumentsSize = 1; + // Skip the first character, which is always a '('. + int currentOffset = 1; + int currentChar = methodDescriptor.charAt(currentOffset); + // Parse the argument types and compute their size, one at a each loop iteration. + while (currentChar != ')') { + if (currentChar == 'J' || currentChar == 'D') { + currentOffset++; + argumentsSize += 2; + } else { + while (methodDescriptor.charAt(currentOffset) == '[') { + currentOffset++; + } + if (methodDescriptor.charAt(currentOffset++) == 'L') { + // Skip the argument descriptor content. + int semiColumnOffset = methodDescriptor.indexOf(';', currentOffset); + currentOffset = Math.max(currentOffset, semiColumnOffset + 1); + } + argumentsSize += 1; + } + currentChar = methodDescriptor.charAt(currentOffset); + } + currentChar = methodDescriptor.charAt(currentOffset + 1); + if (currentChar == 'V') { + return argumentsSize << 2; + } else { + int returnSize = (currentChar == 'J' || currentChar == 'D') ? 2 : 1; + return argumentsSize << 2 | returnSize; + } + } + /** * Returns a JVM instruction opcode adapted to this {@link Type}. This method must not be used for * method types. @@ -775,7 +789,7 @@ public int getSize() { * IASTORE, IADD, ISUB, IMUL, IDIV, IREM, INEG, ISHL, ISHR, IUSHR, IAND, IOR, IXOR and * IRETURN. * @return an opcode that is similar to the given opcode, but adapted to this {@link Type}. For - * example, if this type is float and opcode is IRETURN, this method returns + * example, if this type is {@code float} and {@code opcode} is IRETURN, this method returns * FRETURN. */ public int getOpcode(final int opcode) { @@ -841,14 +855,14 @@ public int getOpcode(final int opcode) { } // ----------------------------------------------------------------------------------------------- - // Equals, hashCode and toString + // Equals, hashCode and toString. // ----------------------------------------------------------------------------------------------- /** * Tests if the given object is equal to this type. * * @param object the object to be compared to this type. - * @return true if the given object is equal to this type. + * @return {@literal true} if the given object is equal to this type. */ @Override public boolean equals(final Object object) { diff --git a/src/jvm/clojure/asm/TypePath.java b/src/jvm/clojure/asm/TypePath.java index aaffa43b9e..c1cc6967a3 100644 --- a/src/jvm/clojure/asm/TypePath.java +++ b/src/jvm/clojure/asm/TypePath.java @@ -34,7 +34,7 @@ * * @author Eric Bruneton */ -public class TypePath { +public final class TypePath { /** A type path step that steps into the element type of an array type. See {@link #getStep}. */ public static final int ARRAY_ELEMENT = 0; @@ -113,8 +113,8 @@ public int getStepArgument(final int index) { * object. * * @param typePath a type path in string form, in the format used by {@link #toString()}. May be - * null or empty. - * @return the corresponding TypePath object, or null if the path is empty. + * {@literal null} or empty. + * @return the corresponding TypePath object, or {@literal null} if the path is empty. */ public static TypePath fromString(final String typePath) { if (typePath == null || typePath.length() == 0) { @@ -187,7 +187,7 @@ public String toString() { * Puts the type_path JVMS structure corresponding to the given TypePath into the given * ByteVector. * - * @param typePath a TypePath instance, or null for empty paths. + * @param typePath a TypePath instance, or {@literal null} for empty paths. * @param output where the type path must be put. */ static void put(final TypePath typePath, final ByteVector output) { diff --git a/src/jvm/clojure/asm/commons/GeneratorAdapter.java b/src/jvm/clojure/asm/commons/GeneratorAdapter.java index 7c4b1096f5..f4240a5a18 100644 --- a/src/jvm/clojure/asm/commons/GeneratorAdapter.java +++ b/src/jvm/clojure/asm/commons/GeneratorAdapter.java @@ -30,13 +30,14 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; - import clojure.asm.ClassVisitor; +import clojure.asm.ConstantDynamic; import clojure.asm.Handle; import clojure.asm.Label; import clojure.asm.MethodVisitor; import clojure.asm.Opcodes; import clojure.asm.Type; +import clojure.asm.InstructionAdapter; /** * A {@link MethodVisitor} with convenient methods to generate code. For example, using this @@ -50,7 +51,7 @@ * } * * - * can be generated as follows: + *

    can be generated as follows: * *

      * ClassWriter cw = new ClassWriter(0);
    @@ -183,7 +184,7 @@ public class GeneratorAdapter extends LocalVariablesSorter {
       private final Type[] argumentTypes;
     
       /** The types of the local variables of the visited method. */
    -  private final List localTypes = new ArrayList();
    +  private final List localTypes = new ArrayList<>();
     
       /**
        * Constructs a new {@link GeneratorAdapter}. Subclasses must not use this constructor.
    @@ -201,7 +202,7 @@ public GeneratorAdapter(
           final int access,
           final String name,
           final String descriptor) {
    -    this(Opcodes.ASM6, methodVisitor, access, name, descriptor);
    +    this(/* latest api = */ Opcodes.ASM9, methodVisitor, access, name, descriptor);
         if (getClass() != GeneratorAdapter.class) {
           throw new IllegalStateException();
         }
    @@ -210,8 +211,8 @@ public GeneratorAdapter(
       /**
        * Constructs a new {@link GeneratorAdapter}.
        *
    -   * @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_EXPERIMENTAL}.
    +   * @param api the ASM API version implemented by this visitor. Must be one of the {@code
    +   *     ASM}x values in {@link Opcodes}.
        * @param methodVisitor the method visitor to which this adapter delegates calls.
        * @param access the method's access flags (see {@link Opcodes}).
        * @param name the method's name.
    @@ -251,8 +252,8 @@ public GeneratorAdapter(
        *
        * @param access access flags of the adapted method.
        * @param method the adapted method.
    -   * @param signature the signature of the adapted method (may be null).
    -   * @param exceptions the exceptions thrown by the adapted method (may be null).
    +   * @param signature the signature of the adapted method (may be {@literal null}).
    +   * @param exceptions the exceptions thrown by the adapted method (may be {@literal null}).
        * @param classVisitor the class visitor to which this adapter delegates calls.
        */
       public GeneratorAdapter(
    @@ -269,19 +270,16 @@ public GeneratorAdapter(
                 method.getName(),
                 method.getDescriptor(),
                 signature,
    -            getInternalNames(exceptions)));
    +            exceptions == null ? null : getInternalNames(exceptions)));
       }
     
       /**
        * Returns the internal names of the given types.
        *
        * @param types a set of types.
    -   * @return the internal names of the given types.
    +   * @return the internal names of the given types (see {@link Type#getInternalName()}).
        */
       private static String[] getInternalNames(final Type[] types) {
    -    if (types == null) {
    -      return null;
    -    }
         String[] names = new String[types.length];
         for (int i = 0; i < names.length; ++i) {
           names[i] = types[i].getInternalName();
    @@ -379,7 +377,7 @@ public void push(final double value) {
       /**
        * Generates the instruction to push the given value on the stack.
        *
    -   * @param value the value to be pushed on the stack. May be null.
    +   * @param value the value to be pushed on the stack. May be {@literal null}.
        */
       public void push(final String value) {
         if (value == null) {
    @@ -425,6 +423,7 @@ public void push(final Type value) {
               break;
             default:
               mv.visitLdcInsn(value);
    +          break;
           }
         }
       }
    @@ -442,6 +441,19 @@ public void push(final Handle handle) {
         }
       }
     
    +  /**
    +   * Generates the instruction to push a constant dynamic on the stack.
    +   *
    +   * @param constantDynamic the constant dynamic to be pushed on the stack.
    +   */
    +  public void push(final ConstantDynamic constantDynamic) {
    +    if (constantDynamic == null) {
    +      mv.visitInsn(Opcodes.ACONST_NULL);
    +    } else {
    +      mv.visitLdcInsn(constantDynamic);
    +    }
    +  }
    +
       // -----------------------------------------------------------------------------------------------
       // Instructions to load and store method arguments
       // -----------------------------------------------------------------------------------------------
    @@ -745,50 +757,9 @@ public void cast(final Type from, final Type to) {
               || from.getSort() > Type.DOUBLE
               || to.getSort() < Type.BOOLEAN
               || to.getSort() > Type.DOUBLE) {
    -        throw new IllegalArgumentException();
    -      }
    -      if (from == Type.DOUBLE_TYPE) {
    -        if (to == Type.FLOAT_TYPE) {
    -          mv.visitInsn(Opcodes.D2F);
    -        } else if (to == Type.LONG_TYPE) {
    -          mv.visitInsn(Opcodes.D2L);
    -        } else {
    -          mv.visitInsn(Opcodes.D2I);
    -          cast(Type.INT_TYPE, to);
    -        }
    -      } else if (from == Type.FLOAT_TYPE) {
    -        if (to == Type.DOUBLE_TYPE) {
    -          mv.visitInsn(Opcodes.F2D);
    -        } else if (to == Type.LONG_TYPE) {
    -          mv.visitInsn(Opcodes.F2L);
    -        } else {
    -          mv.visitInsn(Opcodes.F2I);
    -          cast(Type.INT_TYPE, to);
    -        }
    -      } else if (from == Type.LONG_TYPE) {
    -        if (to == Type.DOUBLE_TYPE) {
    -          mv.visitInsn(Opcodes.L2D);
    -        } else if (to == Type.FLOAT_TYPE) {
    -          mv.visitInsn(Opcodes.L2F);
    -        } else {
    -          mv.visitInsn(Opcodes.L2I);
    -          cast(Type.INT_TYPE, to);
    -        }
    -      } else {
    -        if (to == Type.BYTE_TYPE) {
    -          mv.visitInsn(Opcodes.I2B);
    -        } else if (to == Type.CHAR_TYPE) {
    -          mv.visitInsn(Opcodes.I2C);
    -        } else if (to == Type.DOUBLE_TYPE) {
    -          mv.visitInsn(Opcodes.I2D);
    -        } else if (to == Type.FLOAT_TYPE) {
    -          mv.visitInsn(Opcodes.I2F);
    -        } else if (to == Type.LONG_TYPE) {
    -          mv.visitInsn(Opcodes.I2L);
    -        } else if (to == Type.SHORT_TYPE) {
    -          mv.visitInsn(Opcodes.I2S);
    -        }
    +        throw new IllegalArgumentException("Cannot cast from " + from + " to " + to);
           }
    +      InstructionAdapter.cast(mv, from, to);
         }
       }
     
    @@ -902,6 +873,7 @@ public void unbox(final Type type) {
             break;
           default:
             unboxMethod = null;
    +        break;
         }
         if (unboxMethod == null) {
           checkCast(type);
    @@ -950,7 +922,7 @@ public Label mark() {
        *
        * @param type the type of the top two stack values.
        * @param mode how these values must be compared. One of EQ, NE, LT, GE, GT, LE.
    -   * @param label where to jump if the comparison result is true.
    +   * @param label where to jump if the comparison result is {@literal true}.
        */
       public void ifCmp(final Type type, final int mode, final Label label) {
         switch (type.getSort()) {
    @@ -1009,7 +981,7 @@ public void ifCmp(final Type type, final int mode, final Label label) {
        * stack values.
        *
        * @param mode how these values must be compared. One of EQ, NE, LT, GE, GT, LE.
    -   * @param label where to jump if the comparison result is true.
    +   * @param label where to jump if the comparison result is {@literal true}.
        */
       public void ifICmp(final int mode, final Label label) {
         ifCmp(Type.INT_TYPE, mode, label);
    @@ -1020,7 +992,7 @@ public void ifICmp(final int mode, final Label label) {
        * value with zero.
        *
        * @param mode how these values must be compared. One of EQ, NE, LT, GE, GT, LE.
    -   * @param label where to jump if the comparison result is true.
    +   * @param label where to jump if the comparison result is {@literal true}.
        */
       public void ifZCmp(final int mode, final Label label) {
         mv.visitJumpInsn(mode, label);
    @@ -1029,7 +1001,7 @@ public void ifZCmp(final int mode, final Label label) {
       /**
        * Generates the instruction to jump to the given label if the top stack value is null.
        *
    -   * @param label where to jump if the condition is true.
    +   * @param label where to jump if the condition is {@literal true}.
        */
       public void ifNull(final Label label) {
         mv.visitJumpInsn(Opcodes.IFNULL, label);
    @@ -1038,7 +1010,7 @@ public void ifNull(final Label label) {
       /**
        * Generates the instruction to jump to the given label if the top stack value is not null.
        *
    -   * @param label where to jump if the condition is true.
    +   * @param label where to jump if the condition is {@literal true}.
        */
       public void ifNonNull(final Label label) {
         mv.visitJumpInsn(Opcodes.IFNONNULL, label);
    @@ -1047,7 +1019,7 @@ public void ifNonNull(final Label label) {
       /**
        * Generates the instruction to jump to the given label.
        *
    -   * @param label where to jump if the condition is true.
    +   * @param label where to jump if the condition is {@literal true}.
        */
       public void goTo(final Label label) {
         mv.visitJumpInsn(Opcodes.GOTO, label);
    @@ -1084,7 +1056,7 @@ public void tableSwitch(final int[] keys, final TableSwitchGenerator generator)
        *
        * @param keys the switch case keys.
        * @param generator a generator to generate the code for the switch cases.
    -   * @param useTable true to use a TABLESWITCH instruction, or false to use a
    +   * @param useTable {@literal true} to use a TABLESWITCH instruction, or {@literal false} to use a
        *     LOOKUPSWITCH instruction.
        */
       public void tableSwitch(
    @@ -1304,37 +1276,7 @@ public void newInstance(final Type type) {
        * @param type the type of the array elements.
        */
       public void newArray(final Type type) {
    -    int arrayType;
    -    switch (type.getSort()) {
    -      case Type.BOOLEAN:
    -        arrayType = Opcodes.T_BOOLEAN;
    -        break;
    -      case Type.CHAR:
    -        arrayType = Opcodes.T_CHAR;
    -        break;
    -      case Type.BYTE:
    -        arrayType = Opcodes.T_BYTE;
    -        break;
    -      case Type.SHORT:
    -        arrayType = Opcodes.T_SHORT;
    -        break;
    -      case Type.INT:
    -        arrayType = Opcodes.T_INT;
    -        break;
    -      case Type.FLOAT:
    -        arrayType = Opcodes.T_FLOAT;
    -        break;
    -      case Type.LONG:
    -        arrayType = Opcodes.T_LONG;
    -        break;
    -      case Type.DOUBLE:
    -        arrayType = Opcodes.T_DOUBLE;
    -        break;
    -      default:
    -        typeInsn(Opcodes.ANEWARRAY, type);
    -        return;
    -    }
    -    mv.visitIntInsn(Opcodes.NEWARRAY, arrayType);
    +    InstructionAdapter.newarray(mv, type);
       }
     
       // -----------------------------------------------------------------------------------------------
    @@ -1413,7 +1355,8 @@ public void endMethod() {
        *
        * @param start beginning of the exception handler's scope (inclusive).
        * @param end end of the exception handler's scope (exclusive).
    -   * @param exception internal name of the type of exceptions handled by the handler.
    +   * @param exception internal name of the type of exceptions handled by the handler (see {@link
    +   *     Type#getInternalName()}).
        */
       public void catchException(final Label start, final Label end, final Type exception) {
         Label catchLabel = new Label();
    @@ -1424,4 +1367,4 @@ public void catchException(final Label start, final Label end, final Type except
         }
         mark(catchLabel);
       }
    -}
    \ No newline at end of file
    +}
    diff --git a/src/jvm/clojure/asm/commons/LocalVariablesSorter.java b/src/jvm/clojure/asm/commons/LocalVariablesSorter.java
    index d78e651f4c..0be77fe2c3 100644
    --- a/src/jvm/clojure/asm/commons/LocalVariablesSorter.java
    +++ b/src/jvm/clojure/asm/commons/LocalVariablesSorter.java
    @@ -81,7 +81,7 @@ public class LocalVariablesSorter extends MethodVisitor {
        */
       public LocalVariablesSorter(
           final int access, final String descriptor, final MethodVisitor methodVisitor) {
    -    this(Opcodes.ASM6, access, descriptor, methodVisitor);
    +    this(/* latest api = */ Opcodes.ASM9, access, descriptor, methodVisitor);
         if (getClass() != LocalVariablesSorter.class) {
           throw new IllegalStateException();
         }
    @@ -90,8 +90,8 @@ public LocalVariablesSorter(
       /**
        * Constructs a new {@link LocalVariablesSorter}.
        *
    -   * @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_EXPERIMENTAL}.
    +   * @param api the ASM API version implemented by this visitor. Must be one of the {@code
    +   *     ASM}x values in {@link Opcodes}.
        * @param access access flags of the adapted method.
        * @param descriptor the method's descriptor (see {@link Type}).
        * @param methodVisitor the method visitor to which this adapter delegates calls.
    @@ -107,7 +107,7 @@ protected LocalVariablesSorter(
       }
     
       @Override
    -  public void visitVarInsn(final int opcode, final int var) {
    +  public void visitVarInsn(final int opcode, final int varIndex) {
         Type varType;
         switch (opcode) {
           case Opcodes.LLOAD:
    @@ -134,12 +134,12 @@ public void visitVarInsn(final int opcode, final int var) {
           default:
             throw new IllegalArgumentException("Invalid opcode " + opcode);
         }
    -    super.visitVarInsn(opcode, remap(var, varType));
    +    super.visitVarInsn(opcode, remap(varIndex, varType));
       }
     
       @Override
    -  public void visitIincInsn(final int var, final int increment) {
    -    super.visitIincInsn(remap(var, Type.INT_TYPE), increment);
    +  public void visitIincInsn(final int varIndex, final int increment) {
    +    super.visitIincInsn(remap(varIndex, Type.INT_TYPE), increment);
       }
     
       @Override
    @@ -180,9 +180,9 @@ public AnnotationVisitor visitLocalVariableAnnotation(
       @Override
       public void visitFrame(
           final int type,
    -      final int nLocal,
    +      final int numLocal,
           final Object[] local,
    -      final int nStack,
    +      final int numStack,
           final Object[] stack) {
         if (type != Opcodes.F_NEW) { // Uncompressed frame.
           throw new IllegalArgumentException(
    @@ -198,7 +198,7 @@ public void visitFrame(
         // Copy the types from 'local' to 'remappedLocals'. 'remappedLocals' already contains the
         // variables added with 'newLocal'.
         int oldVar = 0; // Old local variable index.
    -    for (int i = 0; i < nLocal; ++i) {
    +    for (int i = 0; i < numLocal; ++i) {
           Object localType = local[i];
           if (localType != Opcodes.TOP) {
             Type varType = OBJECT_TYPE;
    @@ -221,20 +221,20 @@ public void visitFrame(
         // Remove TOP after long and double types as well as trailing TOPs.
         oldVar = 0;
         int newVar = 0;
    -    int remappedNLocal = 0;
    +    int remappedNumLocal = 0;
         while (oldVar < remappedLocalTypes.length) {
           Object localType = remappedLocalTypes[oldVar];
           oldVar += localType == Opcodes.LONG || localType == Opcodes.DOUBLE ? 2 : 1;
           if (localType != null && localType != Opcodes.TOP) {
             remappedLocalTypes[newVar++] = localType;
    -        remappedNLocal = newVar;
    +        remappedNumLocal = newVar;
           } else {
             remappedLocalTypes[newVar++] = Opcodes.TOP;
           }
         }
     
         // Visit the remapped frame.
    -    super.visitFrame(type, remappedNLocal, remappedLocalTypes, nStack, stack);
    +    super.visitFrame(type, remappedNumLocal, remappedLocalTypes, numStack, stack);
     
         // Restore the original value of 'remappedLocals'.
         remappedLocalTypes = oldRemappedLocals;
    @@ -297,7 +297,7 @@ public int newLocal(final Type type) {
        *     types for the current stack map frame must be updated in place in this array.
        */
       protected void updateNewLocals(final Object[] newLocals) {
    -    // The default implementation does nothing.    
    +    // The default implementation does nothing.
       }
     
       /**
    @@ -321,11 +321,11 @@ private void setFrameLocal(final int local, final Object type) {
         remappedLocalTypes[local] = type;
       }
     
    -  private int remap(final int var, final Type type) {
    -    if (var + type.getSize() <= firstLocal) {
    -      return var;
    +  private int remap(final int varIndex, final Type type) {
    +    if (varIndex + type.getSize() <= firstLocal) {
    +      return varIndex;
         }
    -    int key = 2 * var + type.getSize() - 1;
    +    int key = 2 * varIndex + type.getSize() - 1;
         int size = remappedVariableIndices.length;
         if (key >= size) {
           int[] newRemappedVariableIndices = new int[Math.max(2 * size, key + 1)];
    diff --git a/src/jvm/clojure/asm/commons/Method.java b/src/jvm/clojure/asm/commons/Method.java
    index a3920f7036..ed51f40bf0 100644
    --- a/src/jvm/clojure/asm/commons/Method.java
    +++ b/src/jvm/clojure/asm/commons/Method.java
    @@ -29,7 +29,6 @@
     
     import java.util.HashMap;
     import java.util.Map;
    -
     import clojure.asm.Type;
     
     /**
    @@ -51,7 +50,7 @@ public class Method {
       private static final Map PRIMITIVE_TYPE_DESCRIPTORS;
     
       static {
    -    HashMap descriptors = new HashMap();
    +    HashMap descriptors = new HashMap<>();
         descriptors.put("void", "V");
         descriptors.put("byte", "B");
         descriptors.put("char", "C");
    @@ -135,14 +134,15 @@ public static Method getMethod(final String method) {
        * @throws IllegalArgumentException if method could not get parsed.
        */
       public static Method getMethod(final String method, final boolean defaultPackage) {
    -    int spaceIndex = method.indexOf(' ');
    +    final int spaceIndex = method.indexOf(' ');
         int currentArgumentStartIndex = method.indexOf('(', spaceIndex) + 1;
    -    int endIndex = method.indexOf(')', currentArgumentStartIndex);
    +    final int endIndex = method.indexOf(')', currentArgumentStartIndex);
         if (spaceIndex == -1 || currentArgumentStartIndex == 0 || endIndex == -1) {
           throw new IllegalArgumentException();
         }
    -    String returnType = method.substring(0, spaceIndex);
    -    String methodName = method.substring(spaceIndex + 1, currentArgumentStartIndex - 1).trim();
    +    final String returnType = method.substring(0, spaceIndex);
    +    final String methodName =
    +        method.substring(spaceIndex + 1, currentArgumentStartIndex - 1).trim();
         StringBuilder stringBuilder = new StringBuilder();
         stringBuilder.append('(');
         int currentArgumentEndIndex;
    @@ -151,19 +151,18 @@ public static Method getMethod(final String method, final boolean defaultPackage
           currentArgumentEndIndex = method.indexOf(',', currentArgumentStartIndex);
           if (currentArgumentEndIndex == -1) {
             argumentDescriptor =
    -            getDescriptor(
    +            getDescriptorInternal(
                     method.substring(currentArgumentStartIndex, endIndex).trim(), defaultPackage);
           } else {
             argumentDescriptor =
    -            getDescriptor(
    +            getDescriptorInternal(
                     method.substring(currentArgumentStartIndex, currentArgumentEndIndex).trim(),
                     defaultPackage);
             currentArgumentStartIndex = currentArgumentEndIndex + 1;
           }
           stringBuilder.append(argumentDescriptor);
         } while (currentArgumentEndIndex != -1);
    -    stringBuilder.append(')');
    -    stringBuilder.append(getDescriptor(returnType, defaultPackage));
    +    stringBuilder.append(')').append(getDescriptorInternal(returnType, defaultPackage));
         return new Method(methodName, stringBuilder.toString());
       }
     
    @@ -176,7 +175,7 @@ public static Method getMethod(final String method, final boolean defaultPackage
        *     option is true, or "java.lang.Object" otherwise.
        * @return the descriptor corresponding to the given type name.
        */
    -  private static String getDescriptor(final String type, final boolean defaultPackage) {
    +  private static String getDescriptorInternal(final String type, final boolean defaultPackage) {
         if ("".equals(type)) {
           return type;
         }
    diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java
    index fdb6f360bf..d01331317f 100644
    --- a/src/jvm/clojure/lang/Compiler.java
    +++ b/src/jvm/clojure/lang/Compiler.java
    @@ -4672,6 +4672,7 @@ public void emitForDefn(ObjExpr objx, GeneratorAdapter gen){
     }
     
     static public class ObjExpr implements Expr{
    +	boolean batch_init = true;
     	static final String CONST_PREFIX = "const__";
     	String name;
     	//String simpleName;
    @@ -4815,6 +4816,7 @@ Type[] ctorTypes(){
     	}
     
     	void compile(String superName, String[] interfaceNames, boolean oneTimeUse) throws IOException{
    +		batch_init = false;
     		//create bytecode for a class
     		//with name current_ns.defname[$letname]+
     		//anonymous fns get names fn__id
    @@ -4824,7 +4826,7 @@ void compile(String superName, String[] interfaceNames, boolean oneTimeUse) thro
     		ClassVisitor cv = cw;
     //		ClassVisitor cv = new TraceClassVisitor(new CheckClassAdapter(cw), new PrintWriter(System.out));
     		//ClassVisitor cv = new TraceClassVisitor(cw, new PrintWriter(System.out));
    -		cv.visit(V1_8, ACC_PUBLIC + ACC_SUPER + ACC_FINAL, internalName, null,superName,interfaceNames);
    +		cv.visit(V17, ACC_PUBLIC + ACC_SUPER + ACC_FINAL, internalName, null,superName,interfaceNames);
     //		         superName != null ? superName :
     //		         (isVariadic() ? "clojure/lang/RestFn" : "clojure/lang/AFunction"), null);
     		String source = (String) SOURCE.deref();
    @@ -5722,9 +5724,21 @@ public void emitKeyword(GeneratorAdapter gen, Keyword k){
     
     	public void emitConstant(GeneratorAdapter gen, int id){
             usedConstants = (IPersistentSet) usedConstants.cons(id);
    -		gen.getStatic(objtype, constantName(id), constantType(id));
    +		gen.getStatic(constantContainer(id), constantName(id), constantType(id));
     	}
     
    +	public static final int INITS_PER = 100;
    +
    +	public Type constantContainer(int id) {
    +		if (!batch_init)
    +			return objtype;
    +		else
    +			return Type.getObjectType(objtype.getInternalName() + "$" + constantContainerName(id));
    +	}
    +
    +	public String constantContainerName(int id) {
    +		return "ID_" + (id / INITS_PER);
    +	}
     
     	String constantName(int id){
     		return CONST_PREFIX + id;
    @@ -8308,7 +8322,7 @@ CONSTANT_IDS, new IdentityHashMap(),
     		objx.objtype = Type.getObjectType(objx.internalName);
     		ClassWriter cw = classWriter();
     		ClassVisitor cv = cw;
    -		cv.visit(V1_8, ACC_PUBLIC + ACC_SUPER, objx.internalName, null, "java/lang/Object", null);
    +		cv.visit(V17, ACC_PUBLIC + ACC_SUPER, objx.internalName, null, "java/lang/Object", null);
     
     		//static load method
     		GeneratorAdapter gen = new GeneratorAdapter(ACC_PUBLIC + ACC_STATIC,
    @@ -8332,14 +8346,6 @@ CONSTANT_IDS, new IdentityHashMap(),
     		gen.returnValue();
     		gen.endMethod();
     
    -		//static fields for constants
    -		for(int i = 0; i < objx.constants.count(); i++)
    -			{
    -            if(objx.usedConstants.contains(i))
    -			    cv.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, objx.constantName(i), objx.constantType(i).getDescriptor(),
    -			              null, null);
    -			}
    -
     		final int INITS_PER = 100;
     		int numInits =  objx.constants.count() / INITS_PER;
     		if(objx.constants.count() % INITS_PER != 0)
    @@ -8347,11 +8353,32 @@ CONSTANT_IDS, new IdentityHashMap(),
     
     		for(int n = 0;n ()"),
    +					                 null,
    +					                 null,
    +					                 inner_cv);
    +
     			clinitgen.visitCode();
     			try
     				{
    @@ -8363,7 +8390,7 @@ CONSTANT_IDS, new IdentityHashMap(),
                             {
                             objx.emitValue(objx.constants.nth(i), clinitgen);
                             clinitgen.checkCast(objx.constantType(i));
    -                        clinitgen.putStatic(objx.objtype, objx.constantName(i), objx.constantType(i));
    +                        clinitgen.putStatic(objx.constantContainer(i), objx.constantName(i), objx.constantType(i));
                             }
     					}
     				}
    @@ -8373,6 +8400,8 @@ CONSTANT_IDS, new IdentityHashMap(),
     				}
     			clinitgen.returnValue();
     			clinitgen.endMethod();
    +			inner_cv.visitEnd();
    +			writeClassFile(inner_type.getInternalName(), inner_cw.toByteArray());
     			}
     
     		//static init for constants, keywords and vars
    @@ -8391,8 +8420,6 @@ CONSTANT_IDS, new IdentityHashMap(),
     //			{
     //			objx.emitConstants(clinitgen);
     //			}
    -		for(int n = 0;n