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:33:58 UTC

[11/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/util/CheckClassAdapter.java
----------------------------------------------------------------------
diff --git a/plastic/src/external/java/org/apache/tapestry5/internal/plastic/asm/util/CheckClassAdapter.java b/plastic/src/external/java/org/apache/tapestry5/internal/plastic/asm/util/CheckClassAdapter.java
old mode 100644
new mode 100755
index f66971c..c0b129c
--- a/plastic/src/external/java/org/apache/tapestry5/internal/plastic/asm/util/CheckClassAdapter.java
+++ b/plastic/src/external/java/org/apache/tapestry5/internal/plastic/asm/util/CheckClassAdapter.java
@@ -1,1035 +1,1066 @@
-/***
- * 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.util;
 
+import org.apache.tapestry5.internal.plastic.asm.*;
+import org.apache.tapestry5.internal.plastic.asm.tree.ClassNode;
+import org.apache.tapestry5.internal.plastic.asm.tree.MethodNode;
+import org.apache.tapestry5.internal.plastic.asm.tree.TryCatchBlockNode;
+import org.apache.tapestry5.internal.plastic.asm.tree.analysis.*;
+import org.apache.tapestry5.internal.plastic.asm.tree.analysis.Frame;
+
 import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
-import org.apache.tapestry5.internal.plastic.asm.AnnotationVisitor;
-import org.apache.tapestry5.internal.plastic.asm.Attribute;
-import org.apache.tapestry5.internal.plastic.asm.ClassReader;
-import org.apache.tapestry5.internal.plastic.asm.ClassVisitor;
-import org.apache.tapestry5.internal.plastic.asm.FieldVisitor;
-import org.apache.tapestry5.internal.plastic.asm.Label;
-import org.apache.tapestry5.internal.plastic.asm.MethodVisitor;
-import org.apache.tapestry5.internal.plastic.asm.ModuleVisitor;
-import org.apache.tapestry5.internal.plastic.asm.Opcodes;
-import org.apache.tapestry5.internal.plastic.asm.Type;
-import org.apache.tapestry5.internal.plastic.asm.TypePath;
-import org.apache.tapestry5.internal.plastic.asm.TypeReference;
-import org.apache.tapestry5.internal.plastic.asm.tree.ClassNode;
-import org.apache.tapestry5.internal.plastic.asm.tree.MethodNode;
-import org.apache.tapestry5.internal.plastic.asm.tree.analysis.Analyzer;
-import org.apache.tapestry5.internal.plastic.asm.tree.analysis.BasicValue;
-import org.apache.tapestry5.internal.plastic.asm.tree.analysis.Frame;
-import org.apache.tapestry5.internal.plastic.asm.tree.analysis.SimpleVerifier;
-
 /**
- * A {@link ClassVisitor} that checks that its methods are properly used. More
- * precisely this class adapter checks each method call individually, based
- * <i>only</i> on its arguments, but does <i>not</i> check the <i>sequence</i>
- * of method calls. For example, the invalid sequence
- * <tt>visitField(ACC_PUBLIC, "i", "I", null)</tt> <tt>visitField(ACC_PUBLIC,
- * "i", "D", null)</tt> will <i>not</i> be detected by this class adapter.
- * 
- * <p>
- * <code>CheckClassAdapter</code> can be also used to verify bytecode
- * transformations in order to make sure transformed bytecode is sane. For
- * example:
- * 
+ * A {@link ClassVisitor} that checks that its methods are properly used. More precisely this class
+ * adapter checks each method call individually, based <i>only</i> on its arguments, but does
+ * <i>not</i> check the <i>sequence</i> of method calls. For example, the invalid sequence {@code
+ * visitField(ACC_PUBLIC, "i", "I", null)} {@code visitField(ACC_PUBLIC, "i", "D", null)} will
+ * <i>not</i> be detected by this class adapter.
+ *
+ * <p><code>CheckClassAdapter</code> can be also used to verify bytecode transformations in order to
+ * make sure that the transformed bytecode is sane. For example:
+ *
  * <pre>
- *   InputStream is = ...; // get bytes for the source class
- *   ClassReader cr = new ClassReader(is);
- *   ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
- *   ClassVisitor cv = new <b>MyClassAdapter</b>(new CheckClassAdapter(cw));
- *   cr.accept(cv, 0);
- * 
- *   StringWriter sw = new StringWriter();
- *   PrintWriter pw = new PrintWriter(sw);
- *   CheckClassAdapter.verify(new ClassReader(cw.toByteArray()), false, pw);
- *   assertTrue(sw.toString(), sw.toString().length()==0);
+ * InputStream inputStream = ...; // get bytes for the source class
+ * ClassReader classReader = new ClassReader(inputStream);
+ * ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS);
+ * ClassVisitor classVisitor = new <b>MyClassAdapter</b>(new CheckClassAdapter(classWriter, true));
+ * classReader.accept(classVisitor, 0);
+ *
+ * StringWriter stringWriter = new StringWriter();
+ * PrintWriter printWriter = new PrintWriter(stringWriter);
+ * CheckClassAdapter.verify(new ClassReader(classWriter.toByteArray()), false, printWriter);
+ * assertTrue(stringWriter.toString().isEmpty());
  * </pre>
- * 
- * Above code runs transformed bytecode trough the
- * <code>CheckClassAdapter</code>. It won't be exactly the same verification as
- * JVM does, but it run data flow analysis for the code of each method and
- * checks that expectations are met for each method instruction.
- * 
- * <p>
- * If method bytecode has errors, assertion text will show the erroneous
- * instruction number and dump of the failed method with information about
- * locals and stack slot for each instruction. For example (format is -
- * insnNumber locals : stack):
- * 
+ *
+ * <p>The above code pass the transformed bytecode through a <code>CheckClassAdapter</code>, with
+ * data flow checks enabled. These checks are not exactly the same as the JVM verification, but
+ * provide some basic type checking for each method instruction. If the bytecode has errors, the
+ * output text shows the erroneous instruction number, and a dump of the failed method with
+ * information about the type of the local variables and of the operand stack slots for each
+ * instruction. For example (format is - insnNumber locals : stack):
+ *
  * <pre>
- * org.objectweb.asm.tree.analysis.AnalyzerException: Error at instruction 71: Expected I, but found .
- *   at org.objectweb.asm.tree.analysis.Analyzer.analyze(Analyzer.java:289)
- *   at org.objectweb.asm.util.CheckClassAdapter.verify(CheckClassAdapter.java:135)
+ * org.apache.tapestry5.internal.plastic.asm.tree.analysis.AnalyzerException: Error at instruction 71: Expected I, but found .
+ *   at org.apache.tapestry5.internal.plastic.asm.tree.analysis.Analyzer.analyze(Analyzer.java:...)
+ *   at org.apache.tapestry5.internal.plastic.asm.util.CheckClassAdapter.verify(CheckClassAdapter.java:...)
  * ...
  * remove()V
- * 00000 LinkedBlockingQueue$Itr . . . . . . . .  :
- *   ICONST_0
- * 00001 LinkedBlockingQueue$Itr . . . . . . . .  : I
- *   ISTORE 2
+ * 00000 LinkedBlockingQueue$Itr . . . . . . . .  : ICONST_0
+ * 00001 LinkedBlockingQueue$Itr . . . . . . . .  : I ISTORE 2
  * 00001 LinkedBlockingQueue$Itr <b>.</b> I . . . . . .  :
  * ...
- * 
- * 00071 LinkedBlockingQueue$Itr <b>.</b> I . . . . . .  :
- *   ILOAD 1
- * 00072 <b>?</b>
- *   INVOKESPECIAL java/lang/Integer.&lt;init&gt; (I)V
+ * 00071 LinkedBlockingQueue$Itr <b>.</b> I . . . . . .  : ILOAD 1
+ * 00072 <b>?</b> INVOKESPECIAL java/lang/Integer.&lt;init&gt; (I)V
  * ...
  * </pre>
- * 
- * In the above output you can see that variable 1 loaded by
- * <code>ILOAD 1</code> instruction at position <code>00071</code> is not
- * initialized. You can also see that at the beginning of the method (code
- * inserted by the transformation) variable 2 is initialized.
- * 
- * <p>
- * Note that when used like that, <code>CheckClassAdapter.verify()</code> can
- * trigger additional class loading, because it is using
- * <code>SimpleVerifier</code>.
- * 
+ *
+ * <p>The above output shows that the local variable 1, loaded by the <code>ILOAD 1</code>
+ * instruction at position <code>00071</code> is not initialized, whereas the local variable 2 is
+ * initialized and contains an int value.
+ *
  * @author Eric Bruneton
  */
 public class CheckClassAdapter extends ClassVisitor {
 
-    /**
-     * The class version number.
-     */
-    private int version;
-
-    /**
-     * <tt>true</tt> if the visit method has been called.
-     */
-    private boolean start;
-
-    /**
-     * <tt>true</tt> if the visitSource method has been called.
-     */
-    private boolean source;
-
-    /**
-     * <tt>true</tt> if the visitOuterClass method has been called.
-     */
-    private boolean outer;
-
-    /**
-     * <tt>true</tt> if the visitEnd method has been called.
-     */
-    private boolean end;
-    
-    /**
-     * <tt>true</tt> if the visitModule method has been called.
-     */
-    private boolean module;
-
-    /**
-     * The already visited labels. This map associate Integer values to Label
-     * keys.
-     */
-    private Map<Label, Integer> labels;
-
-    /**
-     * <tt>true</tt> if the method code must be checked with a BasicVerifier.
-     */
-    private boolean checkDataFlow;
-
-    /**
-     * Checks a given class.
-     * <p>
-     * Usage: CheckClassAdapter &lt;binary class name or class file name&gt;
-     * 
-     * @param args
-     *            the command line arguments.
-     * 
-     * @throws Exception
-     *             if the class cannot be found, or if an IO exception occurs.
-     */
-    public static void main(final String[] args) throws Exception {
-        if (args.length != 1) {
-            System.err.println("Verifies the given class.");
-            System.err.println("Usage: CheckClassAdapter "
-                    + "<fully qualified class name or class file name>");
-            return;
-        }
-        ClassReader cr;
-        if (args[0].endsWith(".class")) {
-            cr = new ClassReader(new FileInputStream(args[0]));
-        } else {
-            cr = new ClassReader(args[0]);
-        }
+  private static final String ERROR_AT = ": error at index ";
 
-        verify(cr, false, new PrintWriter(System.err));
-    }
-
-    /**
-     * Checks a given class.
-     * 
-     * @param cr
-     *            a <code>ClassReader</code> that contains bytecode for the
-     *            analysis.
-     * @param loader
-     *            a <code>ClassLoader</code> which will be used to load
-     *            referenced classes. This is useful if you are verifiying
-     *            multiple interdependent classes.
-     * @param dump
-     *            true if bytecode should be printed out not only when errors
-     *            are found.
-     * @param pw
-     *            write where results going to be printed
-     */
-    public static void verify(final ClassReader cr, final ClassLoader loader,
-            final boolean dump, final PrintWriter pw) {
-        ClassNode cn = new ClassNode();
-        cr.accept(new CheckClassAdapter(cn, false), ClassReader.SKIP_DEBUG);
-
-        Type syperType = cn.superName == null ? null : Type
-                .getObjectType(cn.superName);
-        List<MethodNode> methods = cn.methods;
-
-        List<Type> interfaces = new ArrayList<Type>();
-        for (Iterator<String> i = cn.interfaces.iterator(); i.hasNext();) {
-            interfaces.add(Type.getObjectType(i.next()));
-        }
+  /** Whether the bytecode must be checked with a BasicVerifier. */
+  private boolean checkDataFlow;
 
-        for (int i = 0; i < methods.size(); ++i) {
-            MethodNode method = methods.get(i);
-            SimpleVerifier verifier = new SimpleVerifier(
-                    Type.getObjectType(cn.name), syperType, interfaces,
-                    (cn.access & Opcodes.ACC_INTERFACE) != 0);
-            Analyzer<BasicValue> a = new Analyzer<BasicValue>(verifier);
-            if (loader != null) {
-                verifier.setClassLoader(loader);
-            }
-            try {
-                a.analyze(cn.name, method);
-                if (!dump) {
-                    continue;
-                }
-            } catch (Exception e) {
-                e.printStackTrace(pw);
-            }
-            printAnalyzerResult(method, a, pw);
-        }
-        pw.flush();
-    }
-
-    /**
-     * Checks a given class
-     * 
-     * @param cr
-     *            a <code>ClassReader</code> that contains bytecode for the
-     *            analysis.
-     * @param dump
-     *            true if bytecode should be printed out not only when errors
-     *            are found.
-     * @param pw
-     *            write where results going to be printed
-     */
-    public static void verify(final ClassReader cr, final boolean dump,
-            final PrintWriter pw) {
-        verify(cr, null, dump, pw);
-    }
-
-    static void printAnalyzerResult(MethodNode method, Analyzer<BasicValue> a,
-            final PrintWriter pw) {
-        Frame<BasicValue>[] frames = a.getFrames();
-        Textifier t = new Textifier();
-        TraceMethodVisitor mv = new TraceMethodVisitor(t);
-
-        pw.println(method.name + method.desc);
-        for (int j = 0; j < method.instructions.size(); ++j) {
-            method.instructions.get(j).accept(mv);
-
-            StringBuilder sb = new StringBuilder();
-            Frame<BasicValue> f = frames[j];
-            if (f == null) {
-                sb.append('?');
-            } else {
-                for (int k = 0; k < f.getLocals(); ++k) {
-                    sb.append(getShortName(f.getLocal(k).toString()))
-                            .append(' ');
-                }
-                sb.append(" : ");
-                for (int k = 0; k < f.getStackSize(); ++k) {
-                    sb.append(getShortName(f.getStack(k).toString()))
-                            .append(' ');
-                }
-            }
-            while (sb.length() < method.maxStack + method.maxLocals + 1) {
-                sb.append(' ');
-            }
-            pw.print(Integer.toString(j + 100000).substring(1));
-            pw.print(" " + sb + " : " + t.text.get(t.text.size() - 1));
-        }
-        for (int j = 0; j < method.tryCatchBlocks.size(); ++j) {
-            method.tryCatchBlocks.get(j).accept(mv);
-            pw.print(" " + t.text.get(t.text.size() - 1));
-        }
-        pw.println();
+  /** The class version number. */
+  private int version;
+
+  /** Whether the {@link #visit} method has been called. */
+  private boolean visitCalled;
+
+  /** Whether the {@link #visitModule} method has been called. */
+  private boolean visitModuleCalled;
+
+  /** Whether the {@link #visitSource} method has been called. */
+  private boolean visitSourceCalled;
+
+  /** Whether the {@link #visitOuterClass} method has been called. */
+  private boolean visitOuterClassCalled;
+
+  /** Whether the {@link #visitNestHost} method has been called. */
+  private boolean visitNestHostCalled;
+
+  /**
+   * The common package of all the nest members. Not {@literal null} if the visitNestMember method
+   * has been called.
+   */
+  private String nestMemberPackageName;
+
+  /** Whether the {@link #visitEnd} method has been called. */
+  private boolean visitEndCalled;
+
+  /** The index of the instruction designated by each visited label so far. */
+  private Map<Label, Integer> labelInsnIndices;
+
+  // -----------------------------------------------------------------------------------------------
+  // Constructors
+  // -----------------------------------------------------------------------------------------------
+
+  /**
+   * Constructs a new {@link CheckClassAdapter}. <i>Subclasses must not use this constructor</i>.
+   * Instead, they must use the {@link #CheckClassAdapter(int, ClassVisitor, boolean)} version.
+   *
+   * @param classVisitor the class visitor to which this adapter must delegate calls.
+   */
+  public CheckClassAdapter(final ClassVisitor classVisitor) {
+    this(classVisitor, true);
+  }
+
+  /**
+   * Constructs a new {@link CheckClassAdapter}. <i>Subclasses must not use this constructor</i>.
+   * Instead, they must use the {@link #CheckClassAdapter(int, ClassVisitor, boolean)} version.
+   *
+   * @param classVisitor the class visitor to which this adapter must delegate calls.
+   * @param checkDataFlow whether to perform basic data flow checks. This option requires valid
+   *     maxLocals and maxStack values.
+   * @throws IllegalStateException If a subclass calls this constructor.
+   */
+  public CheckClassAdapter(final ClassVisitor classVisitor, final boolean checkDataFlow) {
+    this(Opcodes.ASM7, classVisitor, checkDataFlow);
+    if (getClass() != CheckClassAdapter.class) {
+      throw new IllegalStateException();
     }
+  }
 
-    private static String getShortName(final String name) {
-        int n = name.lastIndexOf('/');
-        int k = name.length();
-        if (name.charAt(k - 1) == ';') {
-            k--;
-        }
-        return n == -1 ? name : name.substring(n + 1, k);
-    }
-
-    /**
-     * Constructs a new {@link CheckClassAdapter}. <i>Subclasses must not use
-     * this constructor</i>. Instead, they must use the
-     * {@link #CheckClassAdapter(int, ClassVisitor, boolean)} version.
-     * 
-     * @param cv
-     *            the class visitor to which this adapter must delegate calls.
-     */
-    public CheckClassAdapter(final ClassVisitor cv) {
-        this(cv, true);
-    }
-
-    /**
-     * Constructs a new {@link CheckClassAdapter}. <i>Subclasses must not use
-     * this constructor</i>. Instead, they must use the
-     * {@link #CheckClassAdapter(int, ClassVisitor, boolean)} version.
-     * 
-     * @param cv
-     *            the class visitor to which this adapter must delegate calls.
-     * @param checkDataFlow
-     *            <tt>true</tt> to perform basic data flow checks, or
-     *            <tt>false</tt> to not perform any data flow check (see
-     *            {@link CheckMethodAdapter}). This option requires valid
-     *            maxLocals and maxStack values.
-     * @throws IllegalStateException
-     *             If a subclass calls this constructor.
-     */
-    public CheckClassAdapter(final ClassVisitor cv, final boolean checkDataFlow) {
-        this(Opcodes.ASM6, cv, checkDataFlow);
-        if (getClass() != CheckClassAdapter.class) {
-            throw new IllegalStateException();
-        }
+  /**
+   * Constructs a new {@link CheckClassAdapter}.
+   *
+   * @param api the ASM API version implemented by this visitor. Must be one of {@link
+   *     Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+   * @param classVisitor the class visitor to which this adapter must delegate calls.
+   * @param checkDataFlow {@literal true} to perform basic data flow checks, or {@literal false} to
+   *     not perform any data flow check (see {@link CheckMethodAdapter}). This option requires
+   *     valid maxLocals and maxStack values.
+   */
+  protected CheckClassAdapter(
+      final int api, final ClassVisitor classVisitor, final boolean checkDataFlow) {
+    super(api, classVisitor);
+    this.labelInsnIndices = new HashMap<Label, Integer>();
+    this.checkDataFlow = checkDataFlow;
+  }
+
+  // -----------------------------------------------------------------------------------------------
+  // Implementation of the ClassVisitor interface
+  // -----------------------------------------------------------------------------------------------
+
+  @Override
+  public void visit(
+      final int version,
+      final int access,
+      final String name,
+      final String signature,
+      final String superName,
+      final String[] interfaces) {
+    if (visitCalled) {
+      throw new IllegalStateException("visit must be called only once");
+    }
+    visitCalled = true;
+    checkState();
+    checkAccess(
+        access,
+        Opcodes.ACC_PUBLIC
+            | Opcodes.ACC_FINAL
+            | Opcodes.ACC_SUPER
+            | Opcodes.ACC_INTERFACE
+            | Opcodes.ACC_ABSTRACT
+            | Opcodes.ACC_SYNTHETIC
+            | Opcodes.ACC_ANNOTATION
+            | Opcodes.ACC_ENUM
+            | Opcodes.ACC_DEPRECATED
+            | Opcodes.ACC_MODULE);
+    if (name == null) {
+      throw new IllegalArgumentException("Illegal class name (null)");
+    }
+    if (!name.endsWith("package-info") && !name.endsWith("module-info")) {
+      CheckMethodAdapter.checkInternalName(version, name, "class name");
+    }
+    if ("java/lang/Object".equals(name)) {
+      if (superName != null) {
+        throw new IllegalArgumentException(
+            "The super class name of the Object class must be 'null'");
+      }
+    } else if (name.endsWith("module-info")) {
+      if (superName != null) {
+        throw new IllegalArgumentException(
+            "The super class name of a module-info class must be 'null'");
+      }
+    } else {
+      CheckMethodAdapter.checkInternalName(version, superName, "super class name");
     }
+    if (signature != null) {
+      checkClassSignature(signature);
+    }
+    if ((access & Opcodes.ACC_INTERFACE) != 0 && !"java/lang/Object".equals(superName)) {
+      throw new IllegalArgumentException(
+          "The super class name of interfaces must be 'java/lang/Object'");
+    }
+    if (interfaces != null) {
+      for (int i = 0; i < interfaces.length; ++i) {
+        CheckMethodAdapter.checkInternalName(
+            version, interfaces[i], "interface name at index " + i);
+      }
+    }
+    this.version = version;
+    super.visit(version, access, name, signature, superName, interfaces);
+  }
 
-    /**
-     * Constructs a new {@link CheckClassAdapter}.
-     * 
-     * @param api
-     *            the ASM API version implemented by this visitor. Must be one
-     *            of {@link Opcodes#ASM4}, {@link Opcodes#ASM5} or {@link Opcodes#ASM6}.
-     * @param cv
-     *            the class visitor to which this adapter must delegate calls.
-     * @param checkDataFlow
-     *            <tt>true</tt> to perform basic data flow checks, or
-     *            <tt>false</tt> to not perform any data flow check (see
-     *            {@link CheckMethodAdapter}). This option requires valid
-     *            maxLocals and maxStack values.
-     */
-    protected CheckClassAdapter(final int api, final ClassVisitor cv,
-            final boolean checkDataFlow) {
-        super(api, cv);
-        this.labels = new HashMap<Label, Integer>();
-        this.checkDataFlow = checkDataFlow;
-    }
-
-    // ------------------------------------------------------------------------
-    // Implementation of the ClassVisitor interface
-    // ------------------------------------------------------------------------
-
-    @Override
-    public void visit(final int version, final int access, final String name,
-            final String signature, final String superName,
-            final String[] interfaces) {
-        if (start) {
-            throw new IllegalStateException("visit must be called only once");
-        }
-        start = true;
-        checkState();
-        checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL
-                + Opcodes.ACC_SUPER + Opcodes.ACC_INTERFACE
-                + Opcodes.ACC_ABSTRACT + Opcodes.ACC_SYNTHETIC
-                + Opcodes.ACC_ANNOTATION + Opcodes.ACC_ENUM
-                + Opcodes.ACC_DEPRECATED + Opcodes.ACC_MODULE
-                + 0x40000); // ClassWriter.ACC_SYNTHETIC_ATTRIBUTE
-        if (name == null) {
-            throw new IllegalArgumentException("Illegal class name (null)");
-        }
-        if (!name.endsWith("package-info")) {
-            CheckMethodAdapter.checkInternalName(name, "class name");
-        }
-        if ("java/lang/Object".equals(name)) {
-            if (superName != null) {
-                throw new IllegalArgumentException(
-                        "The super class name of the Object class must be 'null'");
-            }
-        } else {
-            CheckMethodAdapter.checkInternalName(superName, "super class name");
-        }
-        if (signature != null) {
-            checkClassSignature(signature);
-        }
-        if ((access & Opcodes.ACC_INTERFACE) != 0) {
-            if (!"java/lang/Object".equals(superName)) {
-                throw new IllegalArgumentException(
-                        "The super class name of interfaces must be 'java/lang/Object'");
-            }
-        }
-        if (interfaces != null) {
-            for (int i = 0; i < interfaces.length; ++i) {
-                CheckMethodAdapter.checkInternalName(interfaces[i],
-                        "interface name at index " + i);
-            }
-        }
-        this.version = version;
-        super.visit(version, access, name, signature, superName, interfaces);
+  @Override
+  public void visitSource(final String file, final String debug) {
+    checkState();
+    if (visitSourceCalled) {
+      throw new IllegalStateException("visitSource can be called only once.");
     }
+    visitSourceCalled = true;
+    super.visitSource(file, debug);
+  }
 
-    @Override
-    public void visitSource(final String file, final String debug) {
-        checkState();
-        if (source) {
-            throw new IllegalStateException(
-                    "visitSource can be called only once.");
-        }
-        source = true;
-        super.visitSource(file, debug);
+  @Override
+  public ModuleVisitor visitModule(final String name, final int access, final String version) {
+    checkState();
+    if (visitModuleCalled) {
+      throw new IllegalStateException("visitModule can be called only once.");
     }
+    visitModuleCalled = true;
+    checkFullyQualifiedName(this.version, name, "module name");
+    checkAccess(access, Opcodes.ACC_OPEN | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_MANDATED);
+    CheckModuleAdapter checkModuleAdapter =
+        new CheckModuleAdapter(
+            api, super.visitModule(name, access, version), (access & Opcodes.ACC_OPEN) != 0);
+    checkModuleAdapter.classVersion = this.version;
+    return checkModuleAdapter;
+  }
 
-    @Override
-    public ModuleVisitor visitModule(String name, int access, String version) {
-        checkState();
-        if (module) {
-            throw new IllegalStateException(
-                    "visitModule can be called only once.");
-        }
-        module = true;
-        if (name == null) {
-            throw new IllegalArgumentException("Illegal module name (null)");
-        }
-        checkAccess(access, Opcodes.ACC_OPEN | Opcodes.ACC_SYNTHETIC);
-        return new CheckModuleAdapter(super.visitModule(name, access, version), 
-            (access & Opcodes.ACC_OPEN) != 0);
-    }
-    
-    @Override
-    public void visitOuterClass(final String owner, final String name,
-            final String desc) {
-        checkState();
-        if (outer) {
-            throw new IllegalStateException(
-                    "visitOuterClass can be called only once.");
-        }
-        outer = true;
-        if (owner == null) {
-            throw new IllegalArgumentException("Illegal outer class owner");
-        }
-        if (desc != null) {
-            CheckMethodAdapter.checkMethodDesc(desc);
-        }
-        super.visitOuterClass(owner, name, desc);
+  @Override
+  public void visitNestHost(final String nestHost) {
+    checkState();
+    CheckMethodAdapter.checkInternalName(version, nestHost, "nestHost");
+    if (visitNestHostCalled) {
+      throw new IllegalStateException("visitNestHost can be called only once.");
     }
+    if (nestMemberPackageName != null) {
+      throw new IllegalStateException("visitNestHost and visitNestMember are mutually exclusive.");
+    }
+    visitNestHostCalled = true;
+    super.visitNestHost(nestHost);
+  }
 
-    @Override
-    public void visitInnerClass(final String name, final String outerName,
-            final String innerName, final int access) {
-        checkState();
-        CheckMethodAdapter.checkInternalName(name, "class name");
-        if (outerName != null) {
-            CheckMethodAdapter.checkInternalName(outerName, "outer class name");
-        }
-        if (innerName != null) {
-            int start = 0;
-            while (start < innerName.length()
-                    && Character.isDigit(innerName.charAt(start))) {
-                start++;
-            }
-            if (start == 0 || start < innerName.length()) {
-                CheckMethodAdapter.checkIdentifier(innerName, start, -1,
-                        "inner class name");
-            }
-        }
-        checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_PRIVATE
-                + Opcodes.ACC_PROTECTED + Opcodes.ACC_STATIC
-                + Opcodes.ACC_FINAL + Opcodes.ACC_INTERFACE
-                + Opcodes.ACC_ABSTRACT + Opcodes.ACC_SYNTHETIC
-                + Opcodes.ACC_ANNOTATION + Opcodes.ACC_ENUM);
-        super.visitInnerClass(name, outerName, innerName, access);
-    }
-
-    @Override
-    public FieldVisitor visitField(final int access, final String name,
-            final String desc, final String signature, final Object value) {
-        checkState();
-        checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_PRIVATE
-                + Opcodes.ACC_PROTECTED + Opcodes.ACC_STATIC
-                + Opcodes.ACC_FINAL + Opcodes.ACC_VOLATILE
-                + Opcodes.ACC_TRANSIENT + Opcodes.ACC_SYNTHETIC
-                + Opcodes.ACC_ENUM + Opcodes.ACC_DEPRECATED + 0x40000); // ClassWriter.ACC_SYNTHETIC_ATTRIBUTE
-        CheckMethodAdapter.checkUnqualifiedName(version, name, "field name");
-        CheckMethodAdapter.checkDesc(desc, false);
-        if (signature != null) {
-            checkFieldSignature(signature);
-        }
-        if (value != null) {
-            CheckMethodAdapter.checkConstant(value);
-        }
-        FieldVisitor av = super
-                .visitField(access, name, desc, signature, value);
-        return new CheckFieldAdapter(av);
-    }
-
-    @Override
-    public MethodVisitor visitMethod(final int access, final String name,
-            final String desc, final String signature, final String[] exceptions) {
-        checkState();
-        checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_PRIVATE
-                + Opcodes.ACC_PROTECTED + Opcodes.ACC_STATIC
-                + Opcodes.ACC_FINAL + Opcodes.ACC_SYNCHRONIZED
-                + Opcodes.ACC_BRIDGE + Opcodes.ACC_VARARGS + Opcodes.ACC_NATIVE
-                + Opcodes.ACC_ABSTRACT + Opcodes.ACC_STRICT
-                + Opcodes.ACC_SYNTHETIC + Opcodes.ACC_DEPRECATED + 0x40000); // ClassWriter.ACC_SYNTHETIC_ATTRIBUTE
-        if (!"<init>".equals(name) && !"<clinit>".equals(name)) {
-            CheckMethodAdapter.checkMethodIdentifier(version, name,
-                    "method name");
-        }
-        CheckMethodAdapter.checkMethodDesc(desc);
-        if (signature != null) {
-            checkMethodSignature(signature);
-        }
-        if (exceptions != null) {
-            for (int i = 0; i < exceptions.length; ++i) {
-                CheckMethodAdapter.checkInternalName(exceptions[i],
-                        "exception name at index " + i);
-            }
-        }
-        CheckMethodAdapter cma;
-        if (checkDataFlow) {
-            cma = new CheckMethodAdapter(access, name, desc, super.visitMethod(
-                    access, name, desc, signature, exceptions), labels);
-        } else {
-            cma = new CheckMethodAdapter(super.visitMethod(access, name, desc,
-                    signature, exceptions), labels);
-        }
-        cma.version = version;
-        return cma;
-    }
-
-    @Override
-    public AnnotationVisitor visitAnnotation(final String desc,
-            final boolean visible) {
-        checkState();
-        CheckMethodAdapter.checkDesc(desc, false);
-        return new CheckAnnotationAdapter(super.visitAnnotation(desc, visible));
-    }
-
-    @Override
-    public AnnotationVisitor visitTypeAnnotation(final int typeRef,
-            final TypePath typePath, final String desc, final boolean visible) {
-        checkState();
-        int sort = typeRef >>> 24;
-        if (sort != TypeReference.CLASS_TYPE_PARAMETER
-                && sort != TypeReference.CLASS_TYPE_PARAMETER_BOUND
-                && sort != TypeReference.CLASS_EXTENDS) {
-            throw new IllegalArgumentException("Invalid type reference sort 0x"
-                    + Integer.toHexString(sort));
-        }
-        checkTypeRefAndPath(typeRef, typePath);
-        CheckMethodAdapter.checkDesc(desc, false);
-        return new CheckAnnotationAdapter(super.visitTypeAnnotation(typeRef,
-                typePath, desc, visible));
-    }
-
-    @Override
-    public void visitAttribute(final Attribute attr) {
-        checkState();
-        if (attr == null) {
-            throw new IllegalArgumentException(
-                    "Invalid attribute (must not be null)");
-        }
-        super.visitAttribute(attr);
+  @Override
+  public void visitNestMember(final String nestMember) {
+    checkState();
+    CheckMethodAdapter.checkInternalName(version, nestMember, "nestMember");
+    if (visitNestHostCalled) {
+      throw new IllegalStateException(
+          "visitMemberOfNest and visitNestHost are mutually exclusive.");
+    }
+    String packageName = packageName(nestMember);
+    if (nestMemberPackageName == null) {
+      nestMemberPackageName = packageName;
+    } else if (!nestMemberPackageName.equals(packageName)) {
+      throw new IllegalStateException(
+          "nest member " + nestMember + " should be in the package " + nestMemberPackageName);
     }
+    super.visitNestMember(nestMember);
+  }
 
-    @Override
-    public void visitEnd() {
-        checkState();
-        end = true;
-        super.visitEnd();
+  @Override
+  public void visitOuterClass(final String owner, final String name, final String descriptor) {
+    checkState();
+    if (visitOuterClassCalled) {
+      throw new IllegalStateException("visitOuterClass can be called only once.");
+    }
+    visitOuterClassCalled = true;
+    if (owner == null) {
+      throw new IllegalArgumentException("Illegal outer class owner");
     }
+    if (descriptor != null) {
+      CheckMethodAdapter.checkMethodDescriptor(version, descriptor);
+    }
+    super.visitOuterClass(owner, name, descriptor);
+  }
 
-    // ------------------------------------------------------------------------
-    // Utility methods
-    // ------------------------------------------------------------------------
+  @Override
+  public void visitInnerClass(
+      final String name, final String outerName, final String innerName, final int access) {
+    checkState();
+    CheckMethodAdapter.checkInternalName(version, name, "class name");
+    if (outerName != null) {
+      CheckMethodAdapter.checkInternalName(version, outerName, "outer class name");
+    }
+    if (innerName != null) {
+      int startIndex = 0;
+      while (startIndex < innerName.length() && Character.isDigit(innerName.charAt(startIndex))) {
+        startIndex++;
+      }
+      if (startIndex == 0 || startIndex < innerName.length()) {
+        CheckMethodAdapter.checkIdentifier(version, innerName, startIndex, -1, "inner class name");
+      }
+    }
+    checkAccess(
+        access,
+        Opcodes.ACC_PUBLIC
+            | Opcodes.ACC_PRIVATE
+            | Opcodes.ACC_PROTECTED
+            | Opcodes.ACC_STATIC
+            | Opcodes.ACC_FINAL
+            | Opcodes.ACC_INTERFACE
+            | Opcodes.ACC_ABSTRACT
+            | Opcodes.ACC_SYNTHETIC
+            | Opcodes.ACC_ANNOTATION
+            | Opcodes.ACC_ENUM);
+    super.visitInnerClass(name, outerName, innerName, access);
+  }
 
-    /**
-     * Checks that the visit method has been called and that visitEnd has not
-     * been called.
-     */
-    private void checkState() {
-        if (!start) {
-            throw new IllegalStateException(
-                    "Cannot visit member before visit has been called.");
-        }
-        if (end) {
-            throw new IllegalStateException(
-                    "Cannot visit member after visitEnd has been called.");
-        }
+  @Override
+  public FieldVisitor visitField(
+      final int access,
+      final String name,
+      final String descriptor,
+      final String signature,
+      final Object value) {
+    checkState();
+    checkAccess(
+        access,
+        Opcodes.ACC_PUBLIC
+            | Opcodes.ACC_PRIVATE
+            | Opcodes.ACC_PROTECTED
+            | Opcodes.ACC_STATIC
+            | Opcodes.ACC_FINAL
+            | Opcodes.ACC_VOLATILE
+            | Opcodes.ACC_TRANSIENT
+            | Opcodes.ACC_SYNTHETIC
+            | Opcodes.ACC_ENUM
+            | Opcodes.ACC_DEPRECATED);
+    CheckMethodAdapter.checkUnqualifiedName(version, name, "field name");
+    CheckMethodAdapter.checkDescriptor(version, descriptor, /* canBeVoid = */ false);
+    if (signature != null) {
+      checkFieldSignature(signature);
     }
+    if (value != null) {
+      CheckMethodAdapter.checkConstant(value);
+    }
+    return new CheckFieldAdapter(api, super.visitField(access, name, descriptor, signature, value));
+  }
 
-    /**
-     * Checks that the given access flags do not contain invalid flags. This
-     * method also checks that mutually incompatible flags are not set
-     * simultaneously.
-     * 
-     * @param access
-     *            the access flags to be checked
-     * @param possibleAccess
-     *            the valid access flags.
-     */
-    static void checkAccess(final int access, final int possibleAccess) {
-        if ((access & ~possibleAccess) != 0) {
-            throw new IllegalArgumentException("Invalid access flags: "
-                    + access);
-        }
-        int pub = (access & Opcodes.ACC_PUBLIC) == 0 ? 0 : 1;
-        int pri = (access & Opcodes.ACC_PRIVATE) == 0 ? 0 : 1;
-        int pro = (access & Opcodes.ACC_PROTECTED) == 0 ? 0 : 1;
-        if (pub + pri + pro > 1) {
-            throw new IllegalArgumentException(
-                    "public private and protected are mutually exclusive: "
-                            + access);
-        }
-        int fin = (access & Opcodes.ACC_FINAL) == 0 ? 0 : 1;
-        int abs = (access & Opcodes.ACC_ABSTRACT) == 0 ? 0 : 1;
-        if (fin + abs > 1) {
-            throw new IllegalArgumentException(
-                    "final and abstract are mutually exclusive: " + access);
-        }
+  @Override
+  public MethodVisitor visitMethod(
+      final int access,
+      final String name,
+      final String descriptor,
+      final String signature,
+      final String[] exceptions) {
+    checkState();
+    checkAccess(
+        access,
+        Opcodes.ACC_PUBLIC
+            | Opcodes.ACC_PRIVATE
+            | Opcodes.ACC_PROTECTED
+            | Opcodes.ACC_STATIC
+            | Opcodes.ACC_FINAL
+            | Opcodes.ACC_SYNCHRONIZED
+            | Opcodes.ACC_BRIDGE
+            | Opcodes.ACC_VARARGS
+            | Opcodes.ACC_NATIVE
+            | Opcodes.ACC_ABSTRACT
+            | Opcodes.ACC_STRICT
+            | Opcodes.ACC_SYNTHETIC
+            | Opcodes.ACC_DEPRECATED);
+    if (!"<init>".equals(name) && !"<clinit>".equals(name)) {
+      CheckMethodAdapter.checkMethodIdentifier(version, name, "method name");
+    }
+    CheckMethodAdapter.checkMethodDescriptor(version, descriptor);
+    if (signature != null) {
+      checkMethodSignature(signature);
+    }
+    if (exceptions != null) {
+      for (int i = 0; i < exceptions.length; ++i) {
+        CheckMethodAdapter.checkInternalName(
+            version, exceptions[i], "exception name at index " + i);
+      }
+    }
+    CheckMethodAdapter checkMethodAdapter;
+    if (checkDataFlow) {
+      checkMethodAdapter =
+          new CheckMethodAdapter(
+              api,
+              access,
+              name,
+              descriptor,
+              super.visitMethod(access, name, descriptor, signature, exceptions),
+              labelInsnIndices);
+    } else {
+      checkMethodAdapter =
+          new CheckMethodAdapter(
+              api,
+              super.visitMethod(access, name, descriptor, signature, exceptions),
+              labelInsnIndices);
     }
+    checkMethodAdapter.version = version;
+    return checkMethodAdapter;
+  }
 
-    /**
-     * Checks a class signature.
-     * 
-     * @param signature
-     *            a string containing the signature that must be checked.
-     */
-    public static void checkClassSignature(final String signature) {
-        // ClassSignature:
-        // FormalTypeParameters? ClassTypeSignature ClassTypeSignature*
+  @Override
+  public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
+    checkState();
+    CheckMethodAdapter.checkDescriptor(version, descriptor, false);
+    return new CheckAnnotationAdapter(super.visitAnnotation(descriptor, visible));
+  }
 
-        int pos = 0;
-        if (getChar(signature, 0) == '<') {
-            pos = checkFormalTypeParameters(signature, pos);
-        }
+  @Override
+  public AnnotationVisitor visitTypeAnnotation(
+      final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+    checkState();
+    int sort = new TypeReference(typeRef).getSort();
+    if (sort != TypeReference.CLASS_TYPE_PARAMETER
+        && sort != TypeReference.CLASS_TYPE_PARAMETER_BOUND
+        && sort != TypeReference.CLASS_EXTENDS) {
+      throw new IllegalArgumentException(
+          "Invalid type reference sort 0x" + Integer.toHexString(sort));
+    }
+    checkTypeRef(typeRef);
+    CheckMethodAdapter.checkDescriptor(version, descriptor, false);
+    return new CheckAnnotationAdapter(
+        super.visitTypeAnnotation(typeRef, typePath, descriptor, visible));
+  }
+
+  @Override
+  public void visitAttribute(final Attribute attribute) {
+    checkState();
+    if (attribute == null) {
+      throw new IllegalArgumentException("Invalid attribute (must not be null)");
+    }
+    super.visitAttribute(attribute);
+  }
+
+  @Override
+  public void visitEnd() {
+    checkState();
+    visitEndCalled = true;
+    super.visitEnd();
+  }
+
+  // -----------------------------------------------------------------------------------------------
+  // Utility methods
+  // -----------------------------------------------------------------------------------------------
+
+  /** Checks that the visit method has been called and that visitEnd has not been called. */
+  private void checkState() {
+    if (!visitCalled) {
+      throw new IllegalStateException("Cannot visit member before visit has been called.");
+    }
+    if (visitEndCalled) {
+      throw new IllegalStateException("Cannot visit member after visitEnd has been called.");
+    }
+  }
+
+  /**
+   * Checks that the given access flags do not contain invalid flags. This method also checks that
+   * mutually incompatible flags are not set simultaneously.
+   *
+   * @param access the access flags to be checked.
+   * @param possibleAccess the valid access flags.
+   */
+  static void checkAccess(final int access, final int possibleAccess) {
+    if ((access & ~possibleAccess) != 0) {
+      throw new IllegalArgumentException("Invalid access flags: " + access);
+    }
+    int publicProtectedPrivate = Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE;
+    if (Integer.bitCount(access & publicProtectedPrivate) > 1) {
+      throw new IllegalArgumentException(
+          "public, protected and private are mutually exclusive: " + access);
+    }
+    if (Integer.bitCount(access & (Opcodes.ACC_FINAL | Opcodes.ACC_ABSTRACT)) > 1) {
+      throw new IllegalArgumentException("final and abstract are mutually exclusive: " + access);
+    }
+  }
+
+  /**
+   * Checks that the given name is a fully qualified name, using dots.
+   *
+   * @param version the class version.
+   * @param name the name to be checked.
+   * @param source the source of 'name' (e.g 'module' for a module name).
+   */
+  static void checkFullyQualifiedName(final int version, final String name, final String source) {
+    try {
+      int startIndex = 0;
+      int dotIndex;
+      while ((dotIndex = name.indexOf('.', startIndex + 1)) != -1) {
+        CheckMethodAdapter.checkIdentifier(version, name, startIndex, dotIndex, null);
+        startIndex = dotIndex + 1;
+      }
+      CheckMethodAdapter.checkIdentifier(version, name, startIndex, name.length(), null);
+    } catch (IllegalArgumentException e) {
+      throw new IllegalArgumentException(
+          "Invalid " + source + " (must be a fully qualified name): " + name, e);
+    }
+  }
+
+  /**
+   * Checks a class signature.
+   *
+   * @param signature a string containing the signature that must be checked.
+   */
+  public static void checkClassSignature(final String signature) {
+    // From https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.9.1:
+    // ClassSignature:
+    //   [TypeParameters] SuperclassSignature SuperinterfaceSignature*
+    // SuperclassSignature:
+    //   ClassTypeSignature
+    // SuperinterfaceSignature:
+    //   ClassTypeSignature
+    int pos = 0;
+    if (getChar(signature, 0) == '<') {
+      pos = checkTypeParameters(signature, pos);
+    }
+    pos = checkClassTypeSignature(signature, pos);
+    while (getChar(signature, pos) == 'L') {
+      pos = checkClassTypeSignature(signature, pos);
+    }
+    if (pos != signature.length()) {
+      throw new IllegalArgumentException(signature + ERROR_AT + pos);
+    }
+  }
+
+  /**
+   * Checks a method signature.
+   *
+   * @param signature a string containing the signature that must be checked.
+   */
+  public static void checkMethodSignature(final String signature) {
+    // From https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.9.1:
+    // MethodSignature:
+    //   [TypeParameters] ( JavaTypeSignature* ) Result ThrowsSignature*
+    // Result:
+    //   JavaTypeSignature
+    //   VoidDescriptor
+    // ThrowsSignature:
+    //   ^ ClassTypeSignature
+    //   ^ TypeVariableSignature
+    int pos = 0;
+    if (getChar(signature, 0) == '<') {
+      pos = checkTypeParameters(signature, pos);
+    }
+    pos = checkChar('(', signature, pos);
+    while ("ZCBSIFJDL[T".indexOf(getChar(signature, pos)) != -1) {
+      pos = checkJavaTypeSignature(signature, pos);
+    }
+    pos = checkChar(')', signature, pos);
+    if (getChar(signature, pos) == 'V') {
+      ++pos;
+    } else {
+      pos = checkJavaTypeSignature(signature, pos);
+    }
+    while (getChar(signature, pos) == '^') {
+      ++pos;
+      if (getChar(signature, pos) == 'L') {
         pos = checkClassTypeSignature(signature, pos);
-        while (getChar(signature, pos) == 'L') {
-            pos = checkClassTypeSignature(signature, pos);
-        }
-        if (pos != signature.length()) {
-            throw new IllegalArgumentException(signature + ": error at index "
-                    + pos);
-        }
+      } else {
+        pos = checkTypeVariableSignature(signature, pos);
+      }
+    }
+    if (pos != signature.length()) {
+      throw new IllegalArgumentException(signature + ERROR_AT + pos);
     }
+  }
 
-    /**
-     * Checks a method signature.
-     * 
-     * @param signature
-     *            a string containing the signature that must be checked.
-     */
-    public static void checkMethodSignature(final String signature) {
-        // MethodTypeSignature:
-        // FormalTypeParameters? ( TypeSignature* ) ( TypeSignature | V ) (
-        // ^ClassTypeSignature | ^TypeVariableSignature )*
-
-        int pos = 0;
-        if (getChar(signature, 0) == '<') {
-            pos = checkFormalTypeParameters(signature, pos);
-        }
-        pos = checkChar('(', signature, pos);
-        while ("ZCBSIFJDL[T".indexOf(getChar(signature, pos)) != -1) {
-            pos = checkTypeSignature(signature, pos);
-        }
-        pos = checkChar(')', signature, pos);
-        if (getChar(signature, pos) == 'V') {
-            ++pos;
-        } else {
-            pos = checkTypeSignature(signature, pos);
-        }
-        while (getChar(signature, pos) == '^') {
-            ++pos;
-            if (getChar(signature, pos) == 'L') {
-                pos = checkClassTypeSignature(signature, pos);
-            } else {
-                pos = checkTypeVariableSignature(signature, pos);
-            }
-        }
-        if (pos != signature.length()) {
-            throw new IllegalArgumentException(signature + ": error at index "
-                    + pos);
-        }
+  /**
+   * Checks a field signature.
+   *
+   * @param signature a string containing the signature that must be checked.
+   */
+  public static void checkFieldSignature(final String signature) {
+    // From https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.9.1:
+    // FieldSignature:
+    //   ReferenceTypeSignature
+    int pos = checkReferenceTypeSignature(signature, 0);
+    if (pos != signature.length()) {
+      throw new IllegalArgumentException(signature + ERROR_AT + pos);
     }
+  }
 
-    /**
-     * Checks a field signature.
-     * 
-     * @param signature
-     *            a string containing the signature that must be checked.
-     */
-    public static void checkFieldSignature(final String signature) {
-        int pos = checkFieldTypeSignature(signature, 0);
-        if (pos != signature.length()) {
-            throw new IllegalArgumentException(signature + ": error at index "
-                    + pos);
-        }
+  /**
+   * Checks the type parameters of a class or method signature.
+   *
+   * @param signature a string containing the signature that must be checked.
+   * @param startPos index of first character to be checked.
+   * @return the index of the first character after the checked part.
+   */
+  private static int checkTypeParameters(final String signature, final int startPos) {
+    // From https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.9.1:
+    // TypeParameters:
+    //   < TypeParameter TypeParameter* >
+    int pos = startPos;
+    pos = checkChar('<', signature, pos);
+    pos = checkTypeParameter(signature, pos);
+    while (getChar(signature, pos) != '>') {
+      pos = checkTypeParameter(signature, pos);
     }
+    return pos + 1;
+  }
 
-    /**
-     * Checks the reference to a type in a type annotation.
-     * 
-     * @param typeRef
-     *            a reference to an annotated type.
-     * @param typePath
-     *            the path to the annotated type argument, wildcard bound, array
-     *            element type, or static inner type within 'typeRef'. May be
-     *            <tt>null</tt> if the annotation targets 'typeRef' as a whole.
-     */
-    static void checkTypeRefAndPath(int typeRef, TypePath typePath) {
-        int mask = 0;
-        switch (typeRef >>> 24) {
-        case TypeReference.CLASS_TYPE_PARAMETER:
-        case TypeReference.METHOD_TYPE_PARAMETER:
-        case TypeReference.METHOD_FORMAL_PARAMETER:
-            mask = 0xFFFF0000;
-            break;
-        case TypeReference.FIELD:
-        case TypeReference.METHOD_RETURN:
-        case TypeReference.METHOD_RECEIVER:
-        case TypeReference.LOCAL_VARIABLE:
-        case TypeReference.RESOURCE_VARIABLE:
-        case TypeReference.INSTANCEOF:
-        case TypeReference.NEW:
-        case TypeReference.CONSTRUCTOR_REFERENCE:
-        case TypeReference.METHOD_REFERENCE:
-            mask = 0xFF000000;
-            break;
-        case TypeReference.CLASS_EXTENDS:
-        case TypeReference.CLASS_TYPE_PARAMETER_BOUND:
-        case TypeReference.METHOD_TYPE_PARAMETER_BOUND:
-        case TypeReference.THROWS:
-        case TypeReference.EXCEPTION_PARAMETER:
-            mask = 0xFFFFFF00;
-            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:
-            mask = 0xFF0000FF;
-            break;
-        default:
-            throw new IllegalArgumentException("Invalid type reference sort 0x"
-                    + Integer.toHexString(typeRef >>> 24));
-        }
-        if ((typeRef & ~mask) != 0) {
-            throw new IllegalArgumentException("Invalid type reference 0x"
-                    + Integer.toHexString(typeRef));
-        }
-        if (typePath != null) {
-            for (int i = 0; i < typePath.getLength(); ++i) {
-                int step = typePath.getStep(i);
-                if (step != TypePath.ARRAY_ELEMENT
-                        && step != TypePath.INNER_TYPE
-                        && step != TypePath.TYPE_ARGUMENT
-                        && step != TypePath.WILDCARD_BOUND) {
-                    throw new IllegalArgumentException(
-                            "Invalid type path step " + i + " in " + typePath);
-                }
-                if (step != TypePath.TYPE_ARGUMENT
-                        && typePath.getStepArgument(i) != 0) {
-                    throw new IllegalArgumentException(
-                            "Invalid type path step argument for step " + i
-                                    + " in " + typePath);
-                }
-            }
-        }
+  /**
+   * Checks a type parameter of a class or method signature.
+   *
+   * @param signature a string containing the signature that must be checked.
+   * @param startPos index of first character to be checked.
+   * @return the index of the first character after the checked part.
+   */
+  private static int checkTypeParameter(final String signature, final int startPos) {
+    // From https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.9.1:
+    // TypeParameter:
+    //   Identifier ClassBound InterfaceBound*
+    // ClassBound:
+    //   : [ReferenceTypeSignature]
+    // InterfaceBound:
+    //   : ReferenceTypeSignature
+    int pos = startPos;
+    pos = checkSignatureIdentifier(signature, pos);
+    pos = checkChar(':', signature, pos);
+    if ("L[T".indexOf(getChar(signature, pos)) != -1) {
+      pos = checkReferenceTypeSignature(signature, pos);
+    }
+    while (getChar(signature, pos) == ':') {
+      pos = checkReferenceTypeSignature(signature, pos + 1);
     }
+    return pos;
+  }
 
-    /**
-     * Checks the formal type parameters of a class or method signature.
-     * 
-     * @param signature
-     *            a string containing the signature that must be checked.
-     * @param pos
-     *            index of first character to be checked.
-     * @return the index of the first character after the checked part.
-     */
-    private static int checkFormalTypeParameters(final String signature, int pos) {
-        // FormalTypeParameters:
-        // < FormalTypeParameter+ >
-
-        pos = checkChar('<', signature, pos);
-        pos = checkFormalTypeParameter(signature, pos);
-        while (getChar(signature, pos) != '>') {
-            pos = checkFormalTypeParameter(signature, pos);
-        }
-        return pos + 1;
+  /**
+   * Checks a reference type signature.
+   *
+   * @param signature a string containing the signature that must be checked.
+   * @param pos index of first character to be checked.
+   * @return the index of the first character after the checked part.
+   */
+  private static int checkReferenceTypeSignature(final String signature, final int pos) {
+    // From https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.9.1:
+    // ReferenceTypeSignature:
+    //   ClassTypeSignature
+    //   TypeVariableSignature
+    //   ArrayTypeSignature
+    // ArrayTypeSignature:
+    //   [ JavaTypeSignature
+    switch (getChar(signature, pos)) {
+      case 'L':
+        return checkClassTypeSignature(signature, pos);
+      case '[':
+        return checkJavaTypeSignature(signature, pos + 1);
+      default:
+        return checkTypeVariableSignature(signature, pos);
     }
+  }
 
-    /**
-     * Checks a formal type parameter of a class or method signature.
-     * 
-     * @param signature
-     *            a string containing the signature that must be checked.
-     * @param pos
-     *            index of first character to be checked.
-     * @return the index of the first character after the checked part.
-     */
-    private static int checkFormalTypeParameter(final String signature, int pos) {
-        // FormalTypeParameter:
-        // Identifier : FieldTypeSignature? (: FieldTypeSignature)*
-
-        pos = checkIdentifier(signature, pos);
-        pos = checkChar(':', signature, pos);
-        if ("L[T".indexOf(getChar(signature, pos)) != -1) {
-            pos = checkFieldTypeSignature(signature, pos);
-        }
-        while (getChar(signature, pos) == ':') {
-            pos = checkFieldTypeSignature(signature, pos + 1);
-        }
-        return pos;
-    }
-
-    /**
-     * Checks a field type signature.
-     * 
-     * @param signature
-     *            a string containing the signature that must be checked.
-     * @param pos
-     *            index of first character to be checked.
-     * @return the index of the first character after the checked part.
-     */
-    private static int checkFieldTypeSignature(final String signature, int pos) {
-        // FieldTypeSignature:
-        // ClassTypeSignature | ArrayTypeSignature | TypeVariableSignature
-        //
-        // ArrayTypeSignature:
-        // [ TypeSignature
-
-        switch (getChar(signature, pos)) {
-        case 'L':
-            return checkClassTypeSignature(signature, pos);
-        case '[':
-            return checkTypeSignature(signature, pos + 1);
-        default:
-            return checkTypeVariableSignature(signature, pos);
-        }
+  /**
+   * Checks a class type signature.
+   *
+   * @param signature a string containing the signature that must be checked.
+   * @param startPos index of first character to be checked.
+   * @return the index of the first character after the checked part.
+   */
+  private static int checkClassTypeSignature(final String signature, final int startPos) {
+    // From https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.9.1:
+    // ClassTypeSignature:
+    //   L [PackageSpecifier] SimpleClassTypeSignature ClassTypeSignatureSuffix* ;
+    // PackageSpecifier:
+    //   Identifier / PackageSpecifier*
+    // SimpleClassTypeSignature:
+    //   Identifier [TypeArguments]
+    // ClassTypeSignatureSuffix:
+    //   . SimpleClassTypeSignature
+    int pos = startPos;
+    pos = checkChar('L', signature, pos);
+    pos = checkSignatureIdentifier(signature, pos);
+    while (getChar(signature, pos) == '/') {
+      pos = checkSignatureIdentifier(signature, pos + 1);
     }
+    if (getChar(signature, pos) == '<') {
+      pos = checkTypeArguments(signature, pos);
+    }
+    while (getChar(signature, pos) == '.') {
+      pos = checkSignatureIdentifier(signature, pos + 1);
+      if (getChar(signature, pos) == '<') {
+        pos = checkTypeArguments(signature, pos);
+      }
+    }
+    return checkChar(';', signature, pos);
+  }
 
-    /**
-     * Checks a class type signature.
-     * 
-     * @param signature
-     *            a string containing the signature that must be checked.
-     * @param pos
-     *            index of first character to be checked.
-     * @return the index of the first character after the checked part.
-     */
-    private static int checkClassTypeSignature(final String signature, int pos) {
-        // ClassTypeSignature:
-        // L Identifier ( / Identifier )* TypeArguments? ( . Identifier
-        // TypeArguments? )* ;
-
-        pos = checkChar('L', signature, pos);
-        pos = checkIdentifier(signature, pos);
-        while (getChar(signature, pos) == '/') {
-            pos = checkIdentifier(signature, pos + 1);
-        }
-        if (getChar(signature, pos) == '<') {
-            pos = checkTypeArguments(signature, pos);
-        }
-        while (getChar(signature, pos) == '.') {
-            pos = checkIdentifier(signature, pos + 1);
-            if (getChar(signature, pos) == '<') {
-                pos = checkTypeArguments(signature, pos);
-            }
-        }
-        return checkChar(';', signature, pos);
-    }
-
-    /**
-     * Checks the type arguments in a class type signature.
-     * 
-     * @param signature
-     *            a string containing the signature that must be checked.
-     * @param pos
-     *            index of first character to be checked.
-     * @return the index of the first character after the checked part.
-     */
-    private static int checkTypeArguments(final String signature, int pos) {
-        // TypeArguments:
-        // < TypeArgument+ >
-
-        pos = checkChar('<', signature, pos);
-        pos = checkTypeArgument(signature, pos);
-        while (getChar(signature, pos) != '>') {
-            pos = checkTypeArgument(signature, pos);
-        }
+  /**
+   * Checks the type arguments in a class type signature.
+   *
+   * @param signature a string containing the signature that must be checked.
+   * @param startPos index of first character to be checked.
+   * @return the index of the first character after the checked part.
+   */
+  private static int checkTypeArguments(final String signature, final int startPos) {
+    // From https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.9.1:
+    // TypeArguments:
+    //   < TypeArgument TypeArgument* >
+    int pos = startPos;
+    pos = checkChar('<', signature, pos);
+    pos = checkTypeArgument(signature, pos);
+    while (getChar(signature, pos) != '>') {
+      pos = checkTypeArgument(signature, pos);
+    }
+    return pos + 1;
+  }
+
+  /**
+   * Checks a type argument in a class type signature.
+   *
+   * @param signature a string containing the signature that must be checked.
+   * @param startPos index of first character to be checked.
+   * @return the index of the first character after the checked part.
+   */
+  private static int checkTypeArgument(final String signature, final int startPos) {
+    // From https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.9.1:
+    // TypeArgument:
+    //   [WildcardIndicator] ReferenceTypeSignature
+    //   *
+    // WildcardIndicator:
+    //   +
+    //   -
+    int pos = startPos;
+    char c = getChar(signature, pos);
+    if (c == '*') {
+      return pos + 1;
+    } else if (c == '+' || c == '-') {
+      pos++;
+    }
+    return checkReferenceTypeSignature(signature, pos);
+  }
+
+  /**
+   * Checks a type variable signature.
+   *
+   * @param signature a string containing the signature that must be checked.
+   * @param startPos index of first character to be checked.
+   * @return the index of the first character after the checked part.
+   */
+  private static int checkTypeVariableSignature(final String signature, final int startPos) {
+    // From https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.9.1:
+    // TypeVariableSignature:
+    //  T Identifier ;
+    int pos = startPos;
+    pos = checkChar('T', signature, pos);
+    pos = checkSignatureIdentifier(signature, pos);
+    return checkChar(';', signature, pos);
+  }
+
+  /**
+   * Checks a Java type signature.
+   *
+   * @param signature a string containing the signature that must be checked.
+   * @param startPos index of first character to be checked.
+   * @return the index of the first character after the checked part.
+   */
+  private static int checkJavaTypeSignature(final String signature, final int startPos) {
+    // From https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.9.1:
+    // JavaTypeSignature:
+    //   ReferenceTypeSignature
+    //   BaseType
+    // BaseType:
+    //   (one of)
+    //   B C D F I J S Z
+    int pos = startPos;
+    switch (getChar(signature, pos)) {
+      case 'B':
+      case 'C':
+      case 'D':
+      case 'F':
+      case 'I':
+      case 'J':
+      case 'S':
+      case 'Z':
         return pos + 1;
+      default:
+        return checkReferenceTypeSignature(signature, pos);
     }
+  }
 
-    /**
-     * Checks a type argument in a class type signature.
-     * 
-     * @param signature
-     *            a string containing the signature that must be checked.
-     * @param pos
-     *            index of first character to be checked.
-     * @return the index of the first character after the checked part.
-     */
-    private static int checkTypeArgument(final String signature, int pos) {
-        // TypeArgument:
-        // * | ( ( + | - )? FieldTypeSignature )
-
-        char c = getChar(signature, pos);
-        if (c == '*') {
-            return pos + 1;
-        } else if (c == '+' || c == '-') {
-            pos++;
-        }
-        return checkFieldTypeSignature(signature, pos);
-    }
-
-    /**
-     * Checks a type variable signature.
-     * 
-     * @param signature
-     *            a string containing the signature that must be checked.
-     * @param pos
-     *            index of first character to be checked.
-     * @return the index of the first character after the checked part.
-     */
-    private static int checkTypeVariableSignature(final String signature,
-            int pos) {
-        // TypeVariableSignature:
-        // T Identifier ;
-
-        pos = checkChar('T', signature, pos);
-        pos = checkIdentifier(signature, pos);
-        return checkChar(';', signature, pos);
-    }
-
-    /**
-     * Checks a type signature.
-     * 
-     * @param signature
-     *            a string containing the signature that must be checked.
-     * @param pos
-     *            index of first character to be checked.
-     * @return the index of the first character after the checked part.
-     */
-    private static int checkTypeSignature(final String signature, int pos) {
-        // TypeSignature:
-        // Z | C | B | S | I | F | J | D | FieldTypeSignature
-
-        switch (getChar(signature, pos)) {
-        case 'Z':
-        case 'C':
-        case 'B':
-        case 'S':
-        case 'I':
-        case 'F':
-        case 'J':
-        case 'D':
-            return pos + 1;
-        default:
-            return checkFieldTypeSignature(signature, pos);
-        }
+  /**
+   * Checks an identifier.
+   *
+   * @param signature a string containing the signature that must be checked.
+   * @param startPos index of first character to be checked.
+   * @return the index of the first character after the checked part.
+   */
+  private static int checkSignatureIdentifier(final String signature, final int startPos) {
+    int pos = startPos;
+    while (pos < signature.length() && ".;[/<>:".indexOf(signature.codePointAt(pos)) == -1) {
+      pos = signature.offsetByCodePoints(pos, 1);
+    }
+    if (pos == startPos) {
+      throw new IllegalArgumentException(signature + ": identifier expected at index " + startPos);
     }
+    return pos;
+  }
 
-    /**
-     * Checks an identifier.
-     * 
-     * @param signature
-     *            a string containing the signature that must be checked.
-     * @param pos
-     *            index of first character to be checked.
-     * @return the index of the first character after the checked part.
-     */
-    private static int checkIdentifier(final String signature, int pos) {
-        if (!Character.isJavaIdentifierStart(getChar(signature, pos))) {
-            throw new IllegalArgumentException(signature
-                    + ": identifier expected at index " + pos);
-        }
-        ++pos;
-        while (Character.isJavaIdentifierPart(getChar(signature, pos))) {
-            ++pos;
+  /**
+   * Checks a single character.
+   *
+   * @param c a character.
+   * @param signature a string containing the signature that must be checked.
+   * @param pos index of first character to be checked.
+   * @return the index of the first character after the checked part.
+   */
+  private static int checkChar(final char c, final String signature, final int pos) {
+    if (getChar(signature, pos) == c) {
+      return pos + 1;
+    }
+    throw new IllegalArgumentException(signature + ": '" + c + "' expected at index " + pos);
+  }
+
+  /**
+   * Returns the string character at the given index, or 0.
+   *
+   * @param string a string.
+   * @param pos an index in 'string'.
+   * @return the character at the given index, or 0 if there is no such character.
+   */
+  private static char getChar(final String string, final int pos) {
+    return pos < string.length() ? string.charAt(pos) : (char) 0;
+  }
+
+  /**
+   * Checks the reference to a type in a type annotation.
+   *
+   * @param typeRef a reference to an annotated type.
+   */
+  static void checkTypeRef(final int typeRef) {
+    int mask = 0;
+    switch (typeRef >>> 24) {
+      case TypeReference.CLASS_TYPE_PARAMETER:
+      case TypeReference.METHOD_TYPE_PARAMETER:
+      case TypeReference.METHOD_FORMAL_PARAMETER:
+        mask = 0xFFFF0000;
+        break;
+      case TypeReference.FIELD:
+      case TypeReference.METHOD_RETURN:
+      case TypeReference.METHOD_RECEIVER:
+      case TypeReference.LOCAL_VARIABLE:
+      case TypeReference.RESOURCE_VARIABLE:
+      case TypeReference.INSTANCEOF:
+      case TypeReference.NEW:
+      case TypeReference.CONSTRUCTOR_REFERENCE:
+      case TypeReference.METHOD_REFERENCE:
+        mask = 0xFF000000;
+        break;
+      case TypeReference.CLASS_EXTENDS:
+      case TypeReference.CLASS_TYPE_PARAMETER_BOUND:
+      case TypeReference.METHOD_TYPE_PARAMETER_BOUND:
+      case TypeReference.THROWS:
+      case TypeReference.EXCEPTION_PARAMETER:
+        mask = 0xFFFFFF00;
+        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:
+        mask = 0xFF0000FF;
+        break;
+      default:
+        throw new AssertionError();
+    }
+    if ((typeRef & ~mask) != 0) {
+      throw new IllegalArgumentException(
+          "Invalid type reference 0x" + Integer.toHexString(typeRef));
+    }
+  }
+
+  /**
+   * Returns the package name of an internal name.
+   *
+   * @param name an internal name.
+   * @return the package name or "" if there is no package.
+   */
+  private static String packageName(final String name) {
+    int index = name.lastIndexOf('/');
+    if (index == -1) {
+      return "";
+    }
+    return name.substring(0, index);
+  }
+
+  // -----------------------------------------------------------------------------------------------
+  // Static verification methods
+  // -----------------------------------------------------------------------------------------------
+
+  /**
+   * Checks the given class.
+   *
+   * <p>Usage: CheckClassAdapter &lt;binary class name or class file name&gt;
+   *
+   * @param args the command line arguments.
+   * @throws IOException if the class cannot be found, or if an IO exception occurs.
+   */
+  public static void main(final String[] args) throws IOException {
+    if (args.length != 1) {
+      System.err.println(
+          "Verifies the given class.\n"
+              + "Usage: CheckClassAdapter <fully qualified class name or class file name>");
+      return;
+    }
+
+    ClassReader classReader;
+    if (args[0].endsWith(".class")) {
+      InputStream inputStream =
+          new FileInputStream(args[0]); // NOPMD(AvoidFileStream): can't fix for 1.5 compatibility
+      classReader = new ClassReader(inputStream);
+    } else {
+      classReader = new ClassReader(args[0]);
+    }
+
+    verify(classReader, false, new PrintWriter(System.err));
+  }
+
+  /**
+   * Checks the given class.
+   *
+   * @param classReader the class to be checked.
+   * @param printResults whether to print the results of the bytecode verification.
+   * @param printWriter where the results (or the stack trace in case of error) must be printed.
+   */
+  public static void verify(
+      final ClassReader classReader, final boolean printResults, final PrintWriter printWriter) {
+    verify(classReader, null, printResults, printWriter);
+  }
+
+  /**
+   * Checks the given class.
+   *
+   * @param classReader the class to be checked.
+   * @param loader a <code>ClassLoader</code> which will be used to load referenced classes. May be
+   *     {@literal null}.
+   * @param printResults whether to print the results of the bytecode verification.
+   * @param printWriter where the results (or the stack trace in case of error) must be printed.
+   */
+  public static void verify(
+      final ClassReader classReader,
+      final ClassLoader loader,
+      final boolean printResults,
+      final PrintWriter printWriter) {
+    ClassNode classNode = new ClassNode();
+    classReader.accept(
+        new CheckClassAdapter(Opcodes.ASM7, classNode, false) {}, ClassReader.SKIP_DEBUG);
+
+    Type syperType = classNode.superName == null ? null : Type.getObjectType(classNode.superName);
+    List<MethodNode> methods = classNode.methods;
+
+    List<Type> interfaces = new ArrayList<Type>();
+    for (String interfaceName : classNode.interfaces) {
+      interfaces.add(Type.getObjectType(interfaceName));
+    }
+
+    for (MethodNode method : methods) {
+      SimpleVerifier verifier =
+          new SimpleVerifier(
+              Type.getObjectType(classNode.name),
+              syperType,
+              interfaces,
+              (classNode.access & Opcodes.ACC_INTERFACE) != 0);
+      Analyzer<BasicValue> analyzer = new Analyzer<BasicValue>(verifier);
+      if (loader != null) {
+        verifier.setClassLoader(loader);
+      }
+      try {
+        analyzer.analyze(classNode.name, method);
+      } catch (AnalyzerException e) {
+        e.printStackTrace(printWriter);
+      }
+      if (printResults) {
+        printAnalyzerResult(method, analyzer, printWriter);
+      }
+    }
+    printWriter.flush();
+  }
+
+  static void printAnalyzerResult(
+      final MethodNode method, final Analyzer<BasicValue> analyzer, final PrintWriter printWriter) {
+    Textifier textifier = new Textifier();
+    TraceMethodVisitor traceMethodVisitor = new TraceMethodVisitor(textifier);
+
+    printWriter.println(method.name + method.desc);
+    for (int i = 0; i < method.instructions.size(); ++i) {
+      method.instructions.get(i).accept(traceMethodVisitor);
+
+      StringBuilder stringBuilder = new StringBuilder();
+      Frame<BasicValue> frame = analyzer.getFrames()[i];
+      if (frame == null) {
+        stringBuilder.append('?');
+      } else {
+        for (int j = 0; j < frame.getLocals(); ++j) {
+          stringBuilder.append(getUnqualifiedName(frame.getLocal(j).toString())).append(' ');
         }
-        return pos;
-    }
-
-    /**
-     * Checks a single character.
-     * 
-     * @param signature
-     *            a string containing the signature that must be checked.
-     * @param pos
-     *            index of first character to be checked.
-     * @return the index of the first character after the checked part.
-     */
-    private static int checkChar(final char c, final String signature, int pos) {
-        if (getChar(signature, pos) == c) {
-            return pos + 1;
+        stringBuilder.append(" : ");
+        for (int j = 0; j < frame.getStackSize(); ++j) {
+          stringBuilder.append(getUnqualifiedName(frame.getStack(j).toString())).append(' ');
         }
-        throw new IllegalArgumentException(signature + ": '" + c
-                + "' expected at index " + pos);
-    }
-
-    /**
-     * Returns the signature car at the given index.
-     * 
-     * @param signature
-     *            a signature.
-     * @param pos
-     *            an index in signature.
-     * @return the character at the given index, or 0 if there is no such
-     *         character.
-     */
-    private static char getChar(final String signature, int pos) {
-        return pos < signature.length() ? signature.charAt(pos) : (char) 0;
+      }
+      while (stringBuilder.length() < method.maxStack + method.maxLocals + 1) {
+        stringBuilder.append(' ');
+      }
+      printWriter.print(Integer.toString(i + 100000).substring(1));
+      printWriter.print(
+          " " + stringBuilder + " : " + textifier.text.get(textifier.text.size() - 1));
+    }
+    for (TryCatchBlockNode tryCatchBlock : method.tryCatchBlocks) {
+      tryCatchBlock.accept(traceMethodVisitor);
+      printWriter.print(" " + textifier.text.get(textifier.text.size() - 1));
+    }
+    printWriter.println();
+  }
+
+  private static String getUnqualifiedName(final String name) {
+    int lastSlashIndex = name.lastIndexOf('/');
+    if (lastSlashIndex == -1) {
+      return name;
+    } else {
+      int endIndex = name.length();
+      if (name.charAt(endIndex - 1) == ';') {
+        endIndex--;
+      }
+      return name.substring(lastSlashIndex + 1, endIndex);
     }
+  }
 }