From 5912e9ff4d6f0606238ba9bc8b0c1afdd2af9879 Mon Sep 17 00:00:00 2001 From: Konloch Date: Fri, 14 Jun 2019 06:25:25 -0600 Subject: [PATCH] v2.9.21 --- src/org/objectweb/asm/ClassReader.java | 3633 +++++++++++++++++ .../club/bytecodeviewer/BytecodeViewer.java | 4 +- .../decompilers/FernFlowerDecompiler.java | 3 +- .../bytecodeviewer/gui/MainViewerGUI.java | 6 +- 4 files changed, 3640 insertions(+), 6 deletions(-) create mode 100644 src/org/objectweb/asm/ClassReader.java diff --git a/src/org/objectweb/asm/ClassReader.java b/src/org/objectweb/asm/ClassReader.java new file mode 100644 index 00000000..67d22bdb --- /dev/null +++ b/src/org/objectweb/asm/ClassReader.java @@ -0,0 +1,3633 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package org.objectweb.asm; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * A parser to make a {@link ClassVisitor} visit a ClassFile structure, as defined in the Java + * Virtual Machine Specification (JVMS). This class parses the ClassFile content and calls the + * appropriate visit methods of a given {@link ClassVisitor} for each field, method and bytecode + * instruction encountered. + * + * @see JVMS 4 + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public class ClassReader { + + /** + * A flag to skip the Code attributes. If this flag is set the Code attributes are neither parsed + * nor visited. + */ + public static final int SKIP_CODE = 1; + + /** + * A flag to skip the SourceFile, SourceDebugExtension, LocalVariableTable, LocalVariableTypeTable + * and LineNumberTable attributes. If this flag is set these attributes are neither parsed nor + * visited (i.e. {@link ClassVisitor#visitSource}, {@link MethodVisitor#visitLocalVariable} and + * {@link MethodVisitor#visitLineNumber} are not called). + */ + public static final int SKIP_DEBUG = 2; + + /** + * A flag to skip the StackMap and StackMapTable attributes. If this flag is set these attributes + * are neither parsed nor visited (i.e. {@link MethodVisitor#visitFrame} is not called). This flag + * is useful when the {@link ClassWriter#COMPUTE_FRAMES} option is used: it avoids visiting frames + * that will be ignored and recomputed from scratch. + */ + public static final int SKIP_FRAMES = 4; + + /** + * A flag to expand the stack map frames. By default stack map frames are visited in their + * original format (i.e. "expanded" for classes whose version is less than V1_6, and "compressed" + * for the other classes). If this flag is set, stack map frames are always visited in expanded + * format (this option adds a decompression/compression step in ClassReader and ClassWriter which + * degrades performance quite a lot). + */ + public static final int EXPAND_FRAMES = 8; + + /** + * A flag to expand the ASM specific instructions into an equivalent sequence of standard bytecode + * instructions. When resolving a forward jump it may happen that the signed 2 bytes offset + * reserved for it is not sufficient to store the bytecode offset. In this case the jump + * instruction is replaced with a temporary ASM specific instruction using an unsigned 2 bytes + * offset (see {@link Label#resolve}). This internal flag is used to re-read classes containing + * such instructions, in order to replace them with standard instructions. In addition, when this + * flag is used, goto_w and jsr_w are not converted into goto and jsr, to make sure that + * infinite loops where a goto_w is replaced with a goto in ClassReader and converted back to a + * goto_w in ClassWriter cannot occur. + */ + static final int EXPAND_ASM_INSNS = 256; + + /** The size of the temporary byte array used to read class input streams chunk by chunk. */ + private static final int INPUT_STREAM_DATA_CHUNK_SIZE = 4096; + + /** + * A byte array containing the JVMS ClassFile structure to be parsed. + * + * @deprecated Use {@link #readByte(int)} and the other read methods instead. This field will + * eventually be deleted. + */ + @Deprecated + // DontCheck(MemberName): can't be renamed (for backward binary compatibility). + public final byte[] b; + + /** + * A byte array containing the JVMS ClassFile structure to be parsed. The content of this array + * must not be modified. This field is intended for {@link Attribute} sub classes, and is normally + * not needed by class visitors. + * + *

NOTE: the ClassFile structure can start at any offset within this array, i.e. it does not + * necessarily start at offset 0. Use {@link #getItem} and {@link #header} to get correct + * ClassFile element offsets within this byte array. + */ + final byte[] classFileBuffer; + + /** + * The offset in bytes, in {@link #classFileBuffer}, of each cp_info entry of the ClassFile's + * constant_pool array, plus one. In other words, the offset of constant pool entry i is + * given by cpInfoOffsets[i] - 1, i.e. its cp_info's tag field is given by b[cpInfoOffsets[i] - + * 1]. + */ + private final int[] cpInfoOffsets; + + /** + * The String objects corresponding to the CONSTANT_Utf8 constant pool items. This cache avoids + * multiple parsing of a given CONSTANT_Utf8 constant pool item. + */ + private final String[] constantUtf8Values; + + /** + * The ConstantDynamic objects corresponding to the CONSTANT_Dynamic constant pool items. This + * cache avoids multiple parsing of a given CONSTANT_Dynamic constant pool item. + */ + private final ConstantDynamic[] constantDynamicValues; + + /** + * The start offsets in {@link #classFileBuffer} of each element of the bootstrap_methods array + * (in the BootstrapMethods attribute). + * + * @see JVMS + * 4.7.23 + */ + private final int[] bootstrapMethodOffsets; + + /** + * A conservative estimate of the maximum length of the strings contained in the constant pool of + * the class. + */ + private final int maxStringLength; + + /** The offset in bytes of the ClassFile's access_flags field. */ + public final int header; + + // ----------------------------------------------------------------------------------------------- + // Constructors + // ----------------------------------------------------------------------------------------------- + + /** + * Constructs a new {@link ClassReader} object. + * + * @param classFile the JVMS ClassFile structure to be read. + */ + public ClassReader(final byte[] classFile) { + this(classFile, 0, classFile.length); + } + + /** + * Constructs a new {@link ClassReader} object. + * + * @param classFileBuffer a byte array containing the JVMS ClassFile structure to be read. + * @param classFileOffset the offset in byteBuffer of the first byte of the ClassFile to be read. + * @param classFileLength the length in bytes of the ClassFile to be read. + */ + public ClassReader( + final byte[] classFileBuffer, + final int classFileOffset, + final int classFileLength) { // NOPMD(UnusedFormalParameter) used for backward compatibility. + this(classFileBuffer, classFileOffset, /* checkClassVersion = */ true); + } + + /** + * Constructs a new {@link ClassReader} object. This internal constructor must not be exposed + * as a public API. + * + * @param classFileBuffer a byte array containing the JVMS ClassFile structure to be read. + * @param classFileOffset the offset in byteBuffer of the first byte of the ClassFile to be read. + * @param checkClassVersion whether to check the class version or not. + */ + ClassReader( + final byte[] classFileBuffer, final int classFileOffset, final boolean checkClassVersion) { + this.classFileBuffer = classFileBuffer; + this.b = classFileBuffer; + // Check the class' major_version. This field is after the magic and minor_version fields, which + // use 4 and 2 bytes respectively. + if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V13) { + throw new IllegalArgumentException( + "Unsupported class file major version " + readShort(classFileOffset + 6)); + } + // Create the constant pool arrays. The constant_pool_count field is after the magic, + // minor_version and major_version fields, which use 4, 2 and 2 bytes respectively. + int constantPoolCount = readUnsignedShort(classFileOffset + 8); + cpInfoOffsets = new int[constantPoolCount]; + constantUtf8Values = new String[constantPoolCount]; + // Compute the offset of each constant pool entry, as well as a conservative estimate of the + // maximum length of the constant pool strings. The first constant pool entry is after the + // magic, minor_version, major_version and constant_pool_count fields, which use 4, 2, 2 and 2 + // bytes respectively. + int currentCpInfoIndex = 1; + int currentCpInfoOffset = classFileOffset + 10; + int currentMaxStringLength = 0; + boolean hasBootstrapMethods = false; + boolean hasConstantDynamic = false; + // The offset of the other entries depend on the total size of all the previous entries. + while (currentCpInfoIndex < constantPoolCount) { + cpInfoOffsets[currentCpInfoIndex++] = currentCpInfoOffset + 1; + int cpInfoSize; + switch (classFileBuffer[currentCpInfoOffset]) { + case Symbol.CONSTANT_FIELDREF_TAG: + case Symbol.CONSTANT_METHODREF_TAG: + case Symbol.CONSTANT_INTERFACE_METHODREF_TAG: + case Symbol.CONSTANT_INTEGER_TAG: + case Symbol.CONSTANT_FLOAT_TAG: + case Symbol.CONSTANT_NAME_AND_TYPE_TAG: + cpInfoSize = 5; + break; + case Symbol.CONSTANT_DYNAMIC_TAG: + cpInfoSize = 5; + hasBootstrapMethods = true; + hasConstantDynamic = true; + break; + case Symbol.CONSTANT_INVOKE_DYNAMIC_TAG: + cpInfoSize = 5; + hasBootstrapMethods = true; + break; + case Symbol.CONSTANT_LONG_TAG: + case Symbol.CONSTANT_DOUBLE_TAG: + cpInfoSize = 9; + currentCpInfoIndex++; + break; + case Symbol.CONSTANT_UTF8_TAG: + cpInfoSize = 3 + readUnsignedShort(currentCpInfoOffset + 1); + if (cpInfoSize > currentMaxStringLength) { + // The size in bytes of this CONSTANT_Utf8 structure provides a conservative estimate + // of the length in characters of the corresponding string, and is much cheaper to + // compute than this exact length. + currentMaxStringLength = cpInfoSize; + } + break; + case Symbol.CONSTANT_METHOD_HANDLE_TAG: + cpInfoSize = 4; + break; + case Symbol.CONSTANT_CLASS_TAG: + case Symbol.CONSTANT_STRING_TAG: + case Symbol.CONSTANT_METHOD_TYPE_TAG: + case Symbol.CONSTANT_PACKAGE_TAG: + case Symbol.CONSTANT_MODULE_TAG: + cpInfoSize = 3; + break; + default: + throw new IllegalArgumentException(); + } + currentCpInfoOffset += cpInfoSize; + } + maxStringLength = currentMaxStringLength; + // The Classfile's access_flags field is just after the last constant pool entry. + header = currentCpInfoOffset; + + // Allocate the cache of ConstantDynamic values, if there is at least one. + constantDynamicValues = hasConstantDynamic ? new ConstantDynamic[constantPoolCount] : null; + + // Read the BootstrapMethods attribute, if any (only get the offset of each method). + bootstrapMethodOffsets = + hasBootstrapMethods ? readBootstrapMethodsAttribute(currentMaxStringLength) : null; + } + + /** + * Constructs a new {@link ClassReader} object. + * + * @param inputStream an input stream of the JVMS ClassFile structure to be read. This input + * stream must contain nothing more than the ClassFile structure itself. It is read from its + * current position to its end. + * @throws IOException if a problem occurs during reading. + */ + public ClassReader(final InputStream inputStream) throws IOException { + this(readStream(inputStream, false)); + } + + /** + * Constructs a new {@link ClassReader} object. + * + * @param className the fully qualified name of the class to be read. The ClassFile structure is + * retrieved with the current class loader's {@link ClassLoader#getSystemResourceAsStream}. + * @throws IOException if an exception occurs during reading. + */ + public ClassReader(final String className) throws IOException { + this( + readStream( + ClassLoader.getSystemResourceAsStream(className.replace('.', '/') + ".class"), true)); + } + + /** + * Reads the given input stream and returns its content as a byte array. + * + * @param inputStream an input stream. + * @param close true to close the input stream after reading. + * @return the content of the given input stream. + * @throws IOException if a problem occurs during reading. + */ + private static byte[] readStream(final InputStream inputStream, final boolean close) + throws IOException { + if (inputStream == null) { + throw new IOException("Class not found"); + } + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + byte[] data = new byte[INPUT_STREAM_DATA_CHUNK_SIZE]; + int bytesRead; + while ((bytesRead = inputStream.read(data, 0, data.length)) != -1) { + outputStream.write(data, 0, bytesRead); + } + outputStream.flush(); + return outputStream.toByteArray(); + } finally { + if (close) { + inputStream.close(); + } + } + } + + // ----------------------------------------------------------------------------------------------- + // Accessors + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the class's access flags (see {@link Opcodes}). This value may not reflect Deprecated + * and Synthetic flags when bytecode is before 1.5 and those flags are represented by attributes. + * + * @return the class access flags. + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public int getAccess() { + return readUnsignedShort(header); + } + + /** + * Returns the internal name of the class (see {@link Type#getInternalName()}). + * + * @return the internal class name. + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public String getClassName() { + // this_class is just after the access_flags field (using 2 bytes). + return readClass(header + 2, new char[maxStringLength]); + } + + /** + * Returns the internal of name of the super class (see {@link Type#getInternalName()}). For + * interfaces, the super class is {@link Object}. + * + * @return the internal name of the super class, or {@literal null} for {@link Object} class. + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public String getSuperName() { + // super_class is after the access_flags and this_class fields (2 bytes each). + return readClass(header + 4, new char[maxStringLength]); + } + + /** + * Returns the internal names of the implemented interfaces (see {@link Type#getInternalName()}). + * + * @return the internal names of the directly implemented interfaces. Inherited implemented + * interfaces are not returned. + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public String[] getInterfaces() { + // interfaces_count is after the access_flags, this_class and super_class fields (2 bytes each). + int currentOffset = header + 6; + int interfacesCount = readUnsignedShort(currentOffset); + String[] interfaces = new String[interfacesCount]; + if (interfacesCount > 0) { + char[] charBuffer = new char[maxStringLength]; + for (int i = 0; i < interfacesCount; ++i) { + currentOffset += 2; + interfaces[i] = readClass(currentOffset, charBuffer); + } + } + return interfaces; + } + + // ----------------------------------------------------------------------------------------------- + // Public methods + // ----------------------------------------------------------------------------------------------- + + /** + * Makes the given visitor visit the JVMS ClassFile structure passed to the constructor of this + * {@link ClassReader}. + * + * @param classVisitor the visitor that must visit this class. + * @param parsingOptions the options to use to parse this class. One or more of {@link + * #SKIP_CODE}, {@link #SKIP_DEBUG}, {@link #SKIP_FRAMES} or {@link #EXPAND_FRAMES}. + */ + public void accept(final ClassVisitor classVisitor, final int parsingOptions) { + accept(classVisitor, new Attribute[0], parsingOptions); + } + + /** + * Makes the given visitor visit the JVMS ClassFile structure passed to the constructor of this + * {@link ClassReader}. + * + * @param classVisitor the visitor that must visit this class. + * @param attributePrototypes prototypes of the attributes that must be parsed during the visit of + * the class. Any attribute whose type is not equal to the type of one the prototypes will not + * be parsed: its byte array value will be passed unchanged to the ClassWriter. This may + * corrupt it if this value contains references to the constant pool, or has syntactic or + * semantic links with a class element that has been transformed by a class adapter between + * the reader and the writer. + * @param parsingOptions the options to use to parse this class. One or more of {@link + * #SKIP_CODE}, {@link #SKIP_DEBUG}, {@link #SKIP_FRAMES} or {@link #EXPAND_FRAMES}. + */ + public void accept( + final ClassVisitor classVisitor, + final Attribute[] attributePrototypes, + final int parsingOptions) { + Context context = new Context(); + context.attributePrototypes = attributePrototypes; + context.parsingOptions = parsingOptions; + context.charBuffer = new char[maxStringLength]; + + // Read the access_flags, this_class, super_class, interface_count and interfaces fields. + char[] charBuffer = context.charBuffer; + int currentOffset = header; + int accessFlags = readUnsignedShort(currentOffset); + String thisClass = readClass(currentOffset + 2, charBuffer); + String superClass = readClass(currentOffset + 4, charBuffer); + String[] interfaces = new String[readUnsignedShort(currentOffset + 6)]; + currentOffset += 8; + for (int i = 0; i < interfaces.length; ++i) { + interfaces[i] = readClass(currentOffset, charBuffer); + currentOffset += 2; + } + + // Read the class attributes (the variables are ordered as in Section 4.7 of the JVMS). + // Attribute offsets exclude the attribute_name_index and attribute_length fields. + // - The offset of the InnerClasses attribute, or 0. + int innerClassesOffset = 0; + // - The offset of the EnclosingMethod attribute, or 0. + int enclosingMethodOffset = 0; + // - The string corresponding to the Signature attribute, or null. + String signature = null; + // - The string corresponding to the SourceFile attribute, or null. + String sourceFile = null; + // - The string corresponding to the SourceDebugExtension attribute, or null. + String sourceDebugExtension = null; + // - The offset of the RuntimeVisibleAnnotations attribute, or 0. + int runtimeVisibleAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleAnnotations attribute, or 0. + int runtimeInvisibleAnnotationsOffset = 0; + // - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0. + int runtimeVisibleTypeAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0. + int runtimeInvisibleTypeAnnotationsOffset = 0; + // - The offset of the Module attribute, or 0. + int moduleOffset = 0; + // - The offset of the ModulePackages attribute, or 0. + int modulePackagesOffset = 0; + // - The string corresponding to the ModuleMainClass attribute, or null. + String moduleMainClass = null; + // - The string corresponding to the NestHost attribute, or null. + String nestHostClass = null; + // - The offset of the NestMembers attribute, or 0. + int nestMembersOffset = 0; + // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field). + // This list in the reverse order or their order in the ClassFile structure. + Attribute attributes = null; + + int currentAttributeOffset = getFirstAttributeOffset(); + for (int i = readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) { + // Read the attribute_info's attribute_name and attribute_length fields. + String attributeName = readUTF8(currentAttributeOffset, charBuffer); + int attributeLength = readInt(currentAttributeOffset + 2); + currentAttributeOffset += 6; + // The tests are sorted in decreasing frequency order (based on frequencies observed on + // typical classes). + if (Constants.SOURCE_FILE.equals(attributeName)) { + sourceFile = readUTF8(currentAttributeOffset, charBuffer); + } else if (Constants.INNER_CLASSES.equals(attributeName)) { + innerClassesOffset = currentAttributeOffset; + } else if (Constants.ENCLOSING_METHOD.equals(attributeName)) { + enclosingMethodOffset = currentAttributeOffset; + } else if (Constants.NEST_HOST.equals(attributeName)) { + nestHostClass = readClass(currentAttributeOffset, charBuffer); + } else if (Constants.NEST_MEMBERS.equals(attributeName)) { + nestMembersOffset = currentAttributeOffset; + } else if (Constants.SIGNATURE.equals(attributeName)) { + signature = readUTF8(currentAttributeOffset, charBuffer); + } else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleAnnotationsOffset = currentAttributeOffset; + } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleTypeAnnotationsOffset = currentAttributeOffset; + } else if (Constants.DEPRECATED.equals(attributeName)) { + accessFlags |= Opcodes.ACC_DEPRECATED; + } else if (Constants.SYNTHETIC.equals(attributeName)) { + accessFlags |= Opcodes.ACC_SYNTHETIC; + } else if (Constants.SOURCE_DEBUG_EXTENSION.equals(attributeName)) { + sourceDebugExtension = + readUtf(currentAttributeOffset, attributeLength, new char[attributeLength]); + } else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleAnnotationsOffset = currentAttributeOffset; + } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleTypeAnnotationsOffset = currentAttributeOffset; + } else if (Constants.MODULE.equals(attributeName)) { + moduleOffset = currentAttributeOffset; + } else if (Constants.MODULE_MAIN_CLASS.equals(attributeName)) { + moduleMainClass = readClass(currentAttributeOffset, charBuffer); + } else if (Constants.MODULE_PACKAGES.equals(attributeName)) { + modulePackagesOffset = currentAttributeOffset; + } else if (!Constants.BOOTSTRAP_METHODS.equals(attributeName)) { + // The BootstrapMethods attribute is read in the constructor. + Attribute attribute = + readAttribute( + attributePrototypes, + attributeName, + currentAttributeOffset, + attributeLength, + charBuffer, + -1, + null); + attribute.nextAttribute = attributes; + attributes = attribute; + } + currentAttributeOffset += attributeLength; + } + + // Visit the class declaration. The minor_version and major_version fields start 6 bytes before + // the first constant pool entry, which itself starts at cpInfoOffsets[1] - 1 (by definition). + classVisitor.visit( + readInt(cpInfoOffsets[1] - 7), accessFlags, thisClass, signature, superClass, interfaces); + + // Visit the SourceFile and SourceDebugExtenstion attributes. + if ((parsingOptions & SKIP_DEBUG) == 0 + && (sourceFile != null || sourceDebugExtension != null)) { + classVisitor.visitSource(sourceFile, sourceDebugExtension); + } + + // Visit the Module, ModulePackages and ModuleMainClass attributes. + if (moduleOffset != 0) { + readModuleAttributes( + classVisitor, context, moduleOffset, modulePackagesOffset, moduleMainClass); + } + + // Visit the NestHost attribute. + if (nestHostClass != null) { + classVisitor.visitNestHost(nestHostClass); + } + + // Visit the EnclosingMethod attribute. + if (enclosingMethodOffset != 0) { + String className = readClass(enclosingMethodOffset, charBuffer); + int methodIndex = readUnsignedShort(enclosingMethodOffset + 2); + String name = methodIndex == 0 ? null : readUTF8(cpInfoOffsets[methodIndex], charBuffer); + String type = methodIndex == 0 ? null : readUTF8(cpInfoOffsets[methodIndex] + 2, charBuffer); + classVisitor.visitOuterClass(className, name, type); + } + + // Visit the RuntimeVisibleAnnotations attribute. + if (runtimeVisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + classVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleAnnotations attribute. + if (runtimeInvisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + classVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeVisibleTypeAnnotations attribute. + if (runtimeVisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + classVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleTypeAnnotations attribute. + if (runtimeInvisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + classVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the non standard attributes. + while (attributes != null) { + // Copy and reset the nextAttribute field so that it can also be used in ClassWriter. + Attribute nextAttribute = attributes.nextAttribute; + attributes.nextAttribute = null; + classVisitor.visitAttribute(attributes); + attributes = nextAttribute; + } + + // Visit the NestedMembers attribute. + if (nestMembersOffset != 0) { + int numberOfNestMembers = readUnsignedShort(nestMembersOffset); + int currentNestMemberOffset = nestMembersOffset + 2; + while (numberOfNestMembers-- > 0) { + classVisitor.visitNestMember(readClass(currentNestMemberOffset, charBuffer)); + currentNestMemberOffset += 2; + } + } + + // Visit the InnerClasses attribute. + if (innerClassesOffset != 0) { + int numberOfClasses = readUnsignedShort(innerClassesOffset); + int currentClassesOffset = innerClassesOffset + 2; + while (numberOfClasses-- > 0) { + classVisitor.visitInnerClass( + readClass(currentClassesOffset, charBuffer), + readClass(currentClassesOffset + 2, charBuffer), + readUTF8(currentClassesOffset + 4, charBuffer), + readUnsignedShort(currentClassesOffset + 6)); + currentClassesOffset += 8; + } + } + + // Visit the fields and methods. + int fieldsCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (fieldsCount-- > 0) { + currentOffset = readField(classVisitor, context, currentOffset); + } + int methodsCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (methodsCount-- > 0) { + currentOffset = readMethod(classVisitor, context, currentOffset); + } + + // Visit the end of the class. + classVisitor.visitEnd(); + } + + // ---------------------------------------------------------------------------------------------- + // Methods to parse modules, fields and methods + // ---------------------------------------------------------------------------------------------- + + /** + * Reads the Module, ModulePackages and ModuleMainClass attributes and visit them. + * + * @param classVisitor the current class visitor + * @param context information about the class being parsed. + * @param moduleOffset the offset of the Module attribute (excluding the attribute_info's + * attribute_name_index and attribute_length fields). + * @param modulePackagesOffset the offset of the ModulePackages attribute (excluding the + * attribute_info's attribute_name_index and attribute_length fields), or 0. + * @param moduleMainClass the string corresponding to the ModuleMainClass attribute, or null. + */ + private void readModuleAttributes( + final ClassVisitor classVisitor, + final Context context, + final int moduleOffset, + final int modulePackagesOffset, + final String moduleMainClass) { + char[] buffer = context.charBuffer; + + // Read the module_name_index, module_flags and module_version_index fields and visit them. + int currentOffset = moduleOffset; + String moduleName = readModule(currentOffset, buffer); + int moduleFlags = readUnsignedShort(currentOffset + 2); + String moduleVersion = readUTF8(currentOffset + 4, buffer); + currentOffset += 6; + ModuleVisitor moduleVisitor = classVisitor.visitModule(moduleName, moduleFlags, moduleVersion); + if (moduleVisitor == null) { + return; + } + + // Visit the ModuleMainClass attribute. + if (moduleMainClass != null) { + moduleVisitor.visitMainClass(moduleMainClass); + } + + // Visit the ModulePackages attribute. + if (modulePackagesOffset != 0) { + int packageCount = readUnsignedShort(modulePackagesOffset); + int currentPackageOffset = modulePackagesOffset + 2; + while (packageCount-- > 0) { + moduleVisitor.visitPackage(readPackage(currentPackageOffset, buffer)); + currentPackageOffset += 2; + } + } + + // Read the 'requires_count' and 'requires' fields. + int requiresCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (requiresCount-- > 0) { + // Read the requires_index, requires_flags and requires_version fields and visit them. + String requires = readModule(currentOffset, buffer); + int requiresFlags = readUnsignedShort(currentOffset + 2); + String requiresVersion = readUTF8(currentOffset + 4, buffer); + currentOffset += 6; + moduleVisitor.visitRequire(requires, requiresFlags, requiresVersion); + } + + // Read the 'exports_count' and 'exports' fields. + int exportsCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (exportsCount-- > 0) { + // Read the exports_index, exports_flags, exports_to_count and exports_to_index fields + // and visit them. + String exports = readPackage(currentOffset, buffer); + int exportsFlags = readUnsignedShort(currentOffset + 2); + int exportsToCount = readUnsignedShort(currentOffset + 4); + currentOffset += 6; + String[] exportsTo = null; + if (exportsToCount != 0) { + exportsTo = new String[exportsToCount]; + for (int i = 0; i < exportsToCount; ++i) { + exportsTo[i] = readModule(currentOffset, buffer); + currentOffset += 2; + } + } + moduleVisitor.visitExport(exports, exportsFlags, exportsTo); + } + + // Reads the 'opens_count' and 'opens' fields. + int opensCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (opensCount-- > 0) { + // Read the opens_index, opens_flags, opens_to_count and opens_to_index fields and visit them. + String opens = readPackage(currentOffset, buffer); + int opensFlags = readUnsignedShort(currentOffset + 2); + int opensToCount = readUnsignedShort(currentOffset + 4); + currentOffset += 6; + String[] opensTo = null; + if (opensToCount != 0) { + opensTo = new String[opensToCount]; + for (int i = 0; i < opensToCount; ++i) { + opensTo[i] = readModule(currentOffset, buffer); + currentOffset += 2; + } + } + moduleVisitor.visitOpen(opens, opensFlags, opensTo); + } + + // Read the 'uses_count' and 'uses' fields. + int usesCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (usesCount-- > 0) { + moduleVisitor.visitUse(readClass(currentOffset, buffer)); + currentOffset += 2; + } + + // Read the 'provides_count' and 'provides' fields. + int providesCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (providesCount-- > 0) { + // Read the provides_index, provides_with_count and provides_with_index fields and visit them. + String provides = readClass(currentOffset, buffer); + int providesWithCount = readUnsignedShort(currentOffset + 2); + currentOffset += 4; + String[] providesWith = new String[providesWithCount]; + for (int i = 0; i < providesWithCount; ++i) { + providesWith[i] = readClass(currentOffset, buffer); + currentOffset += 2; + } + moduleVisitor.visitProvide(provides, providesWith); + } + + // Visit the end of the module attributes. + moduleVisitor.visitEnd(); + } + + /** + * Reads a JVMS field_info structure and makes the given visitor visit it. + * + * @param classVisitor the visitor that must visit the field. + * @param context information about the class being parsed. + * @param fieldInfoOffset the start offset of the field_info structure. + * @return the offset of the first byte following the field_info structure. + */ + private int readField( + final ClassVisitor classVisitor, final Context context, final int fieldInfoOffset) { + char[] charBuffer = context.charBuffer; + + // Read the access_flags, name_index and descriptor_index fields. + int currentOffset = fieldInfoOffset; + int accessFlags = readUnsignedShort(currentOffset); + String name = readUTF8(currentOffset + 2, charBuffer); + String descriptor = readUTF8(currentOffset + 4, charBuffer); + currentOffset += 6; + + // Read the field attributes (the variables are ordered as in Section 4.7 of the JVMS). + // Attribute offsets exclude the attribute_name_index and attribute_length fields. + // - The value corresponding to the ConstantValue attribute, or null. + Object constantValue = null; + // - The string corresponding to the Signature attribute, or null. + String signature = null; + // - The offset of the RuntimeVisibleAnnotations attribute, or 0. + int runtimeVisibleAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleAnnotations attribute, or 0. + int runtimeInvisibleAnnotationsOffset = 0; + // - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0. + int runtimeVisibleTypeAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0. + int runtimeInvisibleTypeAnnotationsOffset = 0; + // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field). + // This list in the reverse order or their order in the ClassFile structure. + Attribute attributes = null; + + int attributesCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (attributesCount-- > 0) { + // Read the attribute_info's attribute_name and attribute_length fields. + String attributeName = readUTF8(currentOffset, charBuffer); + int attributeLength = readInt(currentOffset + 2); + currentOffset += 6; + // The tests are sorted in decreasing frequency order (based on frequencies observed on + // typical classes). + if (Constants.CONSTANT_VALUE.equals(attributeName)) { + int constantvalueIndex = readUnsignedShort(currentOffset); + constantValue = constantvalueIndex == 0 ? null : readConst(constantvalueIndex, charBuffer); + } else if (Constants.SIGNATURE.equals(attributeName)) { + signature = readUTF8(currentOffset, charBuffer); + } else if (Constants.DEPRECATED.equals(attributeName)) { + accessFlags |= Opcodes.ACC_DEPRECATED; + } else if (Constants.SYNTHETIC.equals(attributeName)) { + accessFlags |= Opcodes.ACC_SYNTHETIC; + } else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleTypeAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleTypeAnnotationsOffset = currentOffset; + } else { + Attribute attribute = + readAttribute( + context.attributePrototypes, + attributeName, + currentOffset, + attributeLength, + charBuffer, + -1, + null); + attribute.nextAttribute = attributes; + attributes = attribute; + } + currentOffset += attributeLength; + } + + // Visit the field declaration. + FieldVisitor fieldVisitor = + classVisitor.visitField(accessFlags, name, descriptor, signature, constantValue); + if (fieldVisitor == null) { + return currentOffset; + } + + // Visit the RuntimeVisibleAnnotations attribute. + if (runtimeVisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + fieldVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleAnnotations attribute. + if (runtimeInvisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + fieldVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeVisibleTypeAnnotations attribute. + if (runtimeVisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + fieldVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleTypeAnnotations attribute. + if (runtimeInvisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + fieldVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the non standard attributes. + while (attributes != null) { + // Copy and reset the nextAttribute field so that it can also be used in FieldWriter. + Attribute nextAttribute = attributes.nextAttribute; + attributes.nextAttribute = null; + fieldVisitor.visitAttribute(attributes); + attributes = nextAttribute; + } + + // Visit the end of the field. + fieldVisitor.visitEnd(); + return currentOffset; + } + + /** + * Reads a JVMS method_info structure and makes the given visitor visit it. + * + * @param classVisitor the visitor that must visit the method. + * @param context information about the class being parsed. + * @param methodInfoOffset the start offset of the method_info structure. + * @return the offset of the first byte following the method_info structure. + */ + private int readMethod( + final ClassVisitor classVisitor, final Context context, final int methodInfoOffset) { + char[] charBuffer = context.charBuffer; + + // Read the access_flags, name_index and descriptor_index fields. + int currentOffset = methodInfoOffset; + context.currentMethodAccessFlags = readUnsignedShort(currentOffset); + context.currentMethodName = readUTF8(currentOffset + 2, charBuffer); + context.currentMethodDescriptor = readUTF8(currentOffset + 4, charBuffer); + currentOffset += 6; + + // Read the method attributes (the variables are ordered as in Section 4.7 of the JVMS). + // Attribute offsets exclude the attribute_name_index and attribute_length fields. + // - The offset of the Code attribute, or 0. + int codeOffset = 0; + // - The offset of the Exceptions attribute, or 0. + int exceptionsOffset = 0; + // - The strings corresponding to the Exceptions attribute, or null. + String[] exceptions = null; + // - Whether the method has a Synthetic attribute. + boolean synthetic = false; + // - The constant pool index contained in the Signature attribute, or 0. + int signatureIndex = 0; + // - The offset of the RuntimeVisibleAnnotations attribute, or 0. + int runtimeVisibleAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleAnnotations attribute, or 0. + int runtimeInvisibleAnnotationsOffset = 0; + // - The offset of the RuntimeVisibleParameterAnnotations attribute, or 0. + int runtimeVisibleParameterAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleParameterAnnotations attribute, or 0. + int runtimeInvisibleParameterAnnotationsOffset = 0; + // - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0. + int runtimeVisibleTypeAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0. + int runtimeInvisibleTypeAnnotationsOffset = 0; + // - The offset of the AnnotationDefault attribute, or 0. + int annotationDefaultOffset = 0; + // - The offset of the MethodParameters attribute, or 0. + int methodParametersOffset = 0; + // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field). + // This list in the reverse order or their order in the ClassFile structure. + Attribute attributes = null; + + int attributesCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (attributesCount-- > 0) { + // Read the attribute_info's attribute_name and attribute_length fields. + String attributeName = readUTF8(currentOffset, charBuffer); + int attributeLength = readInt(currentOffset + 2); + currentOffset += 6; + // The tests are sorted in decreasing frequency order (based on frequencies observed on + // typical classes). + if (Constants.CODE.equals(attributeName)) { + if ((context.parsingOptions & SKIP_CODE) == 0) { + codeOffset = currentOffset; + } + } else if (Constants.EXCEPTIONS.equals(attributeName)) { + exceptionsOffset = currentOffset; + exceptions = new String[readUnsignedShort(exceptionsOffset)]; + int currentExceptionOffset = exceptionsOffset + 2; + for (int i = 0; i < exceptions.length; ++i) { + exceptions[i] = readClass(currentExceptionOffset, charBuffer); + currentExceptionOffset += 2; + } + } else if (Constants.SIGNATURE.equals(attributeName)) { + signatureIndex = readUnsignedShort(currentOffset); + } else if (Constants.DEPRECATED.equals(attributeName)) { + context.currentMethodAccessFlags |= Opcodes.ACC_DEPRECATED; + } else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleTypeAnnotationsOffset = currentOffset; + } else if (Constants.ANNOTATION_DEFAULT.equals(attributeName)) { + annotationDefaultOffset = currentOffset; + } else if (Constants.SYNTHETIC.equals(attributeName)) { + synthetic = true; + context.currentMethodAccessFlags |= Opcodes.ACC_SYNTHETIC; + } else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleTypeAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleParameterAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleParameterAnnotationsOffset = currentOffset; + } else if (Constants.METHOD_PARAMETERS.equals(attributeName)) { + methodParametersOffset = currentOffset; + } else { + Attribute attribute = + readAttribute( + context.attributePrototypes, + attributeName, + currentOffset, + attributeLength, + charBuffer, + -1, + null); + attribute.nextAttribute = attributes; + attributes = attribute; + } + currentOffset += attributeLength; + } + + // Visit the method declaration. + MethodVisitor methodVisitor = + classVisitor.visitMethod( + context.currentMethodAccessFlags, + context.currentMethodName, + context.currentMethodDescriptor, + signatureIndex == 0 ? null : readUtf(signatureIndex, charBuffer), + exceptions); + if (methodVisitor == null) { + return currentOffset; + } + + // If the returned MethodVisitor is in fact a MethodWriter, it means there is no method + // adapter between the reader and the writer. In this case, it might be possible to copy + // the method attributes directly into the writer. If so, return early without visiting + // the content of these attributes. + if (methodVisitor instanceof MethodWriter) { + MethodWriter methodWriter = (MethodWriter) methodVisitor; + if (methodWriter.canCopyMethodAttributes( + this, + synthetic, + (context.currentMethodAccessFlags & Opcodes.ACC_DEPRECATED) != 0, + readUnsignedShort(methodInfoOffset + 4), + signatureIndex, + exceptionsOffset)) { + methodWriter.setMethodAttributesSource(methodInfoOffset, currentOffset - methodInfoOffset); + return currentOffset; + } + } + + // Visit the MethodParameters attribute. + if (methodParametersOffset != 0) { + int parametersCount = readByte(methodParametersOffset); + int currentParameterOffset = methodParametersOffset + 1; + while (parametersCount-- > 0) { + // Read the name_index and access_flags fields and visit them. + methodVisitor.visitParameter( + readUTF8(currentParameterOffset, charBuffer), + readUnsignedShort(currentParameterOffset + 2)); + currentParameterOffset += 4; + } + } + + // Visit the AnnotationDefault attribute. + if (annotationDefaultOffset != 0) { + AnnotationVisitor annotationVisitor = methodVisitor.visitAnnotationDefault(); + readElementValue(annotationVisitor, annotationDefaultOffset, null, charBuffer); + if (annotationVisitor != null) { + annotationVisitor.visitEnd(); + } + } + + // Visit the RuntimeVisibleAnnotations attribute. + if (runtimeVisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + methodVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleAnnotations attribute. + if (runtimeInvisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + methodVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeVisibleTypeAnnotations attribute. + if (runtimeVisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + methodVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleTypeAnnotations attribute. + if (runtimeInvisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + methodVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeVisibleParameterAnnotations attribute. + if (runtimeVisibleParameterAnnotationsOffset != 0) { + readParameterAnnotations( + methodVisitor, context, runtimeVisibleParameterAnnotationsOffset, /* visible = */ true); + } + + // Visit the RuntimeInvisibleParameterAnnotations attribute. + if (runtimeInvisibleParameterAnnotationsOffset != 0) { + readParameterAnnotations( + methodVisitor, + context, + runtimeInvisibleParameterAnnotationsOffset, + /* visible = */ false); + } + + // Visit the non standard attributes. + while (attributes != null) { + // Copy and reset the nextAttribute field so that it can also be used in MethodWriter. + Attribute nextAttribute = attributes.nextAttribute; + attributes.nextAttribute = null; + methodVisitor.visitAttribute(attributes); + attributes = nextAttribute; + } + + // Visit the Code attribute. + if (codeOffset != 0) { + methodVisitor.visitCode(); + readCode(methodVisitor, context, codeOffset); + } + + // Visit the end of the method. + methodVisitor.visitEnd(); + return currentOffset; + } + + // ---------------------------------------------------------------------------------------------- + // Methods to parse a Code attribute + // ---------------------------------------------------------------------------------------------- + + /** + * Reads a JVMS 'Code' attribute and makes the given visitor visit it. + * + * @param methodVisitor the visitor that must visit the Code attribute. + * @param context information about the class being parsed. + * @param codeOffset the start offset in {@link #classFileBuffer} of the Code attribute, excluding + * its attribute_name_index and attribute_length fields. + */ + private void readCode( + final MethodVisitor methodVisitor, final Context context, final int codeOffset) { + int currentOffset = codeOffset; + + // Read the max_stack, max_locals and code_length fields. + final byte[] classBuffer = classFileBuffer; + final char[] charBuffer = context.charBuffer; + final int majorVersion = readUnsignedShort(6); + final int minorVersion = readUnsignedShort(4); + + final int maxStack; + final int maxLocals; + final int codeLength; + + if (majorVersion == 45 && minorVersion <= 2) { + maxStack = readByte(currentOffset); + maxLocals = readByte(currentOffset + 1); + codeLength = readUnsignedShort(currentOffset + 2); + currentOffset += 4; + } else { + maxStack = readUnsignedShort(currentOffset); + maxLocals = readUnsignedShort(currentOffset + 2); + codeLength = readInt(currentOffset + 4); + currentOffset += 8; + } + + // Read the bytecode 'code' array to create a label for each referenced instruction. + final int bytecodeStartOffset = currentOffset; + final int bytecodeEndOffset = currentOffset + codeLength; + final Label[] labels = context.currentMethodLabels = new Label[codeLength + 1]; + while (currentOffset < bytecodeEndOffset) { + final int bytecodeOffset = currentOffset - bytecodeStartOffset; + final int opcode = classBuffer[currentOffset] & 0xFF; + switch (opcode) { + case Constants.NOP: + case Constants.ACONST_NULL: + case Constants.ICONST_M1: + case Constants.ICONST_0: + case Constants.ICONST_1: + case Constants.ICONST_2: + case Constants.ICONST_3: + case Constants.ICONST_4: + case Constants.ICONST_5: + case Constants.LCONST_0: + case Constants.LCONST_1: + case Constants.FCONST_0: + case Constants.FCONST_1: + case Constants.FCONST_2: + case Constants.DCONST_0: + case Constants.DCONST_1: + case Constants.IALOAD: + case Constants.LALOAD: + case Constants.FALOAD: + case Constants.DALOAD: + case Constants.AALOAD: + case Constants.BALOAD: + case Constants.CALOAD: + case Constants.SALOAD: + case Constants.IASTORE: + case Constants.LASTORE: + case Constants.FASTORE: + case Constants.DASTORE: + case Constants.AASTORE: + case Constants.BASTORE: + case Constants.CASTORE: + case Constants.SASTORE: + case Constants.POP: + case Constants.POP2: + case Constants.DUP: + case Constants.DUP_X1: + case Constants.DUP_X2: + case Constants.DUP2: + case Constants.DUP2_X1: + case Constants.DUP2_X2: + case Constants.SWAP: + case Constants.IADD: + case Constants.LADD: + case Constants.FADD: + case Constants.DADD: + case Constants.ISUB: + case Constants.LSUB: + case Constants.FSUB: + case Constants.DSUB: + case Constants.IMUL: + case Constants.LMUL: + case Constants.FMUL: + case Constants.DMUL: + case Constants.IDIV: + case Constants.LDIV: + case Constants.FDIV: + case Constants.DDIV: + case Constants.IREM: + case Constants.LREM: + case Constants.FREM: + case Constants.DREM: + case Constants.INEG: + case Constants.LNEG: + case Constants.FNEG: + case Constants.DNEG: + case Constants.ISHL: + case Constants.LSHL: + case Constants.ISHR: + case Constants.LSHR: + case Constants.IUSHR: + case Constants.LUSHR: + case Constants.IAND: + case Constants.LAND: + case Constants.IOR: + case Constants.LOR: + case Constants.IXOR: + case Constants.LXOR: + case Constants.I2L: + case Constants.I2F: + case Constants.I2D: + case Constants.L2I: + case Constants.L2F: + case Constants.L2D: + case Constants.F2I: + case Constants.F2L: + case Constants.F2D: + case Constants.D2I: + case Constants.D2L: + case Constants.D2F: + case Constants.I2B: + case Constants.I2C: + case Constants.I2S: + case Constants.LCMP: + case Constants.FCMPL: + case Constants.FCMPG: + case Constants.DCMPL: + case Constants.DCMPG: + case Constants.IRETURN: + case Constants.LRETURN: + case Constants.FRETURN: + case Constants.DRETURN: + case Constants.ARETURN: + case Constants.RETURN: + case Constants.ARRAYLENGTH: + case Constants.ATHROW: + case Constants.MONITORENTER: + case Constants.MONITOREXIT: + case Constants.ILOAD_0: + case Constants.ILOAD_1: + case Constants.ILOAD_2: + case Constants.ILOAD_3: + case Constants.LLOAD_0: + case Constants.LLOAD_1: + case Constants.LLOAD_2: + case Constants.LLOAD_3: + case Constants.FLOAD_0: + case Constants.FLOAD_1: + case Constants.FLOAD_2: + case Constants.FLOAD_3: + case Constants.DLOAD_0: + case Constants.DLOAD_1: + case Constants.DLOAD_2: + case Constants.DLOAD_3: + case Constants.ALOAD_0: + case Constants.ALOAD_1: + case Constants.ALOAD_2: + case Constants.ALOAD_3: + case Constants.ISTORE_0: + case Constants.ISTORE_1: + case Constants.ISTORE_2: + case Constants.ISTORE_3: + case Constants.LSTORE_0: + case Constants.LSTORE_1: + case Constants.LSTORE_2: + case Constants.LSTORE_3: + case Constants.FSTORE_0: + case Constants.FSTORE_1: + case Constants.FSTORE_2: + case Constants.FSTORE_3: + case Constants.DSTORE_0: + case Constants.DSTORE_1: + case Constants.DSTORE_2: + case Constants.DSTORE_3: + case Constants.ASTORE_0: + case Constants.ASTORE_1: + case Constants.ASTORE_2: + case Constants.ASTORE_3: + currentOffset += 1; + break; + case Constants.IFEQ: + case Constants.IFNE: + case Constants.IFLT: + case Constants.IFGE: + case Constants.IFGT: + case Constants.IFLE: + case Constants.IF_ICMPEQ: + case Constants.IF_ICMPNE: + case Constants.IF_ICMPLT: + case Constants.IF_ICMPGE: + case Constants.IF_ICMPGT: + case Constants.IF_ICMPLE: + case Constants.IF_ACMPEQ: + case Constants.IF_ACMPNE: + case Constants.GOTO: + case Constants.JSR: + case Constants.IFNULL: + case Constants.IFNONNULL: + createLabel(bytecodeOffset + readShort(currentOffset + 1), labels); + currentOffset += 3; + break; + case Constants.ASM_IFEQ: + case Constants.ASM_IFNE: + case Constants.ASM_IFLT: + case Constants.ASM_IFGE: + case Constants.ASM_IFGT: + case Constants.ASM_IFLE: + case Constants.ASM_IF_ICMPEQ: + case Constants.ASM_IF_ICMPNE: + case Constants.ASM_IF_ICMPLT: + case Constants.ASM_IF_ICMPGE: + case Constants.ASM_IF_ICMPGT: + case Constants.ASM_IF_ICMPLE: + case Constants.ASM_IF_ACMPEQ: + case Constants.ASM_IF_ACMPNE: + case Constants.ASM_GOTO: + case Constants.ASM_JSR: + case Constants.ASM_IFNULL: + case Constants.ASM_IFNONNULL: + createLabel(bytecodeOffset + readUnsignedShort(currentOffset + 1), labels); + currentOffset += 3; + break; + case Constants.GOTO_W: + case Constants.JSR_W: + case Constants.ASM_GOTO_W: + createLabel(bytecodeOffset + readInt(currentOffset + 1), labels); + currentOffset += 5; + break; + case Constants.WIDE: + switch (classBuffer[currentOffset + 1] & 0xFF) { + case Constants.ILOAD: + case Constants.FLOAD: + case Constants.ALOAD: + case Constants.LLOAD: + case Constants.DLOAD: + case Constants.ISTORE: + case Constants.FSTORE: + case Constants.ASTORE: + case Constants.LSTORE: + case Constants.DSTORE: + case Constants.RET: + currentOffset += 4; + break; + case Constants.IINC: + currentOffset += 6; + break; + default: + throw new IllegalArgumentException(); + } + break; + case Constants.TABLESWITCH: + // Skip 0 to 3 padding bytes. + currentOffset += 4 - (bytecodeOffset & 3); + // Read the default label and the number of table entries. + createLabel(bytecodeOffset + readInt(currentOffset), labels); + int numTableEntries = readInt(currentOffset + 8) - readInt(currentOffset + 4) + 1; + currentOffset += 12; + // Read the table labels. + while (numTableEntries-- > 0) { + createLabel(bytecodeOffset + readInt(currentOffset), labels); + currentOffset += 4; + } + break; + case Constants.LOOKUPSWITCH: + // Skip 0 to 3 padding bytes. + currentOffset += 4 - (bytecodeOffset & 3); + // Read the default label and the number of switch cases. + createLabel(bytecodeOffset + readInt(currentOffset), labels); + int numSwitchCases = readInt(currentOffset + 4); + currentOffset += 8; + // Read the switch labels. + while (numSwitchCases-- > 0) { + createLabel(bytecodeOffset + readInt(currentOffset + 4), labels); + currentOffset += 8; + } + break; + case Constants.ILOAD: + case Constants.LLOAD: + case Constants.FLOAD: + case Constants.DLOAD: + case Constants.ALOAD: + case Constants.ISTORE: + case Constants.LSTORE: + case Constants.FSTORE: + case Constants.DSTORE: + case Constants.ASTORE: + case Constants.RET: + case Constants.BIPUSH: + case Constants.NEWARRAY: + case Constants.LDC: + currentOffset += 2; + break; + case Constants.SIPUSH: + case Constants.LDC_W: + case Constants.LDC2_W: + case Constants.GETSTATIC: + case Constants.PUTSTATIC: + case Constants.GETFIELD: + case Constants.PUTFIELD: + case Constants.INVOKEVIRTUAL: + case Constants.INVOKESPECIAL: + case Constants.INVOKESTATIC: + case Constants.NEW: + case Constants.ANEWARRAY: + case Constants.CHECKCAST: + case Constants.INSTANCEOF: + case Constants.IINC: + currentOffset += 3; + break; + case Constants.INVOKEINTERFACE: + case Constants.INVOKEDYNAMIC: + currentOffset += 5; + break; + case Constants.MULTIANEWARRAY: + currentOffset += 4; + break; + default: + throw new IllegalArgumentException(); + } + } + + // Read the 'exception_table_length' and 'exception_table' field to create a label for each + // referenced instruction, and to make methodVisitor visit the corresponding try catch blocks. + int exceptionTableLength = readUnsignedShort(currentOffset); + currentOffset += 2; + while (exceptionTableLength-- > 0) { + Label start = createLabel(readUnsignedShort(currentOffset), labels); + Label end = createLabel(readUnsignedShort(currentOffset + 2), labels); + Label handler = createLabel(readUnsignedShort(currentOffset + 4), labels); + String catchType = readUTF8(cpInfoOffsets[readUnsignedShort(currentOffset + 6)], charBuffer); + currentOffset += 8; + methodVisitor.visitTryCatchBlock(start, end, handler, catchType); + } + + // Read the Code attributes to create a label for each referenced instruction (the variables + // are ordered as in Section 4.7 of the JVMS). Attribute offsets exclude the + // attribute_name_index and attribute_length fields. + // - The offset of the current 'stack_map_frame' in the StackMap[Table] attribute, or 0. + // Initially, this is the offset of the first 'stack_map_frame' entry. Then this offset is + // updated after each stack_map_frame is read. + int stackMapFrameOffset = 0; + // - The end offset of the StackMap[Table] attribute, or 0. + int stackMapTableEndOffset = 0; + // - Whether the stack map frames are compressed (i.e. in a StackMapTable) or not. + boolean compressedFrames = true; + // - The offset of the LocalVariableTable attribute, or 0. + int localVariableTableOffset = 0; + // - The offset of the LocalVariableTypeTable attribute, or 0. + int localVariableTypeTableOffset = 0; + // - The offset of each 'type_annotation' entry in the RuntimeVisibleTypeAnnotations + // attribute, or null. + int[] visibleTypeAnnotationOffsets = null; + // - The offset of each 'type_annotation' entry in the RuntimeInvisibleTypeAnnotations + // attribute, or null. + int[] invisibleTypeAnnotationOffsets = null; + // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field). + // This list in the reverse order or their order in the ClassFile structure. + Attribute attributes = null; + + int attributesCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (attributesCount-- > 0) { + // Read the attribute_info's attribute_name and attribute_length fields. + String attributeName = readUTF8(currentOffset, charBuffer); + int attributeLength = readInt(currentOffset + 2); + currentOffset += 6; + if (Constants.LOCAL_VARIABLE_TABLE.equals(attributeName)) { + if ((context.parsingOptions & SKIP_DEBUG) == 0) { + localVariableTableOffset = currentOffset; + // Parse the attribute to find the corresponding (debug only) labels. + int currentLocalVariableTableOffset = currentOffset; + int localVariableTableLength = readUnsignedShort(currentLocalVariableTableOffset); + currentLocalVariableTableOffset += 2; + while (localVariableTableLength-- > 0) { + int startPc = readUnsignedShort(currentLocalVariableTableOffset); + createDebugLabel(startPc, labels); + int length = readUnsignedShort(currentLocalVariableTableOffset + 2); + createDebugLabel(startPc + length, labels); + // Skip the name_index, descriptor_index and index fields (2 bytes each). + currentLocalVariableTableOffset += 10; + } + } + } else if (Constants.LOCAL_VARIABLE_TYPE_TABLE.equals(attributeName)) { + localVariableTypeTableOffset = currentOffset; + // Here we do not extract the labels corresponding to the attribute content. We assume they + // are the same or a subset of those of the LocalVariableTable attribute. + } else if (Constants.LINE_NUMBER_TABLE.equals(attributeName)) { + if ((context.parsingOptions & SKIP_DEBUG) == 0) { + // Parse the attribute to find the corresponding (debug only) labels. + int currentLineNumberTableOffset = currentOffset; + int lineNumberTableLength = readUnsignedShort(currentLineNumberTableOffset); + currentLineNumberTableOffset += 2; + while (lineNumberTableLength-- > 0) { + int startPc = readUnsignedShort(currentLineNumberTableOffset); + int lineNumber = readUnsignedShort(currentLineNumberTableOffset + 2); + currentLineNumberTableOffset += 4; + createDebugLabel(startPc, labels); + labels[startPc].addLineNumber(lineNumber); + } + } + } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + visibleTypeAnnotationOffsets = + readTypeAnnotations(methodVisitor, context, currentOffset, /* visible = */ true); + // Here we do not extract the labels corresponding to the attribute content. This would + // require a full parsing of the attribute, which would need to be repeated when parsing + // the bytecode instructions (see below). Instead, the content of the attribute is read one + // type annotation at a time (i.e. after a type annotation has been visited, the next type + // annotation is read), and the labels it contains are also extracted one annotation at a + // time. This assumes that type annotations are ordered by increasing bytecode offset. + } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + invisibleTypeAnnotationOffsets = + readTypeAnnotations(methodVisitor, context, currentOffset, /* visible = */ false); + // Same comment as above for the RuntimeVisibleTypeAnnotations attribute. + } else if (Constants.STACK_MAP_TABLE.equals(attributeName)) { + if ((context.parsingOptions & SKIP_FRAMES) == 0) { + stackMapFrameOffset = currentOffset + 2; + stackMapTableEndOffset = currentOffset + attributeLength; + } + // Here we do not extract the labels corresponding to the attribute content. This would + // require a full parsing of the attribute, which would need to be repeated when parsing + // the bytecode instructions (see below). Instead, the content of the attribute is read one + // frame at a time (i.e. after a frame has been visited, the next frame is read), and the + // labels it contains are also extracted one frame at a time. Thanks to the ordering of + // frames, having only a "one frame lookahead" is not a problem, i.e. it is not possible to + // see an offset smaller than the offset of the current instruction and for which no Label + // exist. Except for UNINITIALIZED type offsets. We solve this by parsing the stack map + // table without a full decoding (see below). + } else if ("StackMap".equals(attributeName)) { + if ((context.parsingOptions & SKIP_FRAMES) == 0) { + stackMapFrameOffset = currentOffset + 2; + stackMapTableEndOffset = currentOffset + attributeLength; + compressedFrames = false; + } + // IMPORTANT! Here we assume that the frames are ordered, as in the StackMapTable attribute, + // although this is not guaranteed by the attribute format. This allows an incremental + // extraction of the labels corresponding to this attribute (see the comment above for the + // StackMapTable attribute). + } else { + Attribute attribute = + readAttribute( + context.attributePrototypes, + attributeName, + currentOffset, + attributeLength, + charBuffer, + codeOffset, + labels); + attribute.nextAttribute = attributes; + attributes = attribute; + } + currentOffset += attributeLength; + } + + // Initialize the context fields related to stack map frames, and generate the first + // (implicit) stack map frame, if needed. + final boolean expandFrames = (context.parsingOptions & EXPAND_FRAMES) != 0; + if (stackMapFrameOffset != 0) { + // The bytecode offset of the first explicit frame is not offset_delta + 1 but only + // offset_delta. Setting the implicit frame offset to -1 allows us to use of the + // "offset_delta + 1" rule in all cases. + context.currentFrameOffset = -1; + context.currentFrameType = 0; + context.currentFrameLocalCount = 0; + context.currentFrameLocalCountDelta = 0; + context.currentFrameLocalTypes = new Object[maxLocals]; + context.currentFrameStackCount = 0; + context.currentFrameStackTypes = new Object[maxStack]; + if (expandFrames) { + computeImplicitFrame(context); + } + // Find the labels for UNINITIALIZED frame types. Instead of decoding each element of the + // stack map table, we look for 3 consecutive bytes that "look like" an UNINITIALIZED type + // (tag ITEM_Uninitialized, offset within bytecode bounds, NEW instruction at this offset). + // We may find false positives (i.e. not real UNINITIALIZED types), but this should be rare, + // and the only consequence will be the creation of an unneeded label. This is better than + // creating a label for each NEW instruction, and faster than fully decoding the whole stack + // map table. + for (int offset = stackMapFrameOffset; offset < stackMapTableEndOffset - 2; ++offset) { + if (classBuffer[offset] == Frame.ITEM_UNINITIALIZED) { + int potentialBytecodeOffset = readUnsignedShort(offset + 1); + if (potentialBytecodeOffset >= 0 + && potentialBytecodeOffset < codeLength + && (classBuffer[bytecodeStartOffset + potentialBytecodeOffset] & 0xFF) + == Opcodes.NEW) { + createLabel(potentialBytecodeOffset, labels); + } + } + } + } + if (expandFrames && (context.parsingOptions & EXPAND_ASM_INSNS) != 0) { + // Expanding the ASM specific instructions can introduce F_INSERT frames, even if the method + // does not currently have any frame. These inserted frames must be computed by simulating the + // effect of the bytecode instructions, one by one, starting from the implicit first frame. + // For this, MethodWriter needs to know maxLocals before the first instruction is visited. To + // ensure this, we visit the implicit first frame here (passing only maxLocals - the rest is + // computed in MethodWriter). + methodVisitor.visitFrame(Opcodes.F_NEW, maxLocals, null, 0, null); + } + + // Visit the bytecode instructions. First, introduce state variables for the incremental parsing + // of the type annotations. + + // Index of the next runtime visible type annotation to read (in the + // visibleTypeAnnotationOffsets array). + int currentVisibleTypeAnnotationIndex = 0; + // The bytecode offset of the next runtime visible type annotation to read, or -1. + int currentVisibleTypeAnnotationBytecodeOffset = + getTypeAnnotationBytecodeOffset(visibleTypeAnnotationOffsets, 0); + // Index of the next runtime invisible type annotation to read (in the + // invisibleTypeAnnotationOffsets array). + int currentInvisibleTypeAnnotationIndex = 0; + // The bytecode offset of the next runtime invisible type annotation to read, or -1. + int currentInvisibleTypeAnnotationBytecodeOffset = + getTypeAnnotationBytecodeOffset(invisibleTypeAnnotationOffsets, 0); + + // Whether a F_INSERT stack map frame must be inserted before the current instruction. + boolean insertFrame = false; + + // The delta to subtract from a goto_w or jsr_w opcode to get the corresponding goto or jsr + // opcode, or 0 if goto_w and jsr_w must be left unchanged (i.e. when expanding ASM specific + // instructions). + final int wideJumpOpcodeDelta = + (context.parsingOptions & EXPAND_ASM_INSNS) == 0 ? Constants.WIDE_JUMP_OPCODE_DELTA : 0; + + currentOffset = bytecodeStartOffset; + while (currentOffset < bytecodeEndOffset) { + final int currentBytecodeOffset = currentOffset - bytecodeStartOffset; + + // Visit the label and the line number(s) for this bytecode offset, if any. + Label currentLabel = labels[currentBytecodeOffset]; + if (currentLabel != null) { + currentLabel.accept(methodVisitor, (context.parsingOptions & SKIP_DEBUG) == 0); + } + + // Visit the stack map frame for this bytecode offset, if any. + while (stackMapFrameOffset != 0 + && (context.currentFrameOffset == currentBytecodeOffset + || context.currentFrameOffset == -1)) { + // If there is a stack map frame for this offset, make methodVisitor visit it, and read the + // next stack map frame if there is one. + if (context.currentFrameOffset != -1) { + if (!compressedFrames || expandFrames) { + methodVisitor.visitFrame( + Opcodes.F_NEW, + context.currentFrameLocalCount, + context.currentFrameLocalTypes, + context.currentFrameStackCount, + context.currentFrameStackTypes); + } else { + methodVisitor.visitFrame( + context.currentFrameType, + context.currentFrameLocalCountDelta, + context.currentFrameLocalTypes, + context.currentFrameStackCount, + context.currentFrameStackTypes); + } + // Since there is already a stack map frame for this bytecode offset, there is no need to + // insert a new one. + insertFrame = false; + } + if (stackMapFrameOffset < stackMapTableEndOffset) { + stackMapFrameOffset = + readStackMapFrame(stackMapFrameOffset, compressedFrames, expandFrames, context); + } else { + stackMapFrameOffset = 0; + } + } + + // Insert a stack map frame for this bytecode offset, if requested by setting insertFrame to + // true during the previous iteration. The actual frame content is computed in MethodWriter. + if (insertFrame) { + if ((context.parsingOptions & EXPAND_FRAMES) != 0) { + methodVisitor.visitFrame(Constants.F_INSERT, 0, null, 0, null); + } + insertFrame = false; + } + + // Visit the instruction at this bytecode offset. + int opcode = classBuffer[currentOffset] & 0xFF; + switch (opcode) { + case Constants.NOP: + case Constants.ACONST_NULL: + case Constants.ICONST_M1: + case Constants.ICONST_0: + case Constants.ICONST_1: + case Constants.ICONST_2: + case Constants.ICONST_3: + case Constants.ICONST_4: + case Constants.ICONST_5: + case Constants.LCONST_0: + case Constants.LCONST_1: + case Constants.FCONST_0: + case Constants.FCONST_1: + case Constants.FCONST_2: + case Constants.DCONST_0: + case Constants.DCONST_1: + case Constants.IALOAD: + case Constants.LALOAD: + case Constants.FALOAD: + case Constants.DALOAD: + case Constants.AALOAD: + case Constants.BALOAD: + case Constants.CALOAD: + case Constants.SALOAD: + case Constants.IASTORE: + case Constants.LASTORE: + case Constants.FASTORE: + case Constants.DASTORE: + case Constants.AASTORE: + case Constants.BASTORE: + case Constants.CASTORE: + case Constants.SASTORE: + case Constants.POP: + case Constants.POP2: + case Constants.DUP: + case Constants.DUP_X1: + case Constants.DUP_X2: + case Constants.DUP2: + case Constants.DUP2_X1: + case Constants.DUP2_X2: + case Constants.SWAP: + case Constants.IADD: + case Constants.LADD: + case Constants.FADD: + case Constants.DADD: + case Constants.ISUB: + case Constants.LSUB: + case Constants.FSUB: + case Constants.DSUB: + case Constants.IMUL: + case Constants.LMUL: + case Constants.FMUL: + case Constants.DMUL: + case Constants.IDIV: + case Constants.LDIV: + case Constants.FDIV: + case Constants.DDIV: + case Constants.IREM: + case Constants.LREM: + case Constants.FREM: + case Constants.DREM: + case Constants.INEG: + case Constants.LNEG: + case Constants.FNEG: + case Constants.DNEG: + case Constants.ISHL: + case Constants.LSHL: + case Constants.ISHR: + case Constants.LSHR: + case Constants.IUSHR: + case Constants.LUSHR: + case Constants.IAND: + case Constants.LAND: + case Constants.IOR: + case Constants.LOR: + case Constants.IXOR: + case Constants.LXOR: + case Constants.I2L: + case Constants.I2F: + case Constants.I2D: + case Constants.L2I: + case Constants.L2F: + case Constants.L2D: + case Constants.F2I: + case Constants.F2L: + case Constants.F2D: + case Constants.D2I: + case Constants.D2L: + case Constants.D2F: + case Constants.I2B: + case Constants.I2C: + case Constants.I2S: + case Constants.LCMP: + case Constants.FCMPL: + case Constants.FCMPG: + case Constants.DCMPL: + case Constants.DCMPG: + case Constants.IRETURN: + case Constants.LRETURN: + case Constants.FRETURN: + case Constants.DRETURN: + case Constants.ARETURN: + case Constants.RETURN: + case Constants.ARRAYLENGTH: + case Constants.ATHROW: + case Constants.MONITORENTER: + case Constants.MONITOREXIT: + methodVisitor.visitInsn(opcode); + currentOffset += 1; + break; + case Constants.ILOAD_0: + case Constants.ILOAD_1: + case Constants.ILOAD_2: + case Constants.ILOAD_3: + case Constants.LLOAD_0: + case Constants.LLOAD_1: + case Constants.LLOAD_2: + case Constants.LLOAD_3: + case Constants.FLOAD_0: + case Constants.FLOAD_1: + case Constants.FLOAD_2: + case Constants.FLOAD_3: + case Constants.DLOAD_0: + case Constants.DLOAD_1: + case Constants.DLOAD_2: + case Constants.DLOAD_3: + case Constants.ALOAD_0: + case Constants.ALOAD_1: + case Constants.ALOAD_2: + case Constants.ALOAD_3: + opcode -= Constants.ILOAD_0; + methodVisitor.visitVarInsn(Opcodes.ILOAD + (opcode >> 2), opcode & 0x3); + currentOffset += 1; + break; + case Constants.ISTORE_0: + case Constants.ISTORE_1: + case Constants.ISTORE_2: + case Constants.ISTORE_3: + case Constants.LSTORE_0: + case Constants.LSTORE_1: + case Constants.LSTORE_2: + case Constants.LSTORE_3: + case Constants.FSTORE_0: + case Constants.FSTORE_1: + case Constants.FSTORE_2: + case Constants.FSTORE_3: + case Constants.DSTORE_0: + case Constants.DSTORE_1: + case Constants.DSTORE_2: + case Constants.DSTORE_3: + case Constants.ASTORE_0: + case Constants.ASTORE_1: + case Constants.ASTORE_2: + case Constants.ASTORE_3: + opcode -= Constants.ISTORE_0; + methodVisitor.visitVarInsn(Opcodes.ISTORE + (opcode >> 2), opcode & 0x3); + currentOffset += 1; + break; + case Constants.IFEQ: + case Constants.IFNE: + case Constants.IFLT: + case Constants.IFGE: + case Constants.IFGT: + case Constants.IFLE: + case Constants.IF_ICMPEQ: + case Constants.IF_ICMPNE: + case Constants.IF_ICMPLT: + case Constants.IF_ICMPGE: + case Constants.IF_ICMPGT: + case Constants.IF_ICMPLE: + case Constants.IF_ACMPEQ: + case Constants.IF_ACMPNE: + case Constants.GOTO: + case Constants.JSR: + case Constants.IFNULL: + case Constants.IFNONNULL: + methodVisitor.visitJumpInsn( + opcode, labels[currentBytecodeOffset + readShort(currentOffset + 1)]); + currentOffset += 3; + break; + case Constants.GOTO_W: + case Constants.JSR_W: + methodVisitor.visitJumpInsn( + opcode - wideJumpOpcodeDelta, + labels[currentBytecodeOffset + readInt(currentOffset + 1)]); + currentOffset += 5; + break; + case Constants.ASM_IFEQ: + case Constants.ASM_IFNE: + case Constants.ASM_IFLT: + case Constants.ASM_IFGE: + case Constants.ASM_IFGT: + case Constants.ASM_IFLE: + case Constants.ASM_IF_ICMPEQ: + case Constants.ASM_IF_ICMPNE: + case Constants.ASM_IF_ICMPLT: + case Constants.ASM_IF_ICMPGE: + case Constants.ASM_IF_ICMPGT: + case Constants.ASM_IF_ICMPLE: + case Constants.ASM_IF_ACMPEQ: + case Constants.ASM_IF_ACMPNE: + case Constants.ASM_GOTO: + case Constants.ASM_JSR: + case Constants.ASM_IFNULL: + case Constants.ASM_IFNONNULL: + { + // A forward jump with an offset > 32767. In this case we automatically replace ASM_GOTO + // with GOTO_W, ASM_JSR with JSR_W and ASM_IFxxx with IFNOTxxx GOTO_W L:..., + // where IFNOTxxx is the "opposite" opcode of ASMS_IFxxx (e.g. IFNE for ASM_IFEQ) and + // where designates the instruction just after the GOTO_W. + // First, change the ASM specific opcodes ASM_IFEQ ... ASM_JSR, ASM_IFNULL and + // ASM_IFNONNULL to IFEQ ... JSR, IFNULL and IFNONNULL. + opcode = + opcode < Constants.ASM_IFNULL + ? opcode - Constants.ASM_OPCODE_DELTA + : opcode - Constants.ASM_IFNULL_OPCODE_DELTA; + Label target = labels[currentBytecodeOffset + readUnsignedShort(currentOffset + 1)]; + if (opcode == Opcodes.GOTO || opcode == Opcodes.JSR) { + // Replace GOTO with GOTO_W and JSR with JSR_W. + methodVisitor.visitJumpInsn(opcode + Constants.WIDE_JUMP_OPCODE_DELTA, target); + } else { + // Compute the "opposite" of opcode. This can be done by flipping the least + // significant bit for IFNULL and IFNONNULL, and similarly for IFEQ ... IF_ACMPEQ + // (with a pre and post offset by 1). + opcode = opcode < Opcodes.GOTO ? ((opcode + 1) ^ 1) - 1 : opcode ^ 1; + Label endif = createLabel(currentBytecodeOffset + 3, labels); + methodVisitor.visitJumpInsn(opcode, endif); + methodVisitor.visitJumpInsn(Constants.GOTO_W, target); + // endif designates the instruction just after GOTO_W, and is visited as part of the + // next instruction. Since it is a jump target, we need to insert a frame here. + insertFrame = true; + } + currentOffset += 3; + break; + } + case Constants.ASM_GOTO_W: + // Replace ASM_GOTO_W with GOTO_W. + methodVisitor.visitJumpInsn( + Constants.GOTO_W, labels[currentBytecodeOffset + readInt(currentOffset + 1)]); + // The instruction just after is a jump target (because ASM_GOTO_W is used in patterns + // IFNOTxxx ASM_GOTO_W L:..., see MethodWriter), so we need to insert a frame + // here. + insertFrame = true; + currentOffset += 5; + break; + case Constants.WIDE: + opcode = classBuffer[currentOffset + 1] & 0xFF; + if (opcode == Opcodes.IINC) { + methodVisitor.visitIincInsn( + readUnsignedShort(currentOffset + 2), readShort(currentOffset + 4)); + currentOffset += 6; + } else { + methodVisitor.visitVarInsn(opcode, readUnsignedShort(currentOffset + 2)); + currentOffset += 4; + } + break; + case Constants.TABLESWITCH: + { + // Skip 0 to 3 padding bytes. + currentOffset += 4 - (currentBytecodeOffset & 3); + // Read the instruction. + Label defaultLabel = labels[currentBytecodeOffset + readInt(currentOffset)]; + int low = readInt(currentOffset + 4); + int high = readInt(currentOffset + 8); + currentOffset += 12; + Label[] table = new Label[high - low + 1]; + for (int i = 0; i < table.length; ++i) { + table[i] = labels[currentBytecodeOffset + readInt(currentOffset)]; + currentOffset += 4; + } + methodVisitor.visitTableSwitchInsn(low, high, defaultLabel, table); + break; + } + case Constants.LOOKUPSWITCH: + { + // Skip 0 to 3 padding bytes. + currentOffset += 4 - (currentBytecodeOffset & 3); + // Read the instruction. + Label defaultLabel = labels[currentBytecodeOffset + readInt(currentOffset)]; + int numPairs = readInt(currentOffset + 4); + currentOffset += 8; + int[] keys = new int[numPairs]; + Label[] values = new Label[numPairs]; + for (int i = 0; i < numPairs; ++i) { + keys[i] = readInt(currentOffset); + values[i] = labels[currentBytecodeOffset + readInt(currentOffset + 4)]; + currentOffset += 8; + } + methodVisitor.visitLookupSwitchInsn(defaultLabel, keys, values); + break; + } + case Constants.ILOAD: + case Constants.LLOAD: + case Constants.FLOAD: + case Constants.DLOAD: + case Constants.ALOAD: + case Constants.ISTORE: + case Constants.LSTORE: + case Constants.FSTORE: + case Constants.DSTORE: + case Constants.ASTORE: + case Constants.RET: + methodVisitor.visitVarInsn(opcode, classBuffer[currentOffset + 1] & 0xFF); + currentOffset += 2; + break; + case Constants.BIPUSH: + case Constants.NEWARRAY: + methodVisitor.visitIntInsn(opcode, classBuffer[currentOffset + 1]); + currentOffset += 2; + break; + case Constants.SIPUSH: + methodVisitor.visitIntInsn(opcode, readShort(currentOffset + 1)); + currentOffset += 3; + break; + case Constants.LDC: + methodVisitor.visitLdcInsn(readConst(classBuffer[currentOffset + 1] & 0xFF, charBuffer)); + currentOffset += 2; + break; + case Constants.LDC_W: + case Constants.LDC2_W: + methodVisitor.visitLdcInsn(readConst(readUnsignedShort(currentOffset + 1), charBuffer)); + currentOffset += 3; + break; + case Constants.GETSTATIC: + case Constants.PUTSTATIC: + case Constants.GETFIELD: + case Constants.PUTFIELD: + case Constants.INVOKEVIRTUAL: + case Constants.INVOKESPECIAL: + case Constants.INVOKESTATIC: + case Constants.INVOKEINTERFACE: + { + int cpInfoOffset = cpInfoOffsets[readUnsignedShort(currentOffset + 1)]; + int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 2)]; + String owner = readClass(cpInfoOffset, charBuffer); + String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer); + String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer); + if (opcode < Opcodes.INVOKEVIRTUAL) { + methodVisitor.visitFieldInsn(opcode, owner, name, descriptor); + } else { + boolean isInterface = + classBuffer[cpInfoOffset - 1] == Symbol.CONSTANT_INTERFACE_METHODREF_TAG; + methodVisitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + if (opcode == Opcodes.INVOKEINTERFACE) { + currentOffset += 5; + } else { + currentOffset += 3; + } + break; + } + case Constants.INVOKEDYNAMIC: + { + int cpInfoOffset = cpInfoOffsets[readUnsignedShort(currentOffset + 1)]; + int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 2)]; + String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer); + String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer); + int bootstrapMethodOffset = bootstrapMethodOffsets[readUnsignedShort(cpInfoOffset)]; + Handle handle = + (Handle) readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer); + Object[] bootstrapMethodArguments = + new Object[readUnsignedShort(bootstrapMethodOffset + 2)]; + bootstrapMethodOffset += 4; + for (int i = 0; i < bootstrapMethodArguments.length; i++) { + bootstrapMethodArguments[i] = + readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer); + bootstrapMethodOffset += 2; + } + methodVisitor.visitInvokeDynamicInsn( + name, descriptor, handle, bootstrapMethodArguments); + currentOffset += 5; + break; + } + case Constants.NEW: + case Constants.ANEWARRAY: + case Constants.CHECKCAST: + case Constants.INSTANCEOF: + methodVisitor.visitTypeInsn(opcode, readClass(currentOffset + 1, charBuffer)); + currentOffset += 3; + break; + case Constants.IINC: + methodVisitor.visitIincInsn( + classBuffer[currentOffset + 1] & 0xFF, classBuffer[currentOffset + 2]); + currentOffset += 3; + break; + case Constants.MULTIANEWARRAY: + methodVisitor.visitMultiANewArrayInsn( + readClass(currentOffset + 1, charBuffer), classBuffer[currentOffset + 3] & 0xFF); + currentOffset += 4; + break; + default: + throw new AssertionError(); + } + + // Visit the runtime visible instruction annotations, if any. + while (visibleTypeAnnotationOffsets != null + && currentVisibleTypeAnnotationIndex < visibleTypeAnnotationOffsets.length + && currentVisibleTypeAnnotationBytecodeOffset <= currentBytecodeOffset) { + if (currentVisibleTypeAnnotationBytecodeOffset == currentBytecodeOffset) { + // Parse the target_type, target_info and target_path fields. + int currentAnnotationOffset = + readTypeAnnotationTarget( + context, visibleTypeAnnotationOffsets[currentVisibleTypeAnnotationIndex]); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + readElementValues( + methodVisitor.visitInsnAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + currentVisibleTypeAnnotationBytecodeOffset = + getTypeAnnotationBytecodeOffset( + visibleTypeAnnotationOffsets, ++currentVisibleTypeAnnotationIndex); + } + + // Visit the runtime invisible instruction annotations, if any. + while (invisibleTypeAnnotationOffsets != null + && currentInvisibleTypeAnnotationIndex < invisibleTypeAnnotationOffsets.length + && currentInvisibleTypeAnnotationBytecodeOffset <= currentBytecodeOffset) { + if (currentInvisibleTypeAnnotationBytecodeOffset == currentBytecodeOffset) { + // Parse the target_type, target_info and target_path fields. + int currentAnnotationOffset = + readTypeAnnotationTarget( + context, invisibleTypeAnnotationOffsets[currentInvisibleTypeAnnotationIndex]); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + readElementValues( + methodVisitor.visitInsnAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + currentInvisibleTypeAnnotationBytecodeOffset = + getTypeAnnotationBytecodeOffset( + invisibleTypeAnnotationOffsets, ++currentInvisibleTypeAnnotationIndex); + } + } + if (labels[codeLength] != null) { + methodVisitor.visitLabel(labels[codeLength]); + } + + // Visit LocalVariableTable and LocalVariableTypeTable attributes. + if (localVariableTableOffset != 0 && (context.parsingOptions & SKIP_DEBUG) == 0) { + // The (start_pc, index, signature_index) fields of each entry of the LocalVariableTypeTable. + int[] typeTable = null; + if (localVariableTypeTableOffset != 0) { + typeTable = new int[readUnsignedShort(localVariableTypeTableOffset) * 3]; + currentOffset = localVariableTypeTableOffset + 2; + int typeTableIndex = typeTable.length; + while (typeTableIndex > 0) { + // Store the offset of 'signature_index', and the value of 'index' and 'start_pc'. + typeTable[--typeTableIndex] = currentOffset + 6; + typeTable[--typeTableIndex] = readUnsignedShort(currentOffset + 8); + typeTable[--typeTableIndex] = readUnsignedShort(currentOffset); + currentOffset += 10; + } + } + int localVariableTableLength = readUnsignedShort(localVariableTableOffset); + currentOffset = localVariableTableOffset + 2; + while (localVariableTableLength-- > 0) { + int startPc = readUnsignedShort(currentOffset); + int length = readUnsignedShort(currentOffset + 2); + String name = readUTF8(currentOffset + 4, charBuffer); + String descriptor = readUTF8(currentOffset + 6, charBuffer); + int index = readUnsignedShort(currentOffset + 8); + currentOffset += 10; + String signature = null; + if (typeTable != null) { + for (int i = 0; i < typeTable.length; i += 3) { + if (typeTable[i] == startPc && typeTable[i + 1] == index) { + signature = readUTF8(typeTable[i + 2], charBuffer); + break; + } + } + } + methodVisitor.visitLocalVariable( + name, descriptor, signature, labels[startPc], labels[startPc + length], index); + } + } + + // Visit the local variable type annotations of the RuntimeVisibleTypeAnnotations attribute. + if (visibleTypeAnnotationOffsets != null) { + for (int typeAnnotationOffset : visibleTypeAnnotationOffsets) { + int targetType = readByte(typeAnnotationOffset); + if (targetType == TypeReference.LOCAL_VARIABLE + || targetType == TypeReference.RESOURCE_VARIABLE) { + // Parse the target_type, target_info and target_path fields. + currentOffset = readTypeAnnotationTarget(context, typeAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentOffset, charBuffer); + currentOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + readElementValues( + methodVisitor.visitLocalVariableAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + context.currentLocalVariableAnnotationRangeStarts, + context.currentLocalVariableAnnotationRangeEnds, + context.currentLocalVariableAnnotationRangeIndices, + annotationDescriptor, + /* visible = */ true), + currentOffset, + /* named = */ true, + charBuffer); + } + } + } + + // Visit the local variable type annotations of the RuntimeInvisibleTypeAnnotations attribute. + if (invisibleTypeAnnotationOffsets != null) { + for (int typeAnnotationOffset : invisibleTypeAnnotationOffsets) { + int targetType = readByte(typeAnnotationOffset); + if (targetType == TypeReference.LOCAL_VARIABLE + || targetType == TypeReference.RESOURCE_VARIABLE) { + // Parse the target_type, target_info and target_path fields. + currentOffset = readTypeAnnotationTarget(context, typeAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentOffset, charBuffer); + currentOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + readElementValues( + methodVisitor.visitLocalVariableAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + context.currentLocalVariableAnnotationRangeStarts, + context.currentLocalVariableAnnotationRangeEnds, + context.currentLocalVariableAnnotationRangeIndices, + annotationDescriptor, + /* visible = */ false), + currentOffset, + /* named = */ true, + charBuffer); + } + } + } + + // Visit the non standard attributes. + while (attributes != null) { + // Copy and reset the nextAttribute field so that it can also be used in MethodWriter. + Attribute nextAttribute = attributes.nextAttribute; + attributes.nextAttribute = null; + methodVisitor.visitAttribute(attributes); + attributes = nextAttribute; + } + + // Visit the max stack and max locals values. + methodVisitor.visitMaxs(maxStack, maxLocals); + } + + /** + * Returns the label corresponding to the given bytecode offset. The default implementation of + * this method creates a label for the given offset if it has not been already created. + * + * @param bytecodeOffset a bytecode offset in a method. + * @param labels the already created labels, indexed by their offset. If a label already exists + * for bytecodeOffset this method must not create a new one. Otherwise it must store the new + * label in this array. + * @return a non null Label, which must be equal to labels[bytecodeOffset]. + */ + protected Label readLabel(final int bytecodeOffset, final Label[] labels) { + if (labels[bytecodeOffset] == null) { + labels[bytecodeOffset] = new Label(); + } + return labels[bytecodeOffset]; + } + + /** + * Creates a label without the {@link Label#FLAG_DEBUG_ONLY} flag set, for the given bytecode + * offset. The label is created with a call to {@link #readLabel} and its {@link + * Label#FLAG_DEBUG_ONLY} flag is cleared. + * + * @param bytecodeOffset a bytecode offset in a method. + * @param labels the already created labels, indexed by their offset. + * @return a Label without the {@link Label#FLAG_DEBUG_ONLY} flag set. + */ + private Label createLabel(final int bytecodeOffset, final Label[] labels) { + Label label = readLabel(bytecodeOffset, labels); + label.flags &= ~Label.FLAG_DEBUG_ONLY; + return label; + } + + /** + * Creates a label with the {@link Label#FLAG_DEBUG_ONLY} flag set, if there is no already + * existing label for the given bytecode offset (otherwise does nothing). The label is created + * with a call to {@link #readLabel}. + * + * @param bytecodeOffset a bytecode offset in a method. + * @param labels the already created labels, indexed by their offset. + */ + private void createDebugLabel(final int bytecodeOffset, final Label[] labels) { + if (labels[bytecodeOffset] == null) { + readLabel(bytecodeOffset, labels).flags |= Label.FLAG_DEBUG_ONLY; + } + } + + // ---------------------------------------------------------------------------------------------- + // Methods to parse annotations, type annotations and parameter annotations + // ---------------------------------------------------------------------------------------------- + + /** + * Parses a Runtime[In]VisibleTypeAnnotations attribute to find the offset of each type_annotation + * entry it contains, to find the corresponding labels, and to visit the try catch block + * annotations. + * + * @param methodVisitor the method visitor to be used to visit the try catch block annotations. + * @param context information about the class being parsed. + * @param runtimeTypeAnnotationsOffset the start offset of a Runtime[In]VisibleTypeAnnotations + * attribute, excluding the attribute_info's attribute_name_index and attribute_length fields. + * @param visible true if the attribute to parse is a RuntimeVisibleTypeAnnotations attribute, + * false it is a RuntimeInvisibleTypeAnnotations attribute. + * @return the start offset of each entry of the Runtime[In]VisibleTypeAnnotations_attribute's + * 'annotations' array field. + */ + private int[] readTypeAnnotations( + final MethodVisitor methodVisitor, + final Context context, + final int runtimeTypeAnnotationsOffset, + final boolean visible) { + char[] charBuffer = context.charBuffer; + int currentOffset = runtimeTypeAnnotationsOffset; + // Read the num_annotations field and create an array to store the type_annotation offsets. + int[] typeAnnotationsOffsets = new int[readUnsignedShort(currentOffset)]; + currentOffset += 2; + // Parse the 'annotations' array field. + for (int i = 0; i < typeAnnotationsOffsets.length; ++i) { + typeAnnotationsOffsets[i] = currentOffset; + // Parse the type_annotation's target_type and the target_info fields. The size of the + // target_info field depends on the value of target_type. + int targetType = readInt(currentOffset); + switch (targetType >>> 24) { + case TypeReference.LOCAL_VARIABLE: + case TypeReference.RESOURCE_VARIABLE: + // A localvar_target has a variable size, which depends on the value of their table_length + // field. It also references bytecode offsets, for which we need labels. + int tableLength = readUnsignedShort(currentOffset + 1); + currentOffset += 3; + while (tableLength-- > 0) { + int startPc = readUnsignedShort(currentOffset); + int length = readUnsignedShort(currentOffset + 2); + // Skip the index field (2 bytes). + currentOffset += 6; + createLabel(startPc, context.currentMethodLabels); + createLabel(startPc + length, context.currentMethodLabels); + } + break; + case TypeReference.CAST: + case TypeReference.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT: + case TypeReference.METHOD_INVOCATION_TYPE_ARGUMENT: + case TypeReference.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT: + case TypeReference.METHOD_REFERENCE_TYPE_ARGUMENT: + currentOffset += 4; + break; + case TypeReference.CLASS_EXTENDS: + case TypeReference.CLASS_TYPE_PARAMETER_BOUND: + case TypeReference.METHOD_TYPE_PARAMETER_BOUND: + case TypeReference.THROWS: + case TypeReference.EXCEPTION_PARAMETER: + case TypeReference.INSTANCEOF: + case TypeReference.NEW: + case TypeReference.CONSTRUCTOR_REFERENCE: + case TypeReference.METHOD_REFERENCE: + currentOffset += 3; + break; + case TypeReference.CLASS_TYPE_PARAMETER: + case TypeReference.METHOD_TYPE_PARAMETER: + case TypeReference.METHOD_FORMAL_PARAMETER: + case TypeReference.FIELD: + case TypeReference.METHOD_RETURN: + case TypeReference.METHOD_RECEIVER: + default: + // TypeReference type which can't be used in Code attribute, or which is unknown. + throw new IllegalArgumentException(); + } + // Parse the rest of the type_annotation structure, starting with the target_path structure + // (whose size depends on its path_length field). + int pathLength = readByte(currentOffset); + if ((targetType >>> 24) == TypeReference.EXCEPTION_PARAMETER) { + // Parse the target_path structure and create a corresponding TypePath. + TypePath path = pathLength == 0 ? null : new TypePath(classFileBuffer, currentOffset); + currentOffset += 1 + 2 * pathLength; + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentOffset, charBuffer); + currentOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentOffset = + readElementValues( + methodVisitor.visitTryCatchAnnotation( + targetType & 0xFFFFFF00, path, annotationDescriptor, visible), + currentOffset, + /* named = */ true, + charBuffer); + } else { + // We don't want to visit the other target_type annotations, so we just skip them (which + // requires some parsing because the element_value_pairs array has a variable size). First, + // skip the target_path structure: + currentOffset += 3 + 2 * pathLength; + // Then skip the num_element_value_pairs and element_value_pairs fields (by reading them + // with a null AnnotationVisitor). + currentOffset = + readElementValues( + /* annotationVisitor = */ null, currentOffset, /* named = */ true, charBuffer); + } + } + return typeAnnotationsOffsets; + } + + /** + * Returns the bytecode offset corresponding to the specified JVMS 'type_annotation' structure, or + * -1 if there is no such type_annotation of if it does not have a bytecode offset. + * + * @param typeAnnotationOffsets the offset of each 'type_annotation' entry in a + * Runtime[In]VisibleTypeAnnotations attribute, or null. + * @param typeAnnotationIndex the index a 'type_annotation' entry in typeAnnotationOffsets. + * @return bytecode offset corresponding to the specified JVMS 'type_annotation' structure, or -1 + * if there is no such type_annotation of if it does not have a bytecode offset. + */ + private int getTypeAnnotationBytecodeOffset( + final int[] typeAnnotationOffsets, final int typeAnnotationIndex) { + if (typeAnnotationOffsets == null + || typeAnnotationIndex >= typeAnnotationOffsets.length + || readByte(typeAnnotationOffsets[typeAnnotationIndex]) < TypeReference.INSTANCEOF) { + return -1; + } + return readUnsignedShort(typeAnnotationOffsets[typeAnnotationIndex] + 1); + } + + /** + * Parses the header of a JVMS type_annotation structure to extract its target_type, target_info + * and target_path (the result is stored in the given context), and returns the start offset of + * the rest of the type_annotation structure. + * + * @param context information about the class being parsed. This is where the extracted + * target_type and target_path must be stored. + * @param typeAnnotationOffset the start offset of a type_annotation structure. + * @return the start offset of the rest of the type_annotation structure. + */ + private int readTypeAnnotationTarget(final Context context, final int typeAnnotationOffset) { + int currentOffset = typeAnnotationOffset; + // Parse and store the target_type structure. + int targetType = readInt(typeAnnotationOffset); + switch (targetType >>> 24) { + case TypeReference.CLASS_TYPE_PARAMETER: + case TypeReference.METHOD_TYPE_PARAMETER: + case TypeReference.METHOD_FORMAL_PARAMETER: + targetType &= 0xFFFF0000; + currentOffset += 2; + break; + case TypeReference.FIELD: + case TypeReference.METHOD_RETURN: + case TypeReference.METHOD_RECEIVER: + targetType &= 0xFF000000; + currentOffset += 1; + break; + case TypeReference.LOCAL_VARIABLE: + case TypeReference.RESOURCE_VARIABLE: + targetType &= 0xFF000000; + int tableLength = readUnsignedShort(currentOffset + 1); + currentOffset += 3; + context.currentLocalVariableAnnotationRangeStarts = new Label[tableLength]; + context.currentLocalVariableAnnotationRangeEnds = new Label[tableLength]; + context.currentLocalVariableAnnotationRangeIndices = new int[tableLength]; + for (int i = 0; i < tableLength; ++i) { + int startPc = readUnsignedShort(currentOffset); + int length = readUnsignedShort(currentOffset + 2); + int index = readUnsignedShort(currentOffset + 4); + currentOffset += 6; + context.currentLocalVariableAnnotationRangeStarts[i] = + createLabel(startPc, context.currentMethodLabels); + context.currentLocalVariableAnnotationRangeEnds[i] = + createLabel(startPc + length, context.currentMethodLabels); + context.currentLocalVariableAnnotationRangeIndices[i] = index; + } + break; + case TypeReference.CAST: + case TypeReference.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT: + case TypeReference.METHOD_INVOCATION_TYPE_ARGUMENT: + case TypeReference.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT: + case TypeReference.METHOD_REFERENCE_TYPE_ARGUMENT: + targetType &= 0xFF0000FF; + currentOffset += 4; + break; + case TypeReference.CLASS_EXTENDS: + case TypeReference.CLASS_TYPE_PARAMETER_BOUND: + case TypeReference.METHOD_TYPE_PARAMETER_BOUND: + case TypeReference.THROWS: + case TypeReference.EXCEPTION_PARAMETER: + targetType &= 0xFFFFFF00; + currentOffset += 3; + break; + case TypeReference.INSTANCEOF: + case TypeReference.NEW: + case TypeReference.CONSTRUCTOR_REFERENCE: + case TypeReference.METHOD_REFERENCE: + targetType &= 0xFF000000; + currentOffset += 3; + break; + default: + throw new IllegalArgumentException(); + } + context.currentTypeAnnotationTarget = targetType; + // Parse and store the target_path structure. + int pathLength = readByte(currentOffset); + context.currentTypeAnnotationTargetPath = + pathLength == 0 ? null : new TypePath(classFileBuffer, currentOffset); + // Return the start offset of the rest of the type_annotation structure. + return currentOffset + 1 + 2 * pathLength; + } + + /** + * Reads a Runtime[In]VisibleParameterAnnotations attribute and makes the given visitor visit it. + * + * @param methodVisitor the visitor that must visit the parameter annotations. + * @param context information about the class being parsed. + * @param runtimeParameterAnnotationsOffset the start offset of a + * Runtime[In]VisibleParameterAnnotations attribute, excluding the attribute_info's + * attribute_name_index and attribute_length fields. + * @param visible true if the attribute to parse is a RuntimeVisibleParameterAnnotations + * attribute, false it is a RuntimeInvisibleParameterAnnotations attribute. + */ + private void readParameterAnnotations( + final MethodVisitor methodVisitor, + final Context context, + final int runtimeParameterAnnotationsOffset, + final boolean visible) { + int currentOffset = runtimeParameterAnnotationsOffset; + int numParameters = classFileBuffer[currentOffset++] & 0xFF; + methodVisitor.visitAnnotableParameterCount(numParameters, visible); + char[] charBuffer = context.charBuffer; + for (int i = 0; i < numParameters; ++i) { + int numAnnotations = readUnsignedShort(currentOffset); + currentOffset += 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentOffset, charBuffer); + currentOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentOffset = + readElementValues( + methodVisitor.visitParameterAnnotation(i, annotationDescriptor, visible), + currentOffset, + /* named = */ true, + charBuffer); + } + } + } + + /** + * Reads the element values of a JVMS 'annotation' structure and makes the given visitor visit + * them. This method can also be used to read the values of the JVMS 'array_value' field of an + * annotation's 'element_value'. + * + * @param annotationVisitor the visitor that must visit the values. + * @param annotationOffset the start offset of an 'annotation' structure (excluding its type_index + * field) or of an 'array_value' structure. + * @param named if the annotation values are named or not. This should be true to parse the values + * of a JVMS 'annotation' structure, and false to parse the JVMS 'array_value' of an + * annotation's element_value. + * @param charBuffer the buffer used to read strings in the constant pool. + * @return the end offset of the JVMS 'annotation' or 'array_value' structure. + */ + private int readElementValues( + final AnnotationVisitor annotationVisitor, + final int annotationOffset, + final boolean named, + final char[] charBuffer) { + int currentOffset = annotationOffset; + // Read the num_element_value_pairs field (or num_values field for an array_value). + int numElementValuePairs = readUnsignedShort(currentOffset); + currentOffset += 2; + if (named) { + // Parse the element_value_pairs array. + while (numElementValuePairs-- > 0) { + String elementName = readUTF8(currentOffset, charBuffer); + currentOffset = + readElementValue(annotationVisitor, currentOffset + 2, elementName, charBuffer); + } + } else { + // Parse the array_value array. + while (numElementValuePairs-- > 0) { + currentOffset = + readElementValue(annotationVisitor, currentOffset, /* named = */ null, charBuffer); + } + } + if (annotationVisitor != null) { + annotationVisitor.visitEnd(); + } + return currentOffset; + } + + /** + * Reads a JVMS 'element_value' structure and makes the given visitor visit it. + * + * @param annotationVisitor the visitor that must visit the element_value structure. + * @param elementValueOffset the start offset in {@link #classFileBuffer} of the element_value + * structure to be read. + * @param elementName the name of the element_value structure to be read, or {@literal null}. + * @param charBuffer the buffer used to read strings in the constant pool. + * @return the end offset of the JVMS 'element_value' structure. + */ + private int readElementValue( + final AnnotationVisitor annotationVisitor, + final int elementValueOffset, + final String elementName, + final char[] charBuffer) { + int currentOffset = elementValueOffset; + if (annotationVisitor == null) { + switch (classFileBuffer[currentOffset] & 0xFF) { + case 'e': // enum_const_value + return currentOffset + 5; + case '@': // annotation_value + return readElementValues(null, currentOffset + 3, /* named = */ true, charBuffer); + case '[': // array_value + return readElementValues(null, currentOffset + 1, /* named = */ false, charBuffer); + default: + return currentOffset + 3; + } + } + switch (classFileBuffer[currentOffset++] & 0xFF) { + case 'B': // const_value_index, CONSTANT_Integer + annotationVisitor.visit( + elementName, (byte) readInt(cpInfoOffsets[readUnsignedShort(currentOffset)])); + currentOffset += 2; + break; + case 'C': // const_value_index, CONSTANT_Integer + annotationVisitor.visit( + elementName, (char) readInt(cpInfoOffsets[readUnsignedShort(currentOffset)])); + currentOffset += 2; + break; + case 'D': // const_value_index, CONSTANT_Double + case 'F': // const_value_index, CONSTANT_Float + case 'I': // const_value_index, CONSTANT_Integer + case 'J': // const_value_index, CONSTANT_Long + annotationVisitor.visit( + elementName, readConst(readUnsignedShort(currentOffset), charBuffer)); + currentOffset += 2; + break; + case 'S': // const_value_index, CONSTANT_Integer + annotationVisitor.visit( + elementName, (short) readInt(cpInfoOffsets[readUnsignedShort(currentOffset)])); + currentOffset += 2; + break; + + case 'Z': // const_value_index, CONSTANT_Integer + annotationVisitor.visit( + elementName, + readInt(cpInfoOffsets[readUnsignedShort(currentOffset)]) == 0 + ? Boolean.FALSE + : Boolean.TRUE); + currentOffset += 2; + break; + case 's': // const_value_index, CONSTANT_Utf8 + annotationVisitor.visit(elementName, readUTF8(currentOffset, charBuffer)); + currentOffset += 2; + break; + case 'e': // enum_const_value + annotationVisitor.visitEnum( + elementName, + readUTF8(currentOffset, charBuffer), + readUTF8(currentOffset + 2, charBuffer)); + currentOffset += 4; + break; + case 'c': // class_info + annotationVisitor.visit(elementName, Type.getType(readUTF8(currentOffset, charBuffer))); + currentOffset += 2; + break; + case '@': // annotation_value + currentOffset = + readElementValues( + annotationVisitor.visitAnnotation(elementName, readUTF8(currentOffset, charBuffer)), + currentOffset + 2, + true, + charBuffer); + break; + case '[': // array_value + int numValues = readUnsignedShort(currentOffset); + currentOffset += 2; + if (numValues == 0) { + return readElementValues( + annotationVisitor.visitArray(elementName), + currentOffset - 2, + /* named = */ false, + charBuffer); + } + switch (classFileBuffer[currentOffset] & 0xFF) { + case 'B': + byte[] byteValues = new byte[numValues]; + for (int i = 0; i < numValues; i++) { + byteValues[i] = (byte) readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]); + currentOffset += 3; + } + annotationVisitor.visit(elementName, byteValues); + break; + case 'Z': + boolean[] booleanValues = new boolean[numValues]; + for (int i = 0; i < numValues; i++) { + booleanValues[i] = readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]) != 0; + currentOffset += 3; + } + annotationVisitor.visit(elementName, booleanValues); + break; + case 'S': + short[] shortValues = new short[numValues]; + for (int i = 0; i < numValues; i++) { + shortValues[i] = (short) readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]); + currentOffset += 3; + } + annotationVisitor.visit(elementName, shortValues); + break; + case 'C': + char[] charValues = new char[numValues]; + for (int i = 0; i < numValues; i++) { + charValues[i] = (char) readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]); + currentOffset += 3; + } + annotationVisitor.visit(elementName, charValues); + break; + case 'I': + int[] intValues = new int[numValues]; + for (int i = 0; i < numValues; i++) { + intValues[i] = readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]); + currentOffset += 3; + } + annotationVisitor.visit(elementName, intValues); + break; + case 'J': + long[] longValues = new long[numValues]; + for (int i = 0; i < numValues; i++) { + longValues[i] = readLong(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]); + currentOffset += 3; + } + annotationVisitor.visit(elementName, longValues); + break; + case 'F': + float[] floatValues = new float[numValues]; + for (int i = 0; i < numValues; i++) { + floatValues[i] = + Float.intBitsToFloat( + readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)])); + currentOffset += 3; + } + annotationVisitor.visit(elementName, floatValues); + break; + case 'D': + double[] doubleValues = new double[numValues]; + for (int i = 0; i < numValues; i++) { + doubleValues[i] = + Double.longBitsToDouble( + readLong(cpInfoOffsets[readUnsignedShort(currentOffset + 1)])); + currentOffset += 3; + } + annotationVisitor.visit(elementName, doubleValues); + break; + default: + currentOffset = + readElementValues( + annotationVisitor.visitArray(elementName), + currentOffset - 2, + /* named = */ false, + charBuffer); + break; + } + break; + default: + throw new IllegalArgumentException(); + } + return currentOffset; + } + + // ---------------------------------------------------------------------------------------------- + // Methods to parse stack map frames + // ---------------------------------------------------------------------------------------------- + + /** + * Computes the implicit frame of the method currently being parsed (as defined in the given + * {@link Context}) and stores it in the given context. + * + * @param context information about the class being parsed. + */ + private void computeImplicitFrame(final Context context) { + String methodDescriptor = context.currentMethodDescriptor; + Object[] locals = context.currentFrameLocalTypes; + int numLocal = 0; + if ((context.currentMethodAccessFlags & Opcodes.ACC_STATIC) == 0) { + if ("".equals(context.currentMethodName)) { + locals[numLocal++] = Opcodes.UNINITIALIZED_THIS; + } else { + locals[numLocal++] = readClass(header + 2, context.charBuffer); + } + } + // Parse the method descriptor, one argument type descriptor at each iteration. Start by + // skipping the first method descriptor character, which is always '('. + int currentMethodDescritorOffset = 1; + while (true) { + int currentArgumentDescriptorStartOffset = currentMethodDescritorOffset; + switch (methodDescriptor.charAt(currentMethodDescritorOffset++)) { + case 'Z': + case 'C': + case 'B': + case 'S': + case 'I': + locals[numLocal++] = Opcodes.INTEGER; + break; + case 'F': + locals[numLocal++] = Opcodes.FLOAT; + break; + case 'J': + locals[numLocal++] = Opcodes.LONG; + break; + case 'D': + locals[numLocal++] = Opcodes.DOUBLE; + break; + case '[': + while (methodDescriptor.charAt(currentMethodDescritorOffset) == '[') { + ++currentMethodDescritorOffset; + } + if (methodDescriptor.charAt(currentMethodDescritorOffset) == 'L') { + ++currentMethodDescritorOffset; + while (methodDescriptor.charAt(currentMethodDescritorOffset) != ';') { + ++currentMethodDescritorOffset; + } + } + locals[numLocal++] = + methodDescriptor.substring( + currentArgumentDescriptorStartOffset, ++currentMethodDescritorOffset); + break; + case 'L': + while (methodDescriptor.charAt(currentMethodDescritorOffset) != ';') { + ++currentMethodDescritorOffset; + } + locals[numLocal++] = + methodDescriptor.substring( + currentArgumentDescriptorStartOffset + 1, currentMethodDescritorOffset++); + break; + default: + context.currentFrameLocalCount = numLocal; + return; + } + } + } + + /** + * Reads a JVMS 'stack_map_frame' structure and stores the result in the given {@link Context} + * object. This method can also be used to read a full_frame structure, excluding its frame_type + * field (this is used to parse the legacy StackMap attributes). + * + * @param stackMapFrameOffset the start offset in {@link #classFileBuffer} of the + * stack_map_frame_value structure to be read, or the start offset of a full_frame structure + * (excluding its frame_type field). + * @param compressed true to read a 'stack_map_frame' structure, false to read a 'full_frame' + * structure without its frame_type field. + * @param expand if the stack map frame must be expanded. See {@link #EXPAND_FRAMES}. + * @param context where the parsed stack map frame must be stored. + * @return the end offset of the JVMS 'stack_map_frame' or 'full_frame' structure. + */ + private int readStackMapFrame( + final int stackMapFrameOffset, + final boolean compressed, + final boolean expand, + final Context context) { + int currentOffset = stackMapFrameOffset; + final char[] charBuffer = context.charBuffer; + final Label[] labels = context.currentMethodLabels; + int frameType; + if (compressed) { + // Read the frame_type field. + frameType = classFileBuffer[currentOffset++] & 0xFF; + } else { + frameType = Frame.FULL_FRAME; + context.currentFrameOffset = -1; + } + int offsetDelta; + context.currentFrameLocalCountDelta = 0; + if (frameType < Frame.SAME_LOCALS_1_STACK_ITEM_FRAME) { + offsetDelta = frameType; + context.currentFrameType = Opcodes.F_SAME; + context.currentFrameStackCount = 0; + } else if (frameType < Frame.RESERVED) { + offsetDelta = frameType - Frame.SAME_LOCALS_1_STACK_ITEM_FRAME; + currentOffset = + readVerificationTypeInfo( + currentOffset, context.currentFrameStackTypes, 0, charBuffer, labels); + context.currentFrameType = Opcodes.F_SAME1; + context.currentFrameStackCount = 1; + } else if (frameType >= Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) { + offsetDelta = readUnsignedShort(currentOffset); + currentOffset += 2; + if (frameType == Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) { + currentOffset = + readVerificationTypeInfo( + currentOffset, context.currentFrameStackTypes, 0, charBuffer, labels); + context.currentFrameType = Opcodes.F_SAME1; + context.currentFrameStackCount = 1; + } else if (frameType >= Frame.CHOP_FRAME && frameType < Frame.SAME_FRAME_EXTENDED) { + context.currentFrameType = Opcodes.F_CHOP; + context.currentFrameLocalCountDelta = Frame.SAME_FRAME_EXTENDED - frameType; + context.currentFrameLocalCount -= context.currentFrameLocalCountDelta; + context.currentFrameStackCount = 0; + } else if (frameType == Frame.SAME_FRAME_EXTENDED) { + context.currentFrameType = Opcodes.F_SAME; + context.currentFrameStackCount = 0; + } else if (frameType < Frame.FULL_FRAME) { + int local = expand ? context.currentFrameLocalCount : 0; + for (int k = frameType - Frame.SAME_FRAME_EXTENDED; k > 0; k--) { + currentOffset = + readVerificationTypeInfo( + currentOffset, context.currentFrameLocalTypes, local++, charBuffer, labels); + } + context.currentFrameType = Opcodes.F_APPEND; + context.currentFrameLocalCountDelta = frameType - Frame.SAME_FRAME_EXTENDED; + context.currentFrameLocalCount += context.currentFrameLocalCountDelta; + context.currentFrameStackCount = 0; + } else { + final int numberOfLocals = readUnsignedShort(currentOffset); + currentOffset += 2; + context.currentFrameType = Opcodes.F_FULL; + context.currentFrameLocalCountDelta = numberOfLocals; + context.currentFrameLocalCount = numberOfLocals; + for (int local = 0; local < numberOfLocals; ++local) { + currentOffset = + readVerificationTypeInfo( + currentOffset, context.currentFrameLocalTypes, local, charBuffer, labels); + } + final int numberOfStackItems = readUnsignedShort(currentOffset); + currentOffset += 2; + context.currentFrameStackCount = numberOfStackItems; + for (int stack = 0; stack < numberOfStackItems; ++stack) { + currentOffset = + readVerificationTypeInfo( + currentOffset, context.currentFrameStackTypes, stack, charBuffer, labels); + } + } + } else { + throw new IllegalArgumentException(); + } + context.currentFrameOffset += offsetDelta + 1; + createLabel(context.currentFrameOffset, labels); + return currentOffset; + } + + /** + * Reads a JVMS 'verification_type_info' structure and stores it at the given index in the given + * array. + * + * @param verificationTypeInfoOffset the start offset of the 'verification_type_info' structure to + * read. + * @param frame the array where the parsed type must be stored. + * @param index the index in 'frame' where the parsed type must be stored. + * @param charBuffer the buffer used to read strings in the constant pool. + * @param labels the labels of the method currently being parsed, indexed by their offset. If the + * parsed type is an ITEM_Uninitialized, a new label for the corresponding NEW instruction is + * stored in this array if it does not already exist. + * @return the end offset of the JVMS 'verification_type_info' structure. + */ + private int readVerificationTypeInfo( + final int verificationTypeInfoOffset, + final Object[] frame, + final int index, + final char[] charBuffer, + final Label[] labels) { + int currentOffset = verificationTypeInfoOffset; + int tag = classFileBuffer[currentOffset++] & 0xFF; + switch (tag) { + case Frame.ITEM_TOP: + frame[index] = Opcodes.TOP; + break; + case Frame.ITEM_INTEGER: + frame[index] = Opcodes.INTEGER; + break; + case Frame.ITEM_FLOAT: + frame[index] = Opcodes.FLOAT; + break; + case Frame.ITEM_DOUBLE: + frame[index] = Opcodes.DOUBLE; + break; + case Frame.ITEM_LONG: + frame[index] = Opcodes.LONG; + break; + case Frame.ITEM_NULL: + frame[index] = Opcodes.NULL; + break; + case Frame.ITEM_UNINITIALIZED_THIS: + frame[index] = Opcodes.UNINITIALIZED_THIS; + break; + case Frame.ITEM_OBJECT: + frame[index] = readClass(currentOffset, charBuffer); + currentOffset += 2; + break; + case Frame.ITEM_UNINITIALIZED: + frame[index] = createLabel(readUnsignedShort(currentOffset), labels); + currentOffset += 2; + break; + default: + throw new IllegalArgumentException(); + } + return currentOffset; + } + + // ---------------------------------------------------------------------------------------------- + // Methods to parse attributes + // ---------------------------------------------------------------------------------------------- + + /** + * Returns the offset in {@link #classFileBuffer} of the first ClassFile's 'attributes' array + * field entry. + * + * @return the offset in {@link #classFileBuffer} of the first ClassFile's 'attributes' array + * field entry. + */ + final int getFirstAttributeOffset() { + // Skip the access_flags, this_class, super_class, and interfaces_count fields (using 2 bytes + // each), as well as the interfaces array field (2 bytes per interface). + int currentOffset = header + 8 + readUnsignedShort(header + 6) * 2; + + // Read the fields_count field. + int fieldsCount = readUnsignedShort(currentOffset); + currentOffset += 2; + // Skip the 'fields' array field. + while (fieldsCount-- > 0) { + // Invariant: currentOffset is the offset of a field_info structure. + // Skip the access_flags, name_index and descriptor_index fields (2 bytes each), and read the + // attributes_count field. + int attributesCount = readUnsignedShort(currentOffset + 6); + currentOffset += 8; + // Skip the 'attributes' array field. + while (attributesCount-- > 0) { + // Invariant: currentOffset is the offset of an attribute_info structure. + // Read the attribute_length field (2 bytes after the start of the attribute_info) and skip + // this many bytes, plus 6 for the attribute_name_index and attribute_length fields + // (yielding the total size of the attribute_info structure). + currentOffset += 6 + readInt(currentOffset + 2); + } + } + + // Skip the methods_count and 'methods' fields, using the same method as above. + int methodsCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (methodsCount-- > 0) { + int attributesCount = readUnsignedShort(currentOffset + 6); + currentOffset += 8; + while (attributesCount-- > 0) { + currentOffset += 6 + readInt(currentOffset + 2); + } + } + + // Skip the ClassFile's attributes_count field. + return currentOffset + 2; + } + + /** + * Reads the BootstrapMethods attribute to compute the offset of each bootstrap method. + * + * @param maxStringLength a conservative estimate of the maximum length of the strings contained + * in the constant pool of the class. + * @return the offsets of the bootstrap methods. + */ + private int[] readBootstrapMethodsAttribute(final int maxStringLength) { + char[] charBuffer = new char[maxStringLength]; + int currentAttributeOffset = getFirstAttributeOffset(); + int[] currentBootstrapMethodOffsets = null; + for (int i = readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) { + // Read the attribute_info's attribute_name and attribute_length fields. + String attributeName = readUTF8(currentAttributeOffset, charBuffer); + int attributeLength = readInt(currentAttributeOffset + 2); + currentAttributeOffset += 6; + if (Constants.BOOTSTRAP_METHODS.equals(attributeName)) { + // Read the num_bootstrap_methods field and create an array of this size. + currentBootstrapMethodOffsets = new int[readUnsignedShort(currentAttributeOffset)]; + // Compute and store the offset of each 'bootstrap_methods' array field entry. + int currentBootstrapMethodOffset = currentAttributeOffset + 2; + for (int j = 0; j < currentBootstrapMethodOffsets.length; ++j) { + currentBootstrapMethodOffsets[j] = currentBootstrapMethodOffset; + // Skip the bootstrap_method_ref and num_bootstrap_arguments fields (2 bytes each), + // as well as the bootstrap_arguments array field (of size num_bootstrap_arguments * 2). + currentBootstrapMethodOffset += + 4 + readUnsignedShort(currentBootstrapMethodOffset + 2) * 2; + } + return currentBootstrapMethodOffsets; + } + currentAttributeOffset += attributeLength; + } + throw new IllegalArgumentException(); + } + + /** + * Reads a non standard JVMS 'attribute' structure in {@link #classFileBuffer}. + * + * @param attributePrototypes prototypes of the attributes that must be parsed during the visit of + * the class. Any attribute whose type is not equal to the type of one the prototypes will not + * be parsed: its byte array value will be passed unchanged to the ClassWriter. + * @param type the type of the attribute. + * @param offset the start offset of the JVMS 'attribute' structure in {@link #classFileBuffer}. + * The 6 attribute header bytes (attribute_name_index and attribute_length) are not taken into + * account here. + * @param length the length of the attribute's content (excluding the 6 attribute header bytes). + * @param charBuffer the buffer to be used to read strings in the constant pool. + * @param codeAttributeOffset the start offset of the enclosing Code attribute in {@link + * #classFileBuffer}, or -1 if the attribute to be read is not a code attribute. The 6 + * attribute header bytes (attribute_name_index and attribute_length) are not taken into + * account here. + * @param labels the labels of the method's code, or {@literal null} if the attribute to be read + * is not a code attribute. + * @return the attribute that has been read. + */ + private Attribute readAttribute( + final Attribute[] attributePrototypes, + final String type, + final int offset, + final int length, + final char[] charBuffer, + final int codeAttributeOffset, + final Label[] labels) { + for (Attribute attributePrototype : attributePrototypes) { + if (attributePrototype.type.equals(type)) { + return attributePrototype.read( + this, offset, length, charBuffer, codeAttributeOffset, labels); + } + } + return new Attribute(type).read(this, offset, length, null, -1, null); + } + + // ----------------------------------------------------------------------------------------------- + // Utility methods: low level parsing + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the number of entries in the class's constant pool table. + * + * @return the number of entries in the class's constant pool table. + */ + public int getItemCount() { + return cpInfoOffsets.length; + } + + /** + * Returns the start offset in this {@link ClassReader} of a JVMS 'cp_info' structure (i.e. a + * constant pool entry), plus one. This method is intended for {@link Attribute} sub classes, + * and is normally not needed by class generators or adapters. + * + * @param constantPoolEntryIndex the index a constant pool entry in the class's constant pool + * table. + * @return the start offset in this {@link ClassReader} of the corresponding JVMS 'cp_info' + * structure, plus one. + */ + public int getItem(final int constantPoolEntryIndex) { + return cpInfoOffsets[constantPoolEntryIndex]; + } + + /** + * Returns a conservative estimate of the maximum length of the strings contained in the class's + * constant pool table. + * + * @return a conservative estimate of the maximum length of the strings contained in the class's + * constant pool table. + */ + public int getMaxStringLength() { + return maxStringLength; + } + + /** + * Reads a byte value in this {@link ClassReader}. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param offset the start offset of the value to be read in this {@link ClassReader}. + * @return the read value. + */ + public int readByte(final int offset) { + return classFileBuffer[offset] & 0xFF; + } + + /** + * Reads an unsigned short value in this {@link ClassReader}. This method is intended for + * {@link Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param offset the start index of the value to be read in this {@link ClassReader}. + * @return the read value. + */ + public int readUnsignedShort(final int offset) { + byte[] classBuffer = classFileBuffer; + return ((classBuffer[offset] & 0xFF) << 8) | (classBuffer[offset + 1] & 0xFF); + } + + /** + * Reads a signed short value in this {@link ClassReader}. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param offset the start offset of the value to be read in this {@link ClassReader}. + * @return the read value. + */ + public short readShort(final int offset) { + byte[] classBuffer = classFileBuffer; + return (short) (((classBuffer[offset] & 0xFF) << 8) | (classBuffer[offset + 1] & 0xFF)); + } + + /** + * Reads a signed int value in this {@link ClassReader}. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param offset the start offset of the value to be read in this {@link ClassReader}. + * @return the read value. + */ + public int readInt(final int offset) { + byte[] classBuffer = classFileBuffer; + return ((classBuffer[offset] & 0xFF) << 24) + | ((classBuffer[offset + 1] & 0xFF) << 16) + | ((classBuffer[offset + 2] & 0xFF) << 8) + | (classBuffer[offset + 3] & 0xFF); + } + + /** + * Reads a signed long value in this {@link ClassReader}. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param offset the start offset of the value to be read in this {@link ClassReader}. + * @return the read value. + */ + public long readLong(final int offset) { + long l1 = readInt(offset); + long l0 = readInt(offset + 4) & 0xFFFFFFFFL; + return (l1 << 32) | l0; + } + + /** + * Reads a CONSTANT_Utf8 constant pool entry in this {@link ClassReader}. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by class generators or + * adapters. + * + * @param offset the start offset of an unsigned short value in this {@link ClassReader}, whose + * value is the index of a CONSTANT_Utf8 entry in the class's constant pool table. + * @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the String corresponding to the specified CONSTANT_Utf8 entry. + */ + // DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility). + public String readUTF8(final int offset, final char[] charBuffer) { + int constantPoolEntryIndex = readUnsignedShort(offset); + if (offset == 0 || constantPoolEntryIndex == 0) { + return null; + } + return readUtf(constantPoolEntryIndex, charBuffer); + } + + /** + * Reads a CONSTANT_Utf8 constant pool entry in {@link #classFileBuffer}. + * + * @param constantPoolEntryIndex the index of a CONSTANT_Utf8 entry in the class's constant pool + * table. + * @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the String corresponding to the specified CONSTANT_Utf8 entry. + */ + final String readUtf(final int constantPoolEntryIndex, final char[] charBuffer) { + String value = constantUtf8Values[constantPoolEntryIndex]; + if (value != null) { + return value; + } + int cpInfoOffset = cpInfoOffsets[constantPoolEntryIndex]; + return constantUtf8Values[constantPoolEntryIndex] = + readUtf(cpInfoOffset + 2, readUnsignedShort(cpInfoOffset), charBuffer); + } + + /** + * Reads an UTF8 string in {@link #classFileBuffer}. + * + * @param utfOffset the start offset of the UTF8 string to be read. + * @param utfLength the length of the UTF8 string to be read. + * @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the String corresponding to the specified UTF8 string. + */ + private String readUtf(final int utfOffset, final int utfLength, final char[] charBuffer) { + int currentOffset = utfOffset; + int endOffset = currentOffset + utfLength; + int strLength = 0; + byte[] classBuffer = classFileBuffer; + while (currentOffset < endOffset) { + int currentByte = classBuffer[currentOffset++]; + if ((currentByte & 0x80) == 0) { + charBuffer[strLength++] = (char) (currentByte & 0x7F); + } else if ((currentByte & 0xE0) == 0xC0) { + charBuffer[strLength++] = + (char) (((currentByte & 0x1F) << 6) + (classBuffer[currentOffset++] & 0x3F)); + } else { + charBuffer[strLength++] = + (char) + (((currentByte & 0xF) << 12) + + ((classBuffer[currentOffset++] & 0x3F) << 6) + + (classBuffer[currentOffset++] & 0x3F)); + } + } + return new String(charBuffer, 0, strLength); + } + + /** + * Reads a CONSTANT_Class, CONSTANT_String, CONSTANT_MethodType, CONSTANT_Module or + * CONSTANT_Package constant pool entry in {@link #classFileBuffer}. This method is intended + * for {@link Attribute} sub classes, and is normally not needed by class generators or + * adapters. + * + * @param offset the start offset of an unsigned short value in {@link #classFileBuffer}, whose + * value is the index of a CONSTANT_Class, CONSTANT_String, CONSTANT_MethodType, + * CONSTANT_Module or CONSTANT_Package entry in class's constant pool table. + * @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the String corresponding to the specified constant pool entry. + */ + private String readStringish(final int offset, final char[] charBuffer) { + // Get the start offset of the cp_info structure (plus one), and read the CONSTANT_Utf8 entry + // designated by the first two bytes of this cp_info. + return readUTF8(cpInfoOffsets[readUnsignedShort(offset)], charBuffer); + } + + /** + * Reads a CONSTANT_Class constant pool entry in this {@link ClassReader}. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by class generators or + * adapters. + * + * @param offset the start offset of an unsigned short value in this {@link ClassReader}, whose + * value is the index of a CONSTANT_Class entry in class's constant pool table. + * @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the String corresponding to the specified CONSTANT_Class entry. + */ + public String readClass(final int offset, final char[] charBuffer) { + return readStringish(offset, charBuffer); + } + + /** + * Reads a CONSTANT_Module constant pool entry in this {@link ClassReader}. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by class generators or + * adapters. + * + * @param offset the start offset of an unsigned short value in this {@link ClassReader}, whose + * value is the index of a CONSTANT_Module entry in class's constant pool table. + * @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the String corresponding to the specified CONSTANT_Module entry. + */ + public String readModule(final int offset, final char[] charBuffer) { + return readStringish(offset, charBuffer); + } + + /** + * Reads a CONSTANT_Package constant pool entry in this {@link ClassReader}. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by class generators or + * adapters. + * + * @param offset the start offset of an unsigned short value in this {@link ClassReader}, whose + * value is the index of a CONSTANT_Package entry in class's constant pool table. + * @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the String corresponding to the specified CONSTANT_Package entry. + */ + public String readPackage(final int offset, final char[] charBuffer) { + return readStringish(offset, charBuffer); + } + + /** + * Reads a CONSTANT_Dynamic constant pool entry in {@link #classFileBuffer}. + * + * @param constantPoolEntryIndex the index of a CONSTANT_Dynamic entry in the class's constant + * pool table. + * @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the ConstantDynamic corresponding to the specified CONSTANT_Dynamic entry. + */ + private ConstantDynamic readConstantDynamic( + final int constantPoolEntryIndex, final char[] charBuffer) { + ConstantDynamic constantDynamic = constantDynamicValues[constantPoolEntryIndex]; + if (constantDynamic != null) { + return constantDynamic; + } + int cpInfoOffset = cpInfoOffsets[constantPoolEntryIndex]; + int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 2)]; + String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer); + String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer); + int bootstrapMethodOffset = bootstrapMethodOffsets[readUnsignedShort(cpInfoOffset)]; + Handle handle = (Handle) readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer); + Object[] bootstrapMethodArguments = new Object[readUnsignedShort(bootstrapMethodOffset + 2)]; + bootstrapMethodOffset += 4; + for (int i = 0; i < bootstrapMethodArguments.length; i++) { + bootstrapMethodArguments[i] = readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer); + bootstrapMethodOffset += 2; + } + return constantDynamicValues[constantPoolEntryIndex] = + new ConstantDynamic(name, descriptor, handle, bootstrapMethodArguments); + } + + /** + * Reads a numeric or string constant pool entry in this {@link ClassReader}. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by class generators or + * adapters. + * + * @param constantPoolEntryIndex the index of a CONSTANT_Integer, CONSTANT_Float, CONSTANT_Long, + * CONSTANT_Double, CONSTANT_Class, CONSTANT_String, CONSTANT_MethodType, + * CONSTANT_MethodHandle or CONSTANT_Dynamic entry in the class's constant pool. + * @param charBuffer the buffer to be used to read strings. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the {@link Integer}, {@link Float}, {@link Long}, {@link Double}, {@link String}, + * {@link Type}, {@link Handle} or {@link ConstantDynamic} corresponding to the specified + * constant pool entry. + */ + public Object readConst(final int constantPoolEntryIndex, final char[] charBuffer) { + int cpInfoOffset = cpInfoOffsets[constantPoolEntryIndex]; + switch (classFileBuffer[cpInfoOffset - 1]) { + case Symbol.CONSTANT_INTEGER_TAG: + return readInt(cpInfoOffset); + case Symbol.CONSTANT_FLOAT_TAG: + return Float.intBitsToFloat(readInt(cpInfoOffset)); + case Symbol.CONSTANT_LONG_TAG: + return readLong(cpInfoOffset); + case Symbol.CONSTANT_DOUBLE_TAG: + return Double.longBitsToDouble(readLong(cpInfoOffset)); + case Symbol.CONSTANT_CLASS_TAG: + return Type.getObjectType(readUTF8(cpInfoOffset, charBuffer)); + case Symbol.CONSTANT_STRING_TAG: + return readUTF8(cpInfoOffset, charBuffer); + case Symbol.CONSTANT_METHOD_TYPE_TAG: + return Type.getMethodType(readUTF8(cpInfoOffset, charBuffer)); + case Symbol.CONSTANT_METHOD_HANDLE_TAG: + int referenceKind = readByte(cpInfoOffset); + int referenceCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 1)]; + int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(referenceCpInfoOffset + 2)]; + String owner = readClass(referenceCpInfoOffset, charBuffer); + String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer); + String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer); + boolean isInterface = + classFileBuffer[referenceCpInfoOffset - 1] == Symbol.CONSTANT_INTERFACE_METHODREF_TAG; + return new Handle(referenceKind, owner, name, descriptor, isInterface); + case Symbol.CONSTANT_DYNAMIC_TAG: + return readConstantDynamic(constantPoolEntryIndex, charBuffer); + default: + throw new IllegalArgumentException(); + } + } +} diff --git a/src/the/bytecode/club/bytecodeviewer/BytecodeViewer.java b/src/the/bytecode/club/bytecodeviewer/BytecodeViewer.java index e2941dd9..5976ecaa 100644 --- a/src/the/bytecode/club/bytecodeviewer/BytecodeViewer.java +++ b/src/the/bytecode/club/bytecodeviewer/BytecodeViewer.java @@ -1,6 +1,6 @@ package the.bytecode.club.bytecodeviewer; -import java.awt.Desktop; +import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; @@ -114,7 +114,7 @@ import the.bytecode.club.bytecodeviewer.util.*; public class BytecodeViewer { /*per version*/ - public static final String VERSION = "2.9.20"; + public static final String VERSION = "2.9.21"; public static String krakatauVersion = "12"; public static String enjarifyVersion = "4"; public static final boolean BLOCK_TAB_MENU = true; diff --git a/src/the/bytecode/club/bytecodeviewer/decompilers/FernFlowerDecompiler.java b/src/the/bytecode/club/bytecodeviewer/decompilers/FernFlowerDecompiler.java index df4a0f33..0e3423d1 100644 --- a/src/the/bytecode/club/bytecodeviewer/decompilers/FernFlowerDecompiler.java +++ b/src/the/bytecode/club/bytecodeviewer/decompilers/FernFlowerDecompiler.java @@ -107,7 +107,8 @@ public class FernFlowerDecompiler extends Decompiler { { try { - org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler.main(generateMainMethod(tempClass.getAbsolutePath(), new File(BytecodeViewer.tempDirectory).getAbsolutePath())); + org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler.main(generateMainMethod(tempClass.getAbsolutePath(), + new File(BytecodeViewer.tempDirectory).getAbsolutePath())); } catch(StackOverflowError | Exception e) { diff --git a/src/the/bytecode/club/bytecodeviewer/gui/MainViewerGUI.java b/src/the/bytecode/club/bytecodeviewer/gui/MainViewerGUI.java index f55ea82e..e7183486 100644 --- a/src/the/bytecode/club/bytecodeviewer/gui/MainViewerGUI.java +++ b/src/the/bytecode/club/bytecodeviewer/gui/MainViewerGUI.java @@ -1761,15 +1761,15 @@ public class MainViewerGUI extends JFrame implements FileChangeNotifier mnNewMenu_7.add(mnCfr); jadx1.add(jadxJ1); - jadx1.add(separator_19); + jadx1.add(new JSeparator()); jadx1.add(jadxE1); jadx2.add(jadxJ2); - jadx2.add(separator_19); + jadx2.add(new JSeparator()); jadx2.add(jadxE2); jadx3.add(jadxJ3); - jadx3.add(separator_19); + jadx3.add(new JSeparator()); jadx3.add(jadxE3); mnNewMenu_7.add(jadx1);