You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by th...@apache.org on 2018/11/29 00:34:31 UTC

[44/46] tapestry-5 git commit: TAP5-2588: upgrading from ASM 6 to 7 for Java 9+ support

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/1c71aec7/plastic/src/external/java/org/apache/tapestry5/internal/plastic/asm/ClassReader.java
----------------------------------------------------------------------
diff --git a/plastic/src/external/java/org/apache/tapestry5/internal/plastic/asm/ClassReader.java b/plastic/src/external/java/org/apache/tapestry5/internal/plastic/asm/ClassReader.java
old mode 100644
new mode 100755
index 6d810e0..41b6de2
--- a/plastic/src/external/java/org/apache/tapestry5/internal/plastic/asm/ClassReader.java
+++ b/plastic/src/external/java/org/apache/tapestry5/internal/plastic/asm/ClassReader.java
@@ -1,2756 +1,3603 @@
-/***
- * 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.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+//    notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+//    notice, this list of conditions and the following disclaimer in the
+//    documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
 package org.apache.tapestry5.internal.plastic.asm;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 
 /**
- * A Java class parser to make a {@link ClassVisitor} visit an existing class.
- * This class parses a byte array conforming to the Java class file format and
- * calls the appropriate visit methods of a given class visitor for each field,
- * method and bytecode instruction encountered.
- * 
+ * 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 {
 
-    /**
-     * Flag to skip method code. If this class is set <code>CODE</code>
-     * attribute won't be visited. This can be used, for example, to retrieve
-     * annotations for methods and method parameters.
-     */
-    public static final int SKIP_CODE = 1;
-
-    /**
-     * Flag to skip the debug information in the class. If this flag is set the
-     * debug information of the class is not visited, i.e. the
-     * {@link MethodVisitor#visitLocalVariable visitLocalVariable} and
-     * {@link MethodVisitor#visitLineNumber visitLineNumber} methods will not be
-     * called.
-     */
-    public static final int SKIP_DEBUG = 2;
-
-    /**
-     * Flag to skip the stack map frames in the class. If this flag is set the
-     * stack map frames of the class is not visited, i.e. the
-     * {@link MethodVisitor#visitFrame visitFrame} method will not be 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 in the class writer.
-     */
-    public static final int SKIP_FRAMES = 4;
-
-    /**
-     * 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/recompression step in ClassReader and
-     * ClassWriter which degrades performances quite a lot).
-     */
-    public static final int EXPAND_FRAMES = 8;
-
-    /**
-     * Flag to expand the ASM pseudo 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 pseudo instruction using an unsigned 2
-     * bytes offset (see 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 class 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 generators or adapters.</i>
-     */
-    public final byte[] b;
-
-    /**
-     * The start index of each constant pool item in {@link #b b}, plus one. The
-     * one byte offset skips the constant pool item tag that indicates its type.
-     */
-    private final int[] items;
-
-    /**
-     * The String objects corresponding to the CONSTANT_Utf8 items. This cache
-     * avoids multiple parsing of a given CONSTANT_Utf8 constant pool item,
-     * which GREATLY improves performances (by a factor 2 to 3). This caching
-     * strategy could be extended to all constant pool items, but its benefit
-     * would not be so great for these items (because they are much less
-     * expensive to parse than CONSTANT_Utf8 items).
-     */
-    private final String[] strings;
-
-    /**
-     * Maximum length of the strings contained in the constant pool of the
-     * class.
-     */
-    private final int maxStringLength;
-
-    /**
-     * Start index of the class header information (access, name...) in
-     * {@link #b b}.
-     */
-    public final int header;
-
-    // ------------------------------------------------------------------------
-    // Constructors
-    // ------------------------------------------------------------------------
-
-    /**
-     * Constructs a new {@link ClassReader} object.
-     * 
-     * @param b
-     *            the bytecode of the class to be read.
-     */
-    public ClassReader(final byte[] b) {
-        this(b, 0, b.length);
-    }
-
-    /**
-     * Constructs a new {@link ClassReader} object.
-     * 
-     * @param b
-     *            the bytecode of the class to be read.
-     * @param off
-     *            the start offset of the class data.
-     * @param len
-     *            the length of the class data.
-     */
-    public ClassReader(final byte[] b, final int off, final int len) {
-        this.b = b;
-        // checks the class version
-        if (readShort(off + 6) > Opcodes.V9) {
-            throw new IllegalArgumentException();
-        }
-        // parses the constant pool
-        items = new int[readUnsignedShort(off + 8)];
-        int n = items.length;
-        strings = new String[n];
-        int max = 0;
-        int index = off + 10;
-        for (int i = 1; i < n; ++i) {
-            items[i] = index + 1;
-            int size;
-            switch (b[index]) {
-            case ClassWriter.FIELD:
-            case ClassWriter.METH:
-            case ClassWriter.IMETH:
-            case ClassWriter.INT:
-            case ClassWriter.FLOAT:
-            case ClassWriter.NAME_TYPE:
-            case ClassWriter.INDY:
-                size = 5;
-                break;
-            case ClassWriter.LONG:
-            case ClassWriter.DOUBLE:
-                size = 9;
-                ++i;
-                break;
-            case ClassWriter.UTF8:
-                size = 3 + readUnsignedShort(index + 1);
-                if (size > max) {
-                    max = size;
-                }
-                break;
-            case ClassWriter.HANDLE:
-                size = 4;
-                break;
-            // case ClassWriter.CLASS:
-            // case ClassWriter.STR:
-            // case ClassWriter.MTYPE
-            // case ClassWriter.PACKAGE:
-            // case ClassWriter.MODULE:
-            default:
-                size = 3;
-                break;
-            }
-            index += size;
-        }
-        maxStringLength = max;
-        // the class header information starts just after the constant pool
-        header = index;
-    }
-
-    /**
-     * 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() getInternalName}).
-     * 
-     * @return the internal class name
-     * 
-     * @see ClassVisitor#visit(int, int, String, String, String, String[])
-     */
-    public String getClassName() {
-        return readClass(header + 2, new char[maxStringLength]);
-    }
-
-    /**
-     * Returns the internal of name of the super class (see
-     * {@link Type#getInternalName() getInternalName}). For interfaces, the
-     * super class is {@link Object}.
-     * 
-     * @return the internal name of super class, or <tt>null</tt> for
-     *         {@link Object} class.
-     * 
-     * @see ClassVisitor#visit(int, int, String, String, String, String[])
-     */
-    public String getSuperName() {
-        return readClass(header + 4, new char[maxStringLength]);
-    }
-
-    /**
-     * Returns the internal names of the class's interfaces (see
-     * {@link Type#getInternalName() getInternalName}).
-     * 
-     * @return the array of internal names for all implemented interfaces or
-     *         <tt>null</tt>.
-     * 
-     * @see ClassVisitor#visit(int, int, String, String, String, String[])
-     */
-    public String[] getInterfaces() {
-        int index = header + 6;
-        int n = readUnsignedShort(index);
-        String[] interfaces = new String[n];
-        if (n > 0) {
-            char[] buf = new char[maxStringLength];
-            for (int i = 0; i < n; ++i) {
-                index += 2;
-                interfaces[i] = readClass(index, buf);
-            }
-        }
-        return interfaces;
-    }
-
-    /**
-     * Copies the constant pool data into the given {@link ClassWriter}. Should
-     * be called before the {@link #accept(ClassVisitor,int)} method.
-     * 
-     * @param classWriter
-     *            the {@link ClassWriter} to copy constant pool into.
-     */
-    void copyPool(final ClassWriter classWriter) {
-        char[] buf = new char[maxStringLength];
-        int ll = items.length;
-        Item[] items2 = new Item[ll];
-        for (int i = 1; i < ll; i++) {
-            int index = items[i];
-            int tag = b[index - 1];
-            Item item = new Item(i);
-            int nameType;
-            switch (tag) {
-            case ClassWriter.FIELD:
-            case ClassWriter.METH:
-            case ClassWriter.IMETH:
-                nameType = items[readUnsignedShort(index + 2)];
-                item.set(tag, readClass(index, buf), readUTF8(nameType, buf),
-                        readUTF8(nameType + 2, buf));
-                break;
-            case ClassWriter.INT:
-                item.set(readInt(index));
-                break;
-            case ClassWriter.FLOAT:
-                item.set(Float.intBitsToFloat(readInt(index)));
-                break;
-            case ClassWriter.NAME_TYPE:
-                item.set(tag, readUTF8(index, buf), readUTF8(index + 2, buf),
-                        null);
-                break;
-            case ClassWriter.LONG:
-                item.set(readLong(index));
-                ++i;
-                break;
-            case ClassWriter.DOUBLE:
-                item.set(Double.longBitsToDouble(readLong(index)));
-                ++i;
-                break;
-            case ClassWriter.UTF8: {
-                String s = strings[i];
-                if (s == null) {
-                    index = items[i];
-                    s = strings[i] = readUTF(index + 2,
-                            readUnsignedShort(index), buf);
-                }
-                item.set(tag, s, null, null);
-                break;
-            }
-            case ClassWriter.HANDLE: {
-                int fieldOrMethodRef = items[readUnsignedShort(index + 1)];
-                nameType = items[readUnsignedShort(fieldOrMethodRef + 2)];
-                item.set(ClassWriter.HANDLE_BASE + readByte(index),
-                        readClass(fieldOrMethodRef, buf),
-                        readUTF8(nameType, buf), readUTF8(nameType + 2, buf));
-                break;
-            }
-            case ClassWriter.INDY:
-                if (classWriter.bootstrapMethods == null) {
-                    copyBootstrapMethods(classWriter, items2, buf);
-                }
-                nameType = items[readUnsignedShort(index + 2)];
-                item.set(readUTF8(nameType, buf), readUTF8(nameType + 2, buf),
-                        readUnsignedShort(index));
-                break;
-            // case ClassWriter.STR:
-            // case ClassWriter.CLASS:
-            // case ClassWriter.MTYPE:
-            // case ClassWriter.MODULE:
-            // case ClassWriter.PACKAGE:
-            default:
-                item.set(tag, readUTF8(index, buf), null, null);
-                break;
-            }
+  /**
+   * 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.V12) {
+      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;
+    }
 
-            int index2 = item.hashCode % items2.length;
-            item.next = items2[index2];
-            items2[index2] = item;
-        }
+    // 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;
+    }
 
-        int off = items[1] - 1;
-        classWriter.pool.putByteArray(b, off, header - off);
-        classWriter.items = items2;
-        classWriter.threshold = (int) (0.75d * ll);
-        classWriter.index = ll;
-    }
-
-    /**
-     * Copies the bootstrap method data into the given {@link ClassWriter}.
-     * Should be called before the {@link #accept(ClassVisitor,int)} method.
-     * 
-     * @param classWriter
-     *            the {@link ClassWriter} to copy bootstrap methods into.
-     */
-    private void copyBootstrapMethods(final ClassWriter classWriter,
-            final Item[] items, final char[] c) {
-        // finds the "BootstrapMethods" attribute
-        int u = getAttributes();
-        boolean found = false;
-        for (int i = readUnsignedShort(u); i > 0; --i) {
-            String attrName = readUTF8(u + 2, c);
-            if ("BootstrapMethods".equals(attrName)) {
-                found = true;
-                break;
-            }
-            u += 6 + readInt(u + 4);
-        }
-        if (!found) {
-            return;
-        }
-        // copies the bootstrap methods in the class writer
-        int boostrapMethodCount = readUnsignedShort(u + 8);
-        for (int j = 0, v = u + 10; j < boostrapMethodCount; j++) {
-            int position = v - u - 10;
-            int hashCode = readConst(readUnsignedShort(v), c).hashCode();
-            for (int k = readUnsignedShort(v + 2); k > 0; --k) {
-                hashCode ^= readConst(readUnsignedShort(v + 4), c).hashCode();
-                v += 2;
-            }
-            v += 4;
-            Item item = new Item(j);
-            item.set(position, hashCode & 0x7FFFFFFF);
-            int index = item.hashCode % items.length;
-            item.next = items[index];
-            items[index] = item;
-        }
-        int attrSize = readInt(u + 4);
-        ByteVector bootstrapMethods = new ByteVector(attrSize + 62);
-        bootstrapMethods.putByteArray(b, u + 10, attrSize - 2);
-        classWriter.bootstrapMethodsCount = boostrapMethodCount;
-        classWriter.bootstrapMethods = bootstrapMethods;
-    }
-
-    /**
-     * Constructs a new {@link ClassReader} object.
-     * 
-     * @param is
-     *            an input stream from which to read the class.
-     * @throws IOException
-     *             if a problem occurs during reading.
-     */
-    public ClassReader(final InputStream is) throws IOException {
-        this(readClass(is, false));
-    }
-
-    /**
-     * Constructs a new {@link ClassReader} object.
-     * 
-     * @param name
-     *            the binary qualified name of the class to be read.
-     * @throws IOException
-     *             if an exception occurs during reading.
-     */
-    public ClassReader(final String name) throws IOException {
-        this(readClass(
-                ClassLoader.getSystemResourceAsStream(name.replace('.', '/')
-                        + ".class"), true));
-    }
-
-    /**
-     * Reads the bytecode of a class.
-     * 
-     * @param is
-     *            an input stream from which to read the class.
-     * @param close
-     *            true to close the input stream after reading.
-     * @return the bytecode read from the given input stream.
-     * @throws IOException
-     *             if a problem occurs during reading.
-     */
-    private static byte[] readClass(final InputStream is, boolean close)
-            throws IOException {
-        if (is == null) {
-            throw new IOException("Class not found");
-        }
-        try {
-            byte[] b = new byte[is.available()];
-            int len = 0;
-            while (true) {
-                int n = is.read(b, len, b.length - len);
-                if (n == -1) {
-                    if (len < b.length) {
-                        byte[] c = new byte[len];
-                        System.arraycopy(b, 0, c, 0, len);
-                        b = c;
-                    }
-                    return b;
-                }
-                len += n;
-                if (len == b.length) {
-                    int last = is.read();
-                    if (last < 0) {
-                        return b;
-                    }
-                    byte[] c = new byte[b.length + 1000];
-                    System.arraycopy(b, 0, c, 0, len);
-                    c[len++] = (byte) last;
-                    b = c;
-                }
-            }
-        } finally {
-            if (close) {
-                is.close();
-            }
-        }
+    // 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);
     }
 
-    // ------------------------------------------------------------------------
-    // Public methods
-    // ------------------------------------------------------------------------
-
-    /**
-     * Makes the given visitor visit the Java class of this {@link ClassReader}
-     * . This class is the one specified in the constructor (see
-     * {@link #ClassReader(byte[]) ClassReader}).
-     * 
-     * @param classVisitor
-     *            the visitor that must visit this class.
-     * @param flags
-     *            option flags that can be used to modify the default behavior
-     *            of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES}
-     *            , {@link #SKIP_FRAMES}, {@link #SKIP_CODE}.
-     */
-    public void accept(final ClassVisitor classVisitor, final int flags) {
-        accept(classVisitor, new Attribute[0], flags);
-    }
-
-    /**
-     * Makes the given visitor visit the Java class of this {@link ClassReader}.
-     * This class is the one specified in the constructor (see
-     * {@link #ClassReader(byte[]) ClassReader}).
-     * 
-     * @param classVisitor
-     *            the visitor that must visit this class.
-     * @param attrs
-     *            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 flags
-     *            option flags that can be used to modify the default behavior
-     *            of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES}
-     *            , {@link #SKIP_FRAMES}, {@link #SKIP_CODE}.
-     */
-    public void accept(final ClassVisitor classVisitor,
-            final Attribute[] attrs, final int flags) {
-        int u = header; // current offset in the class file
-        char[] c = new char[maxStringLength]; // buffer used to read strings
-
-        Context context = new Context();
-        context.attrs = attrs;
-        context.flags = flags;
-        context.buffer = c;
-
-        // reads the class declaration
-        int access = readUnsignedShort(u);
-        String name = readClass(u + 2, c);
-        String superClass = readClass(u + 4, c);
-        String[] interfaces = new String[readUnsignedShort(u + 6)];
-        u += 8;
-        for (int i = 0; i < interfaces.length; ++i) {
-            interfaces[i] = readClass(u, c);
-            u += 2;
-        }
+    // Visit the Module, ModulePackages and ModuleMainClass attributes.
+    if (moduleOffset != 0) {
+      readModuleAttributes(
+          classVisitor, context, moduleOffset, modulePackagesOffset, moduleMainClass);
+    }
 
-        // reads the class attributes
-        String signature = null;
-        String sourceFile = null;
-        String sourceDebug = null;
-        String enclosingOwner = null;
-        String enclosingName = null;
-        String enclosingDesc = null;
-        String moduleMainClass = null;
-        int anns = 0;
-        int ianns = 0;
-        int tanns = 0;
-        int itanns = 0;
-        int innerClasses = 0;
-        int module = 0;
-        int packages = 0;
-        Attribute attributes = null;
-
-        u = getAttributes();
-        for (int i = readUnsignedShort(u); i > 0; --i) {
-            String attrName = readUTF8(u + 2, c);
-            // tests are sorted in decreasing frequency order
-            // (based on frequencies observed on typical classes)
-            if ("SourceFile".equals(attrName)) {
-                sourceFile = readUTF8(u + 8, c);
-            } else if ("InnerClasses".equals(attrName)) {
-                innerClasses = u + 8;
-            } else if ("EnclosingMethod".equals(attrName)) {
-                enclosingOwner = readClass(u + 8, c);
-                int item = readUnsignedShort(u + 10);
-                if (item != 0) {
-                    enclosingName = readUTF8(items[item], c);
-                    enclosingDesc = readUTF8(items[item] + 2, c);
-                }
-            } else if ("Signature".equals(attrName)) {
-                signature = readUTF8(u + 8, c);
-            } else if ("RuntimeVisibleAnnotations".equals(attrName)) {
-                anns = u + 8;
-            } else if ("RuntimeVisibleTypeAnnotations".equals(attrName)) {
-                tanns = u + 8;
-            } else if ("Deprecated".equals(attrName)) {
-                access |= Opcodes.ACC_DEPRECATED;
-            } else if ("Synthetic".equals(attrName)) {
-                access |= Opcodes.ACC_SYNTHETIC
-                        | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
-            } else if ("SourceDebugExtension".equals(attrName)) {
-                int len = readInt(u + 4);
-                sourceDebug = readUTF(u + 8, len, new char[len]);
-            } else if ("RuntimeInvisibleAnnotations".equals(attrName)) {
-                ianns = u + 8;
-            } else if ("RuntimeInvisibleTypeAnnotations".equals(attrName)) {
-                itanns = u + 8;
-            } else if ("Module".equals(attrName)) {
-                module = u + 8;
-            } else if ("ModuleMainClass".equals(attrName)) {
-                moduleMainClass = readClass(u + 8, c);
-            } else if ("ModulePackages".equals(attrName)) {
-                packages = u + 10;
-            } else if ("BootstrapMethods".equals(attrName)) {
-                int[] bootstrapMethods = new int[readUnsignedShort(u + 8)];
-                for (int j = 0, v = u + 10; j < bootstrapMethods.length; j++) {
-                    bootstrapMethods[j] = v;
-                    v += 2 + readUnsignedShort(v + 2) << 1;
-                }
-                context.bootstrapMethods = bootstrapMethods;
-            } else {
-                Attribute attr = readAttribute(attrs, attrName, u + 8,
-                        readInt(u + 4), c, -1, null);
-                if (attr != null) {
-                    attr.next = attributes;
-                    attributes = attr;
-                }
-            }
-            u += 6 + readInt(u + 4);
-        }
+    // Visit the NestHost attribute.
+    if (nestHostClass != null) {
+      classVisitor.visitNestHost(nestHostClass);
+    }
 
-        // visits the class declaration
-        classVisitor.visit(readInt(items[1] - 7), access, name, signature,
-                superClass, interfaces);
+    // 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);
+    }
 
-        // visits the source and debug info
-        if ((flags & SKIP_DEBUG) == 0
-                && (sourceFile != null || sourceDebug != null)) {
-            classVisitor.visitSource(sourceFile, sourceDebug);
-        }
+    // 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);
+      }
+    }
 
