src/java.base/share/classes/jdk/internal/org/objectweb/asm/ClassReader.java
author darcy
Tue, 11 Jun 2019 16:45:20 -0700
changeset 55382 30b1b7b4dd86
parent 53023 6879069d9d94
permissions -rw-r--r--
8214546: Start of release updates for JDK 14 8214547: Add SourceVersion.RELEASE_14 8214548: Add source 14 and target 14 to javac 8214549: Bump maximum recognized class file version to 58 for JDK 14 Reviewed-by: jjg, mikael, erikj, jlahoda, dholmes

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 * This file is available under and governed by the GNU General Public
 * License version 2 only, as published by the Free Software Foundation.
 * However, the following notice accompanied the original version of this
 * file:
 *
 * 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 jdk.internal.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 <a href="https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html">JVMS 4</a>
 * @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 <i>not</i> 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. <i>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.</i>
      *
      * <p>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.
      */
    // DontCheck(MemberName): can't be renamed (for backward binary compatibility).
    public final byte[] b;

    /**
      * The offset in bytes, in {@link #b}, of each cp_info entry of the ClassFile's constant_pool
      * array, <i>plus one</i>. 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 #b} of each element of the bootstrap_methods array (in the
      * BootstrapMethods attribute).
      *
      * @see <a href="https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.23">JVMS
      *     4.7.23</a>
      */
    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, in {@link #b}, 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. <i>This internal constructor must not be exposed
      * as a public API</i>.
      *
      * @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) {
        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.V14) {
            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 hasConstantDynamic = false;
        boolean hasConstantInvokeDynamic = false;
        // The offset of the other entries depend on the total size of all the previous entries.
        while (currentCpInfoIndex < constantPoolCount) {
            cpInfoOffsets[currentCpInfoIndex++] = currentCpInfoOffset + 1;
            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;
                    hasConstantDynamic = true;
                    break;
                case Symbol.CONSTANT_INVOKE_DYNAMIC_TAG:
                    cpInfoSize = 5;
                    hasConstantInvokeDynamic = 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 =
                (hasConstantDynamic | hasConstantInvokeDynamic)
                        ? 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. <i>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</i>.
      * @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 <i>reverse order</i> 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 <i>reverse order</i> 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 <i>reverse order</i> 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,
                    methodInfoOffset,
                    currentOffset - methodInfoOffset,
                    synthetic,
                    (context.currentMethodAccessFlags & Opcodes.ACC_DEPRECATED) != 0,
                    readUnsignedShort(methodInfoOffset + 4),
                    signatureIndex,
                    exceptionsOffset)) {
                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 #b} of the Code attribute, excluding its
      *     attribute_name_index and attribute_length fields.
      */
    private void readCode(
            final MethodVisitor methodVisitor, final Context context, final int codeOffset) {
        int currentOffset = codeOffset;

        // Read the max_stack, max_locals and code_length fields.
        final byte[] classFileBuffer = b;
        final char[] charBuffer = context.charBuffer;
        final int maxStack = readUnsignedShort(currentOffset);
        final int maxLocals = readUnsignedShort(currentOffset + 2);
        final int 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 = classFileBuffer[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 (classFileBuffer[currentOffset + 1] & 0xFF) {
                        case Constants.ILOAD:
                        case Constants.FLOAD:
                        case Constants.ALOAD:
                        case Constants.LLOAD:
                        case Constants.DLOAD:
                        case Constants.ISTORE:
                        case Constants.FSTORE:
                        case Constants.ASTORE:
                        case Constants.LSTORE:
                        case Constants.DSTORE:
                        case Constants.RET:
                            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 <i>reverse order</i> 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 (classFileBuffer[offset] == Frame.ITEM_UNINITIALIZED) {
                    int potentialBytecodeOffset = readUnsignedShort(offset + 1);
                    if (potentialBytecodeOffset >= 0
                            && potentialBytecodeOffset < codeLength
                            && (classFileBuffer[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 = classFileBuffer[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 <l> with IFNOTxxx <L> GOTO_W <l> L:...,
                        // where IFNOTxxx is the "opposite" opcode of ASMS_IFxxx (e.g. IFNE for ASM_IFEQ) and
                        // where <L> 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 <L> ASM_GOTO_W <l> L:..., see MethodWriter), so we need to insert a frame
                        // here.
                        insertFrame = true;
                        currentOffset += 5;
                        break;
                    }
                case Constants.WIDE:
                    opcode = classFileBuffer[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, classFileBuffer[currentOffset + 1] & 0xFF);
                    currentOffset += 2;
                    break;
                case Constants.BIPUSH:
                case Constants.NEWARRAY:
                    methodVisitor.visitIntInsn(opcode, classFileBuffer[currentOffset + 1]);
                    currentOffset += 2;
                    break;
                case Constants.SIPUSH:
                    methodVisitor.visitIntInsn(opcode, readShort(currentOffset + 1));
                    currentOffset += 3;
                    break;
                case Constants.LDC:
                    methodVisitor.visitLdcInsn(
                            readConst(classFileBuffer[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 =
                                    classFileBuffer[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(
                            classFileBuffer[currentOffset + 1] & 0xFF, classFileBuffer[currentOffset + 2]);
                    currentOffset += 3;
                    break;
                case Constants.MULTIANEWARRAY:
                    methodVisitor.visitMultiANewArrayInsn(
                            readClass(currentOffset + 1, charBuffer), classFileBuffer[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(b, 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(b, 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 = b[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 #b} 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 (b[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 (b[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 (b[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 ("<init>".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 #b} of the stack_map_frame_value
      *     structure to be read, or the start offset of a full_frame structure (excluding its
      *     frame_type field).
      * @param 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 = b[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 = b[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 #b} of the first ClassFile's 'attributes' array field entry.
      *
      * @return the offset in {@link #b} 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 or null.
      */
    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;
        }
        return null;
    }

    /**
      * Reads a non standard JVMS 'attribute' structure in {@link #b}.
      *
      * @param attributePrototypes prototypes of the attributes that must be parsed during the visit of
      *     the class. Any attribute whose type is not equal to the type of one the prototypes will not
      *     be parsed: its byte array value will be passed unchanged to the ClassWriter.
      * @param type the type of the attribute.
      * @param offset the start offset of the JVMS 'attribute' structure in {@link #b}. The 6 attribute
      *     header bytes (attribute_name_index and attribute_length) are not taken into account here.
      * @param length the length of the attribute's content (excluding the 6 attribute header bytes).
      * @param charBuffer the buffer to be used to read strings in the constant pool.
      * @param codeAttributeOffset the start offset of the enclosing Code attribute in {@link #b}, or
      *     -1 if the attribute to be read is not a code attribute. The 6 attribute header bytes
      *     (attribute_name_index and attribute_length) are not taken into account here.
      * @param labels the labels of the method's code, or {@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 {@link #b} of a JVMS 'cp_info' structure (i.e. a constant pool
      * entry), plus one. <i>This method is intended for {@link Attribute} sub classes, and is normally
      * not needed by class generators or adapters.</i>
      *
      * @param constantPoolEntryIndex the index a constant pool entry in the class's constant pool
      *     table.
      * @return the start offset in {@link #b} of the corresponding JVMS 'cp_info' structure, plus one.
      */
    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 {@link #b}. <i>This method is intended for {@link Attribute} sub classes,
      * and is normally not needed by class generators or adapters.</i>
      *
      * @param offset the start offset of the value to be read in {@link #b}.
      * @return the read value.
      */
    public int readByte(final int offset) {
        return b[offset] & 0xFF;
    }

    /**
      * Reads an unsigned short value in {@link #b}. <i>This method is intended for {@link Attribute}
      * sub classes, and is normally not needed by class generators or adapters.</i>
      *
      * @param offset the start index of the value to be read in {@link #b}.
      * @return the read value.
      */
    public int readUnsignedShort(final int offset) {
        byte[] classFileBuffer = b;
        return ((classFileBuffer[offset] & 0xFF) << 8) | (classFileBuffer[offset + 1] & 0xFF);
    }

    /**
      * Reads a signed short value in {@link #b}. <i>This method is intended for {@link Attribute} sub
      * classes, and is normally not needed by class generators or adapters.</i>
      *
      * @param offset the start offset of the value to be read in {@link #b}.
      * @return the read value.
      */
    public short readShort(final int offset) {
        byte[] classFileBuffer = b;
        return (short) (((classFileBuffer[offset] & 0xFF) << 8) | (classFileBuffer[offset + 1] & 0xFF));
    }

    /**
      * Reads a signed int value in {@link #b}. <i>This method is intended for {@link Attribute} sub
      * classes, and is normally not needed by class generators or adapters.</i>
      *
      * @param offset the start offset of the value to be read in {@link #b}.
      * @return the read value.
      */
    public int readInt(final int offset) {
        byte[] classFileBuffer = b;
        return ((classFileBuffer[offset] & 0xFF) << 24)
                | ((classFileBuffer[offset + 1] & 0xFF) << 16)
                | ((classFileBuffer[offset + 2] & 0xFF) << 8)
                | (classFileBuffer[offset + 3] & 0xFF);
    }

    /**
      * Reads a signed long value in {@link #b}. <i>This method is intended for {@link Attribute} sub
      * classes, and is normally not needed by class generators or adapters.</i>
      *
      * @param offset the start offset of the value to be read in {@link #b}.
      * @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 {@link #b}. <i>This method is intended for {@link
      * Attribute} sub classes, and is normally not needed by class generators or adapters.</i>
      *
      * @param offset the start offset of an unsigned short value in {@link #b}, whose value is the
      *     index of a CONSTANT_Utf8 entry in the class's constant pool table.
      * @param 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 #b}.
      *
      * @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 #b}.
      *
      * @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[] classFileBuffer = b;
        while (currentOffset < endOffset) {
            int currentByte = classFileBuffer[currentOffset++];
            if ((currentByte & 0x80) == 0) {
                charBuffer[strLength++] = (char) (currentByte & 0x7F);
            } else if ((currentByte & 0xE0) == 0xC0) {
                charBuffer[strLength++] =
                        (char) (((currentByte & 0x1F) << 6) + (classFileBuffer[currentOffset++] & 0x3F));
            } else {
                charBuffer[strLength++] =
                        (char)
                                (((currentByte & 0xF) << 12)
                                        + ((classFileBuffer[currentOffset++] & 0x3F) << 6)
                                        + (classFileBuffer[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 #b}. <i>This method is intended for {@link
      * Attribute} sub classes, and is normally not needed by class generators or adapters.</i>
      *
      * @param offset the start offset of an unsigned short value in {@link #b}, whose value is the
      *     index of a CONSTANT_Class, CONSTANT_String, CONSTANT_MethodType, CONSTANT_Module or
      *     CONSTANT_Package entry in class's constant pool table.
      * @param 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 {@link #b}. <i>This method is intended for {@link
      * Attribute} sub classes, and is normally not needed by class generators or adapters.</i>
      *
      * @param offset the start offset of an unsigned short value in {@link #b}, whose value is the
      *     index of a CONSTANT_Class entry in class's constant pool table.
      * @param 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 {@link #b}. <i>This method is intended for
      * {@link Attribute} sub classes, and is normally not needed by class generators or adapters.</i>
      *
      * @param offset the start offset of an unsigned short value in {@link #b}, whose value is the
      *     index of a CONSTANT_Module entry in class's constant pool table.
      * @param 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 {@link #b}. <i>This method is intended for
      * {@link Attribute} sub classes, and is normally not needed by class generators or adapters.</i>
      *
      * @param offset the start offset of an unsigned short value in {@link #b}, whose value is the
      *     index of a CONSTANT_Package entry in class's constant pool table.
      * @param 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 #b}.
      *
      * @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 {@link #b}. <i>This method is intended for
      * {@link Attribute} sub classes, and is normally not needed by class generators or adapters.</i>
      *
      * @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 (b[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 =
                        b[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();
        }
    }
}