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| * ===================================== ** *
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 * 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
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).
*
- * 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).
* nLocals
is 1, 2 or 3).
+ * the empty stack (numLocal
is 1, 2 or 3).
* 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 = " If 'api' is equal to API_NEW, there are two cases:
+ *
+ * If 'api' is equal to API_OLD, there are two cases:
+ *
+ * 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
+ *
+ * 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
+ *
+ * and there are two cases:
+ *
+ * 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):
+ *
+ * If a user class extends this with 'api' equal to API_NEW, the class hierarchy is equivalent
+ * to
+ *
+ * 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
+ *
+ * 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).
+ *
+ * {@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,
* can be generated 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 ]
+ * }
+ * }
+ * }
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * User subclasses
+ *
+ *
+ * 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 ]
+ * }
+ * }
+ *
+ *
+ *
+ * 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 ]
+ * }
+ * }
+ *
+ *
+ *
+ *
+ *
+ * ASM subclasses
+ *
+ *
+ * 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 ]
+ * }
+ * }
+ *
+ *
+ *
+ * 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 ]
+ * }
+ * }
+ *
+ *
+ *
+ * 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 ]
+ * }
+ * }
+ *
+ *
+ * Notes
+ *
+ *
+ *
+ */
+
+ 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.
*
- *
- * 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:
+ *
* 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
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