-        // visits the module info and associated attributes
-        if (module != 0) {
-            readModule(classVisitor, context, module,
-                    moduleMainClass, packages);
-        }
-        
-        // visits the outer class
-        if (enclosingOwner != null) {
-            classVisitor.visitOuterClass(enclosingOwner, enclosingName,
-                    enclosingDesc);
-        }
+    // 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);
+      }
+    }
 
-        // visits the class annotations and type annotations
-        if (anns != 0) {
-            for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
-                v = readAnnotationValues(v + 2, c, true,
-                        classVisitor.visitAnnotation(readUTF8(v, c), true));
-            }
-        }
-        if (ianns != 0) {
-            for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
-                v = readAnnotationValues(v + 2, c, true,
-                        classVisitor.visitAnnotation(readUTF8(v, c), false));
-            }
-        }
-        if (tanns != 0) {
-            for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
-                v = readAnnotationTarget(context, v);
-                v = readAnnotationValues(v + 2, c, true,
-                        classVisitor.visitTypeAnnotation(context.typeRef,
-                                context.typePath, readUTF8(v, c), true));
-            }
-        }
-        if (itanns != 0) {
-            for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
-                v = readAnnotationTarget(context, v);
-                v = readAnnotationValues(v + 2, c, true,
-                        classVisitor.visitTypeAnnotation(context.typeRef,
-                                context.typePath, readUTF8(v, c), false));
-            }
-        }
+    // 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);
+      }
+    }
 
-        // visits the attributes
-        while (attributes != null) {
-            Attribute attr = attributes.next;
-            attributes.next = null;
-            classVisitor.visitAttribute(attributes);
-            attributes = attr;
-        }
+    // 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);
+      }
+    }
 
-        // visits the inner classes
-        if (innerClasses != 0) {
-            int v = innerClasses + 2;
-            for (int i = readUnsignedShort(innerClasses); i > 0; --i) {
-                classVisitor.visitInnerClass(readClass(v, c),
-                        readClass(v + 2, c), readUTF8(v + 4, c),
-                        readUnsignedShort(v + 6));
-                v += 8;
-            }
-        }
+    // 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;
+    }
 
-        // visits the fields and methods
-        u = header + 10 + 2 * interfaces.length;
-        for (int i = readUnsignedShort(u - 2); i > 0; --i) {
-            u = readField(classVisitor, context, u);
-        }
-        u += 2;
-        for (int i = readUnsignedShort(u - 2); i > 0; --i) {
-            u = readMethod(classVisitor, context, u);
-        }
+    // 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;
+      }
+    }
 
-        // visits the end of the class
-        classVisitor.visitEnd();
-    }
-
-    /**
-     * Reads the module attribute and visit it.
-     * 
-     * @param classVisitor
-     *           the current class visitor
-     * @param context
-     *           information about the class being parsed.
-     * @param u
-     *           start offset of the module attribute in the class file.
-     * @param mainClass
-     *           name of the main class of a module or null.
-     * @param packages
-     *           start offset of the concealed package attribute.
-     */
-    private void readModule(final ClassVisitor classVisitor,
-            final Context context, int u,
-            final String mainClass, int packages) {
-    
-        char[] buffer = context.buffer;
-        
-        // reads module name, flags and version
-        String name = readModule(u, buffer);
-        int flags = readUnsignedShort(u + 2);
-        String version = readUTF8(u + 4, buffer);
-        u += 6;
-    
-        ModuleVisitor mv = classVisitor.visitModule(name, flags, version);
-        if (mv == null) {
-            return;
-        }
-        
-        // module attributes (main class, packages)
-        if (mainClass != null) {
-            mv.visitMainClass(mainClass);
-        }
-        
-        if (packages != 0) {
-            for (int i = readUnsignedShort(packages - 2); i > 0; --i) {
-                String packaze = readPackage(packages, buffer);
-                mv.visitPackage(packaze);
-                packages += 2;
-            }
-        }
-        
-        // reads requires
-        u += 2;
-        for (int i = readUnsignedShort(u - 2); i > 0; --i) {
-            String module = readModule(u, buffer);
-            int access = readUnsignedShort(u + 2);
-            String requireVersion = readUTF8(u + 4, buffer);
-            mv.visitRequire(module, access, requireVersion);
-            u += 6;
-        }
-        
-        // reads exports
-        u += 2;
-        for (int i = readUnsignedShort(u - 2); i > 0; --i) {
-            String export = readPackage(u, buffer);
-            int access = readUnsignedShort(u + 2);
-            int exportToCount = readUnsignedShort(u + 4);
-            u += 6;
-            String[] tos = null;
-            if (exportToCount != 0) {
-                tos = new String[exportToCount];
-                for (int j = 0; j < tos.length; ++j) {
-                    tos[j] = readModule(u, buffer);
-                    u += 2;
-                }
-            }
-            mv.visitExport(export, access, tos);
-        }
-        
-        // reads opens
-        u += 2;
-        for (int i = readUnsignedShort(u - 2); i > 0; --i) {
-            String open = readPackage(u, buffer);
-            int access = readUnsignedShort(u + 2);
-            int openToCount = readUnsignedShort(u + 4);
-            u += 6;
-            String[] tos = null;
-            if (openToCount != 0) {
-                tos = new String[openToCount];
-                for (int j = 0; j < tos.length; ++j) {
-                    tos[j] = readModule(u, buffer);
-                    u += 2;
-                }
-            }
-            mv.visitOpen(open, access, tos);
-        }
-        
-        // read uses
-        u += 2;
-        for (int i = readUnsignedShort(u - 2); i > 0; --i) {
-            mv.visitUse(readClass(u, buffer));
-            u += 2;
-        }
-        
-        // read provides
-        u += 2;
-        for (int i = readUnsignedShort(u - 2); i > 0; --i) {
-            String service = readClass(u, buffer);
-            int provideWithCount = readUnsignedShort(u + 2);
-            u += 4;
-            String[] withs = new String[provideWithCount];
-            for (int j = 0; j < withs.length; ++j) {
-                withs[j] = readClass(u, buffer);
-                u += 2;
-            }
-            mv.visitProvide(service, withs);
-        }
-        
-        mv.visitEnd();
-    }
-    
-    /**
-     * Reads a field 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 u
-     *            the start offset of the field in the class file.
-     * @return the offset of the first byte following the field in the class.
-     */
-    private int readField(final ClassVisitor classVisitor,
-            final Context context, int u) {
-        // reads the field declaration
-        char[] c = context.buffer;
-        int access = readUnsignedShort(u);
-        String name = readUTF8(u + 2, c);
-        String desc = readUTF8(u + 4, c);
-        u += 6;
-
-        // reads the field attributes
-        String signature = null;
-        int anns = 0;
-        int ianns = 0;
-        int tanns = 0;
-        int itanns = 0;
-        Object value = null;
-        Attribute attributes = null;
-
-        for (int i = readUnsignedShort(u); i > 0; --i) {
-            String attrName = readUTF8(u + 2, c);
-            // tests are sorted in decreasing frequency order
-            // (based on frequencies observed on typical classes)
-            if ("ConstantValue".equals(attrName)) {
-                int item = readUnsignedShort(u + 8);
-                value = item == 0 ? null : readConst(item, c);
-            } else if ("Signature".equals(attrName)) {
-                signature = readUTF8(u + 8, c);
-            } else if ("Deprecated".equals(attrName)) {
-                access |= Opcodes.ACC_DEPRECATED;
-            } else if ("Synthetic".equals(attrName)) {
-                access |= Opcodes.ACC_SYNTHETIC
-                        | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
-            } else if ("RuntimeVisibleAnnotations".equals(attrName)) {
-                anns = u + 8;
-            } else if ("RuntimeVisibleTypeAnnotations".equals(attrName)) {
-                tanns = u + 8;
-            } else if ("RuntimeInvisibleAnnotations".equals(attrName)) {
-                ianns = u + 8;
-            } else if ("RuntimeInvisibleTypeAnnotations".equals(attrName)) {
-                itanns = u + 8;
-            } else {
-                Attribute attr = readAttribute(context.attrs, attrName, u + 8,
-                        readInt(u + 4), c, -1, null);
-                if (attr != null) {
-                    attr.next = attributes;
-                    attributes = attr;
-                }
-            }
-            u += 6 + readInt(u + 4);
-        }
-        u += 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;
+      }
+    }
 
-        // visits the field declaration
-        FieldVisitor fv = classVisitor.visitField(access, name, desc,
-                signature, value);
-        if (fv == null) {
-            return u;
-        }
+    // 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);
+    }
 
-        // visits the field annotations and type annotations
-        if (anns != 0) {
-            for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
-                v = readAnnotationValues(v + 2, c, true,
-                        fv.visitAnnotation(readUTF8(v, c), true));
-            }
-        }
-        if (ianns != 0) {
-            for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
-                v = readAnnotationValues(v + 2, c, true,
-                        fv.visitAnnotation(readUTF8(v, c), false));
-            }
-        }
-        if (tanns != 0) {
-            for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
-                v = readAnnotationTarget(context, v);
-                v = readAnnotationValues(v + 2, c, true,
-                        fv.visitTypeAnnotation(context.typeRef,
-                                context.typePath, readUTF8(v, c), true));
-            }
-        }
-        if (itanns != 0) {
-            for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
-                v = readAnnotationTarget(context, v);
-                v = readAnnotationValues(v + 2, c, true,
-                        fv.visitTypeAnnotation(context.typeRef,
-                                context.typePath, readUTF8(v, c), false));
-            }
-        }
+    // 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;
+    }
 
-        // visits the field attributes
-        while (attributes != null) {
-            Attribute attr = attributes.next;
-            attributes.next = null;
-            fv.visitAttribute(attributes);
-            attributes = attr;
-        }
+    // Visit the ModuleMainClass attribute.
+    if (moduleMainClass != null) {
+      moduleVisitor.visitMainClass(moduleMainClass);
+    }
 
-        // visits the end of the field
-        fv.visitEnd();
-
-        return u;
-    }
-
-    /**
-     * Reads a method 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 u
-     *            the start offset of the method in the class file.
-     * @return the offset of the first byte following the method in the class.
-     */
-    private int readMethod(final ClassVisitor classVisitor,
-            final Context context, int u) {
-        // reads the method declaration
-        char[] c = context.buffer;
-        context.access = readUnsignedShort(u);
-        context.name = readUTF8(u + 2, c);
-        context.desc = readUTF8(u + 4, c);
-        u += 6;
-
-        // reads the method attributes
-        int code = 0;
-        int exception = 0;
-        String[] exceptions = null;
-        String signature = null;
-        int methodParameters = 0;
-        int anns = 0;
-        int ianns = 0;
-        int tanns = 0;
-        int itanns = 0;
-        int dann = 0;
-        int mpanns = 0;
-        int impanns = 0;
-        int firstAttribute = u;
-        Attribute attributes = null;
-
-        for (int i = readUnsignedShort(u); i > 0; --i) {
-            String attrName = readUTF8(u + 2, c);
-            // tests are sorted in decreasing frequency order
-            // (based on frequencies observed on typical classes)
-            if ("Code".equals(attrName)) {
-                if ((context.flags & SKIP_CODE) == 0) {
-                    code = u + 8;
-                }
-            } else if ("Exceptions".equals(attrName)) {
-                exceptions = new String[readUnsignedShort(u + 8)];
-                exception = u + 10;
-                for (int j = 0; j < exceptions.length; ++j) {
-                    exceptions[j] = readClass(exception, c);
-                    exception += 2;
-                }
-            } else if ("Signature".equals(attrName)) {
-                signature = readUTF8(u + 8, c);
-            } else if ("Deprecated".equals(attrName)) {
-                context.access |= Opcodes.ACC_DEPRECATED;
-            } else if ("RuntimeVisibleAnnotations".equals(attrName)) {
-                anns = u + 8;
-            } else if ("RuntimeVisibleTypeAnnotations".equals(attrName)) {
-                tanns = u + 8;
-            } else if ("AnnotationDefault".equals(attrName)) {
-                dann = u + 8;
-            } else if ("Synthetic".equals(attrName)) {
-                context.access |= Opcodes.ACC_SYNTHETIC
-                        | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
-            } else if ("RuntimeInvisibleAnnotations".equals(attrName)) {
-                ianns = u + 8;
-            } else if ("RuntimeInvisibleTypeAnnotations".equals(attrName)) {
-                itanns = u + 8;
-            } else if ("RuntimeVisibleParameterAnnotations".equals(attrName)) {
-                mpanns = u + 8;
-            } else if ("RuntimeInvisibleParameterAnnotations".equals(attrName)) {
-                impanns = u + 8;
-            } else if ("MethodParameters".equals(attrName)) {
-                methodParameters = u + 8;
-            } else {
-                Attribute attr = readAttribute(context.attrs, attrName, u + 8,
-                        readInt(u + 4), c, -1, null);
-                if (attr != null) {
-                    attr.next = attributes;
-                    attributes = attr;
-                }
-            }
-            u += 6 + readInt(u + 4);
-        }
-        u += 2;
+    // 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;
+      }
+    }
 
-        // visits the method declaration
-        MethodVisitor mv = classVisitor.visitMethod(context.access,
-                context.name, context.desc, signature, exceptions);
-        if (mv == null) {
-            return u;
-        }
+    // 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);
+    }
 
-        /*
-         * if the returned MethodVisitor is in fact a MethodWriter, it means
-         * there is no method adapter between the reader and the writer. If, in
-         * addition, the writer's constant pool was copied from this reader
-         * (mw.cw.cr == this), and the signature and exceptions of the method
-         * have not been changed, then it is possible to skip all visit events
-         * and just copy the original code of the method to the writer (the
-         * access, name and descriptor can have been changed, this is not
-         * important since they are not copied as is from the reader).
-         */
-        if (mv instanceof MethodWriter) {
-            MethodWriter mw = (MethodWriter) mv;
-            if (mw.cw.cr == this && signature == mw.signature) {
-                boolean sameExceptions = false;
-                if (exceptions == null) {
-                    sameExceptions = mw.exceptionCount == 0;
-                } else if (exceptions.length == mw.exceptionCount) {
-                    sameExceptions = true;
-                    for (int j = exceptions.length - 1; j >= 0; --j) {
-                        exception -= 2;
-                        if (mw.exceptions[j] != readUnsignedShort(exception)) {
-                            sameExceptions = false;
-                            break;
-                        }
-                    }
-                }
-                if (sameExceptions) {
-                    /*
-                     * we do not copy directly the code into MethodWriter to
-                     * save a byte array copy operation. The real copy will be
-                     * done in ClassWriter.toByteArray().
-                     */
-                    mw.classReaderOffset = firstAttribute;
-                    mw.classReaderLength = u - firstAttribute;
-                    return u;
-                }
-            }
-        }
+    // 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);
+    }
 
-        // visit the method parameters
-        if (methodParameters != 0) {
-            for (int i = b[methodParameters] & 0xFF, v = methodParameters + 1; i > 0; --i, v = v + 4) {
-                mv.visitParameter(readUTF8(v, c), readUnsignedShort(v + 2));
-            }
-        }
+    // 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);
+    }
 
-        // visits the method annotations
-        if (dann != 0) {
-            AnnotationVisitor dv = mv.visitAnnotationDefault();
-            readAnnotationValue(dann, c, null, dv);
-            if (dv != null) {
-                dv.visitEnd();
-            }
-        }
-        if (anns != 0) {
-            for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
-                v = readAnnotationValues(v + 2, c, true,
-                        mv.visitAnnotation(readUTF8(v, c), true));
-            }
-        }
-        if (ianns != 0) {
-            for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
-                v = readAnnotationValues(v + 2, c, true,
-                        mv.visitAnnotation(readUTF8(v, c), false));
-            }
-        }
-        if (tanns != 0) {
-            for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
-                v = readAnnotationTarget(context, v);
-                v = readAnnotationValues(v + 2, c, true,
-                        mv.visitTypeAnnotation(context.typeRef,
-                                context.typePath, readUTF8(v, c), true));
-            }
-        }
-        if (itanns != 0) {
-            for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
-                v = readAnnotationTarget(context, v);
-                v = readAnnotationValues(v + 2, c, true,
-                        mv.visitTypeAnnotation(context.typeRef,
-                                context.typePath, readUTF8(v, c), false));
-            }
-        }
-        if (mpanns != 0) {
-            readParameterAnnotations(mv, context, mpanns, true);
-        }
-        if (impanns != 0) {
-            readParameterAnnotations(mv, context, impanns, false);
-        }
+    // Read the 'uses_count' and 'uses' fields.
+    int usesCount = readUnsignedShort(currentOffset);
+    currentOffset += 2;
+    while (usesCount-- > 0) {
+      moduleVisitor.visitUse(readClass(currentOffset, buffer));
+      currentOffset += 2;
+    }
 
-        // visits the method attributes
-        while (attributes != null) {
-            Attribute attr = attributes.next;
-            attributes.next = null;
-            mv.visitAttribute(attributes);
-            attributes = attr;
-        }
+    // 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);
+    }
 
-        // visits the method code
-        if (code != 0) {
-            mv.visitCode();
-            readCode(mv, context, code);
-        }
+    // 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;
+    }
 
-        // visits the end of the method
-        mv.visitEnd();
-
-        return u;
-    }
-
-    /**
-     * Reads the bytecode of a method and makes the given visitor visit it.
-     * 
-     * @param mv
-     *            the visitor that must visit the method's code.
-     * @param context
-     *            information about the class being parsed.
-     * @param u
-     *            the start offset of the code attribute in the class file.
-     */
-    private void readCode(final MethodVisitor mv, final Context context, int u) {
-        // reads the header
-        byte[] b = this.b;
-        char[] c = context.buffer;
-        int maxStack = readUnsignedShort(u);
-        int maxLocals = readUnsignedShort(u + 2);
-        int codeLength = readInt(u + 4);
-        u += 8;
-
-        // reads the bytecode to find the labels
-        int codeStart = u;
-        int codeEnd = u + codeLength;
-        Label[] labels = context.labels = new Label[codeLength + 2];
-        createLabel(codeLength + 1, labels);
-        while (u < codeEnd) {
-            int offset = u - codeStart;
-            int opcode = b[u] & 0xFF;
-            switch (ClassWriter.TYPE[opcode]) {
-            case ClassWriter.NOARG_INSN:
-            case ClassWriter.IMPLVAR_INSN:
-                u += 1;
-                break;
-            case ClassWriter.LABEL_INSN:
-                createLabel(offset + readShort(u + 1), labels);
-                u += 3;
-                break;
-            case ClassWriter.ASM_LABEL_INSN:
-                createLabel(offset + readUnsignedShort(u + 1), labels);
-                u += 3;
-                break;
-            case ClassWriter.LABELW_INSN:
-            case ClassWriter.ASM_LABELW_INSN:
-                createLabel(offset + readInt(u + 1), labels);
-                u += 5;
-                break;
-            case ClassWriter.WIDE_INSN:
-                opcode = b[u + 1] & 0xFF;
-                if (opcode == Opcodes.IINC) {
-                    u += 6;
-                } else {
-                    u += 4;
-                }
-                break;
-            case ClassWriter.TABL_INSN:
-                // skips 0 to 3 padding bytes
-                u = u + 4 - (offset & 3);
-                // reads instruction
-                createLabel(offset + readInt(u), labels);
-                for (int i = readInt(u + 8) - readInt(u + 4) + 1; i > 0; --i) {
-                    createLabel(offset + readInt(u + 12), labels);
-                    u += 4;
-                }
-                u += 12;
-                break;
-            case ClassWri

<TRUNCATED>