/***
 * ASM: a very small and fast Java bytecode manipulation framework
 * Copyright (c) 2000,2002,2003 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.eclipse.persistence.internal.libraries.asm.util;

import org.eclipse.persistence.internal.libraries.asm.Label;
import org.eclipse.persistence.internal.libraries.asm.CodeAdapter;
import org.eclipse.persistence.internal.libraries.asm.CodeVisitor;
import org.eclipse.persistence.internal.libraries.asm.Constants;
import org.eclipse.persistence.internal.libraries.asm.Attribute;
import org.eclipse.persistence.internal.libraries.asm.Type;

import java.util.HashMap;

/**
 * A {@link CodeAdapter CodeAdapter} that checks that its methods are properly
 * used. More precisely this code adapter checks each instruction individually
 * (i.e., each visit method checks some preconditions based <i>only</i> on its
 * arguments - such as the fact that the given opcode is correct for a given
 * visit method), but does <i>not</i> check the <i>sequence</i> of instructions.
 * For example, in a method whose signature is <tt>void m ()</tt>, the invalid
 * instruction IRETURN, or the invalid sequence IADD L2I will <i>not</i> be
 * detected by this code adapter.
 * 
 * @author Eric Bruneton
 */

public class CheckCodeAdapter extends CodeAdapter {

  /**
   * <tt>true</tt> if the visitMaxs method has been called.
   */

  private boolean end;

  /**
   * The already visited labels. This map associate Integer values to Label
   * keys.
   */

  private HashMap labels;

  /**
   * Code of the visit method to be used for each opcode.
   */

  private final static int[] TYPE = new int[] {
    0,  //NOP
    0,  //ACONST_NULL
    0,  //ICONST_M1
    0,  //ICONST_0
    0,  //ICONST_1
    0,  //ICONST_2
    0,  //ICONST_3
    0,  //ICONST_4
    0,  //ICONST_5
    0,  //LCONST_0
    0,  //LCONST_1
    0,  //FCONST_0
    0,  //FCONST_1
    0,  //FCONST_2
    0,  //DCONST_0
    0,  //DCONST_1
    1,  //BIPUSH
    1,  //SIPUSH
    7,  //LDC
    -1, //LDC_W
    -1, //LDC2_W
    2,  //ILOAD
    2,  //LLOAD
    2,  //FLOAD
    2,  //DLOAD
    2,  //ALOAD
    -1, //ILOAD_0
    -1, //ILOAD_1
    -1, //ILOAD_2
    -1, //ILOAD_3
    -1, //LLOAD_0
    -1, //LLOAD_1
    -1, //LLOAD_2
    -1, //LLOAD_3
    -1, //FLOAD_0
    -1, //FLOAD_1
    -1, //FLOAD_2
    -1, //FLOAD_3
    -1, //DLOAD_0
    -1, //DLOAD_1
    -1, //DLOAD_2
    -1, //DLOAD_3
    -1, //ALOAD_0
    -1, //ALOAD_1
    -1, //ALOAD_2
    -1, //ALOAD_3
    0,  //IALOAD
    0,  //LALOAD
    0,  //FALOAD
    0,  //DALOAD
    0,  //AALOAD
    0,  //BALOAD
    0,  //CALOAD
    0,  //SALOAD
    2,  //ISTORE
    2,  //LSTORE
    2,  //FSTORE
    2,  //DSTORE
    2,  //ASTORE
    -1, //ISTORE_0
    -1, //ISTORE_1
    -1, //ISTORE_2
    -1, //ISTORE_3
    -1, //LSTORE_0
    -1, //LSTORE_1
    -1, //LSTORE_2
    -1, //LSTORE_3
    -1, //FSTORE_0
    -1, //FSTORE_1
    -1, //FSTORE_2
    -1, //FSTORE_3
    -1, //DSTORE_0
    -1, //DSTORE_1
    -1, //DSTORE_2
    -1, //DSTORE_3
    -1, //ASTORE_0
    -1, //ASTORE_1
    -1, //ASTORE_2
    -1, //ASTORE_3
    0,  //IASTORE
    0,  //LASTORE
    0,  //FASTORE
    0,  //DASTORE
    0,  //AASTORE
    0,  //BASTORE
    0,  //CASTORE
    0,  //SASTORE
    0,  //POP
    0,  //POP2
    0,  //DUP
    0,  //DUP_X1
    0,  //DUP_X2
    0,  //DUP2
    0,  //DUP2_X1
    0,  //DUP2_X2
    0,  //SWAP
    0,  //IADD
    0,  //LADD
    0,  //FADD
    0,  //DADD
    0,  //ISUB
    0,  //LSUB
    0,  //FSUB
    0,  //DSUB
    0,  //IMUL
    0,  //LMUL
    0,  //FMUL
    0,  //DMUL
    0,  //IDIV
    0,  //LDIV
    0,  //FDIV
    0,  //DDIV
    0,  //IREM
    0,  //LREM
    0,  //FREM
    0,  //DREM
    0,  //INEG
    0,  //LNEG
    0,  //FNEG
    0,  //DNEG
    0,  //ISHL
    0,  //LSHL
    0,  //ISHR
    0,  //LSHR
    0,  //IUSHR
    0,  //LUSHR
    0,  //IAND
    0,  //LAND
    0,  //IOR
    0,  //LOR
    0,  //IXOR
    0,  //LXOR
    8,  //IINC
    0,  //I2L
    0,  //I2F
    0,  //I2D
    0,  //L2I
    0,  //L2F
    0,  //L2D
    0,  //F2I
    0,  //F2L
    0,  //F2D
    0,  //D2I
    0,  //D2L
    0,  //D2F
    0,  //I2B
    0,  //I2C
    0,  //I2S
    0,  //LCMP
    0,  //FCMPL
    0,  //FCMPG
    0,  //DCMPL
    0,  //DCMPG
    6,  //IFEQ
    6,  //IFNE
    6,  //IFLT
    6,  //IFGE
    6,  //IFGT
    6,  //IFLE
    6,  //IF_ICMPEQ
    6,  //IF_ICMPNE
    6,  //IF_ICMPLT
    6,  //IF_ICMPGE
    6,  //IF_ICMPGT
    6,  //IF_ICMPLE
    6,  //IF_ACMPEQ
    6,  //IF_ACMPNE
    6,  //GOTO
    6,  //JSR
    2,  //RET
    9,  //TABLESWITCH
    10, //LOOKUPSWITCH
    0,  //IRETURN
    0,  //LRETURN
    0,  //FRETURN
    0,  //DRETURN
    0,  //ARETURN
    0,  //RETURN
    4,  //GETSTATIC
    4,  //PUTSTATIC
    4,  //GETFIELD
    4,  //PUTFIELD
    5,  //INVOKEVIRTUAL
    5,  //INVOKESPECIAL
    5,  //INVOKESTATIC
    5,  //INVOKEINTERFACE
    -1, //UNUSED
    3,  //NEW
    1,  //NEWARRAY
    3,  //ANEWARRAY
    0,  //ARRAYLENGTH
    0,  //ATHROW
    3,  //CHECKCAST
    3,  //INSTANCEOF
    0,  //MONITORENTER
    0,  //MONITOREXIT
    -1, //WIDE
    11, //MULTIANEWARRAY
    6,  //IFNULL
    6,  //IFNONNULL
    -1, //GOTO_W
    -1  //JSR_W
  };

  /**
   * Constructs a new {@link CheckCodeAdapter CheckCodeAdapter} object.
   *
   * @param cv the code visitor to which this adapter must delegate calls.
   */

  public CheckCodeAdapter (final CodeVisitor cv) {
    super(cv);
    this.labels = new HashMap();
  }

  public void visitInsn (final int opcode) {
    checkEnd();
    checkOpcode(opcode, 0);
    cv.visitInsn(opcode);
  }

  public void visitIntInsn (final int opcode, final int operand) {
    checkEnd();
    checkOpcode(opcode, 1);
    switch (opcode) {
      case Constants.BIPUSH:
        checkSignedByte(operand, "Invalid operand");
        break;
      case Constants.SIPUSH:
        checkSignedShort(operand, "Invalid operand");
        break;
      //case Constants.NEWARRAY:
      default:
        if (operand < Constants.T_BOOLEAN || operand > Constants.T_LONG) {
          throw new IllegalArgumentException(
            "Invalid operand (must be an array type code T_...): " + operand);
        }
    }
    cv.visitIntInsn(opcode, operand);
  }

  public void visitVarInsn (final int opcode, final int var) {
    checkEnd();
    checkOpcode(opcode, 2);
    checkUnsignedShort(var, "Invalid variable index");
    cv.visitVarInsn(opcode, var);
  }

  public void visitTypeInsn (final int opcode, final String desc) {
    checkEnd();
    checkOpcode(opcode, 3);
    if (desc != null && desc.length() > 0 && desc.charAt(0) == '[') {
      checkDesc(desc, false);
    } else {
      checkInternalName(desc, "type");
    }
    if (opcode == Constants.NEW && desc.charAt(0) == '[') {
      throw new IllegalArgumentException(
        "NEW cannot be used to create arrays: " + desc);
    }
    cv.visitTypeInsn(opcode, desc);
  }

  public void visitFieldInsn (
    final int opcode,
    final String owner,
    final String name,
    final String desc)
  {
    checkEnd();
    checkOpcode(opcode, 4);
    checkInternalName(owner, "owner");
    checkIdentifier(name, "name");
    checkDesc(desc, false);
    cv.visitFieldInsn(opcode, owner, name, desc);
  }

  public void visitMethodInsn (
    final int opcode,
    final String owner,
    final String name,
    final String desc)
  {
    checkEnd();
    checkOpcode(opcode, 5);
    checkInternalName(owner, "owner");
    checkMethodIdentifier(name, "name");
    checkMethodDesc(desc);
    cv.visitMethodInsn(opcode, owner, name, desc);
  }

  public void visitJumpInsn (final int opcode, final Label label) {
    checkEnd();
    checkOpcode(opcode, 6);
    checkLabel(label, false, "label");
    cv.visitJumpInsn(opcode, label);
  }

  public void visitLabel (final Label label) {
    checkEnd();
    checkLabel(label, false, "label");
    if (labels.get(label) != null) {
      throw new IllegalArgumentException("Already visited label");
    } else {
      labels.put(label, new Integer(labels.size()));
    }
    cv.visitLabel(label);
  }

  public void visitLdcInsn (final Object cst) {
    checkEnd();
    if (!(cst instanceof Type)) {
      checkConstant(cst);
    }
    cv.visitLdcInsn(cst);
  }

  public void visitIincInsn (final int var, final int increment) {
    checkEnd();
    checkUnsignedShort(var, "Invalid variable index");
    checkSignedShort(increment, "Invalid increment");
    cv.visitIincInsn(var, increment);
  }

  public void visitTableSwitchInsn (
    final int min,
    final int max,
    final Label dflt,
    final Label labels[])
  {
    checkEnd();
    if (max < min) {
      throw new IllegalArgumentException(
        "Max = " + max + " must be greater than or equal to min = " + min);
    }
    checkLabel(dflt, false, "default label");
    if (labels == null || labels.length != max - min + 1) {
      throw new IllegalArgumentException(
        "There must be max - min + 1 labels");
    }
    for (int i = 0; i < labels.length; ++i) {
      checkLabel(labels[i], false, "label at index " + i);
    }
    cv.visitTableSwitchInsn(min, max, dflt, labels);
  }

  public void visitLookupSwitchInsn (
    final Label dflt,
    final int keys[],
    final Label labels[])
  {
    checkEnd();
    checkLabel(dflt, false, "default label");
    if (keys == null || labels == null || keys.length != labels.length) {
      throw new IllegalArgumentException(
        "There must be the same number of keys and labels");
    }
    for (int i = 0; i < labels.length; ++i) {
      checkLabel(labels[i], false, "label at index " + i);
    }
    cv.visitLookupSwitchInsn(dflt, keys, labels);
  }

  public void visitMultiANewArrayInsn (final String desc, final int dims) {
    checkEnd();
    checkDesc(desc, false);
    if (desc.charAt(0) != '[') {
      throw new IllegalArgumentException(
        "Invalid descriptor (must be an array type descriptor): " + desc);
    }
    if (dims < 1) {
      throw new IllegalArgumentException(
        "Invalid dimensions (must be greater than 0): " + dims);
    }
    if (dims > desc.lastIndexOf('[') + 1) {
      throw new IllegalArgumentException(
        "Invalid dimensions (must not be greater than dims(desc)): " + dims);
    }
    cv.visitMultiANewArrayInsn(desc, dims);
  }

  public void visitTryCatchBlock (
    final Label start,
    final Label end,
    final Label handler,
    final String type)
  {
    checkLabel(start, true, "start label");
    checkLabel(end, true, "end label");
    checkLabel(handler, true, "handler label");
    if (type != null) {
      checkInternalName(type, "type");
    }
    int s = ((Integer)labels.get(start)).intValue();
    int e = ((Integer)labels.get(end)).intValue();
    if (e <= s) {
      throw new IllegalArgumentException(
        "Invalid start and end labels (end must be greater than start)");
    }
    cv.visitTryCatchBlock(start, end, handler, type);
  }

  public void visitMaxs (final int maxStack, final int maxLocals) {
    checkEnd();
    end = true;
    checkUnsignedShort(maxStack, "Invalid max stack");
    checkUnsignedShort(maxLocals, "Invalid max locals");
    cv.visitMaxs(maxStack, maxLocals);
  }

  public void visitLocalVariable (
    final String name,
    final String desc,
    final Label start,
    final Label end,
    final int index)
  {
    checkIdentifier(name, "name");
    checkDesc(desc, false);
    checkLabel(start, true, "start label");
    checkLabel(end, true, "end label");
    checkUnsignedShort(index, "Invalid variable index");
    int s = ((Integer)labels.get(start)).intValue();
    int e = ((Integer)labels.get(end)).intValue();
    if (e <= s) {
      throw new IllegalArgumentException(
        "Invalid start and end labels (end must be greater than start)");
    }
    cv.visitLocalVariable(name, desc, start, end, index);
  }

  public void visitLineNumber (final int line, final Label start) {
    checkUnsignedShort(line, "Invalid line number");
    checkLabel(start, true, "start label");
    cv.visitLineNumber(line, start);
  }

  public void visitAttribute (Attribute attr) {
    if (attr == null) {
      throw new IllegalArgumentException(
        "Invalid attribute (must not be null)");
    }
  }

  // ---------------------------------------------------------------------------

  /**
   * Checks that the visitMaxs method has not been called.
   */

  void checkEnd () {
    if (end) {
      throw new IllegalStateException(
        "Cannot visit instructions after visitMaxs has been called.");
    }
  }

  /**
   * Checks that the type of the given opcode is equal to the given type.
   *
   * @param opcode the opcode to be checked.
   * @param type the expected opcode type.
   */

  static void checkOpcode (final int opcode, final int type) {
    if (opcode < 0 || opcode > 199 || TYPE[opcode] != type) {
      throw new IllegalArgumentException("Invalid opcode: " + opcode);
    }
  }

  /**
   * Checks that the given value is a signed byte.
   *
   * @param value the value to be checked.
   * @param msg an message to be used in case of error.
   */

  static void checkSignedByte (final int value, final String msg) {
    if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
      throw new IllegalArgumentException(
        msg + " (must be a signed byte): " + value);
    }
  }

  /**
   * Checks that the given value is a signed short.
   *
   * @param value the value to be checked.
   * @param msg an message to be used in case of error.
   */

  static void checkSignedShort (final int value, final String msg) {
    if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
      throw new IllegalArgumentException(
        msg + " (must be a signed short): " + value);
    }
  }

  /**
   * Checks that the given value is an unsigned short.
   *
   * @param value the value to be checked.
   * @param msg an message to be used in case of error.
   */

  static void checkUnsignedShort (final int value, final String msg) {
    if (value < 0 || value > 65535) {
      throw new IllegalArgumentException(
        msg + " (must be an unsigned short): " + value);
    }
  }

  /**
   * Checks that the given value is an {@link java.lang.Integer Integer}, a
   * {@link java.lang.Float Float}, a {@link java.lang.Long Long}, a {@link
   * java.lang.Double Double} or a {@link String String}.
   *
   * @param cst the value to be checked.
   */

  static void checkConstant (final Object cst) {
    if (!(cst instanceof Integer) &&
      !(cst instanceof Float) &&
      !(cst instanceof Long) &&
      !(cst instanceof Double) &&
      !(cst instanceof String))
    {
      throw new IllegalArgumentException("Invalid constant: " + cst);
    }
  }

  /**
   * Checks that the given string is a valid Java identifier.
   *
   * @param name the string to be checked.
   * @param msg a message to be used in case of error.
   */

  static void checkIdentifier (final String name, final String msg) {
    checkIdentifier(name, 0, -1, msg);
  }

  /**
   * Checks that the given substring is a valid Java identifier.
   *
   * @param name the string to be checked.
   * @param start index of the first character of the identifier (inclusive).
   * @param end index of the last character of the identifier (exclusive). -1 is
   *      equivalent to <tt>name.length()</tt> if name is not <tt>null</tt>.
   * @param msg a message to be used in case of error.
   */

  static void checkIdentifier (
    final String name,
    final int start,
    final int end,
    final String msg)
  {
    if (name == null || (end == -1 ? name.length() <= start : end <= start)) {
      throw new IllegalArgumentException(
        "Invalid " + msg + " (must not be null or empty)");
    }
    if (!Character.isJavaIdentifierStart(name.charAt(start))) {
      throw new IllegalArgumentException(
        "Invalid " + msg + " (must be a valid Java identifier): " + name);
    }
    int max = (end == -1 ? name.length() : end);
    for (int i = start + 1; i < max; ++i) {
      if (!Character.isJavaIdentifierPart(name.charAt(i))) {
        throw new IllegalArgumentException(
          "Invalid " + msg + " (must be a valid Java identifier): " + name);
      }
    }
  }

  /**
   * Checks that the given string is a valid Java identifier or is equal to
   * '&lt;init&gt;' or '&lt;clinit&gt;'.
   *
   * @param name the string to be checked.
   * @param msg a message to be used in case of error.
   */

  static void checkMethodIdentifier (final String name, final String msg) {
    if (name == null || name.length() == 0) {
      throw new IllegalArgumentException(
        "Invalid " + msg + " (must not be null or empty)");
    }
    if (name.equals("<init>") || name.equals("<clinit>")) {
      return;
    }
    if (!Character.isJavaIdentifierStart(name.charAt(0))) {
      throw new IllegalArgumentException(
        "Invalid " + msg +
        " (must be a '<init>', '<clinit>' or a valid Java identifier): " +
        name);
    }
    for (int i = 1; i < name.length(); ++i) {
      if (!Character.isJavaIdentifierPart(name.charAt(i))) {
        throw new IllegalArgumentException(
          "Invalid " + msg +
          " (must be '<init>' or '<clinit>' or a valid Java identifier): " +
          name);
      }
    }
  }

  /**
   * Checks that the given string is a valid internal class name.
   *
   * @param name the string to be checked.
   * @param msg a message to be used in case of error.
   */

  static void checkInternalName (final String name, final String msg) {
    checkInternalName(name, 0, -1, msg);
  }

  /**
   * Checks that the given substring is a valid internal class name.
   *
   * @param name the string to be checked.
   * @param start index of the first character of the identifier (inclusive).
   * @param end index of the last character of the identifier (exclusive). -1 is
   *      equivalent to <tt>name.length()</tt> if name is not <tt>null</tt>.
   * @param msg a message to be used in case of error.
   */

  static void checkInternalName (
    final String name,
    final int start,
    final int end,
    final String msg)
  {
    if (name == null || name.length() == 0) {
      throw new IllegalArgumentException(
        "Invalid " + msg + " (must not be null or empty)");
    }
    int max = (end == -1 ? name.length() : end);
    try {
      int begin = start;
      int slash;
      do {
        slash = name.indexOf('/', begin + 1);
        if (slash == -1 || slash > max) {
          slash = max;
        }
        checkIdentifier(name, begin, slash, null);
        begin = slash + 1;
      } while (slash != max);
    } catch (IllegalArgumentException _) {
      throw new IllegalArgumentException(
        "Invalid " + msg +
        " (must be a fully qualified class name in internal form): " +
        name);
    }
  }

  /**
   * Checks that the given string is a valid type descriptor.
   *
   * @param desc the string to be checked.
   * @param canBeVoid <tt>true</tt> if <tt>V</tt> can be considered valid.
   */

  static void checkDesc (final String desc, final boolean canBeVoid) {
    int end = checkDesc(desc, 0, canBeVoid);
    if (end != desc.length()) {
      throw new IllegalArgumentException("Invalid descriptor: " + desc);
    }
  }

  /**
   * Checks that a the given substring is a valid type descriptor.
   *
   * @param desc the string to be checked.
   * @param start index of the first character of the identifier (inclusive).
   * @param canBeVoid <tt>true</tt> if <tt>V</tt> can be considered valid.
   * @return the index of the last character of the type decriptor, plus one.
   */

  static int checkDesc (
    final String desc,
    final int start,
    final boolean canBeVoid)
  {
    if (desc == null || start >= desc.length()) {
      throw new IllegalArgumentException(
        "Invalid type descriptor (must not be null or empty)");
    }
    int index;
    switch (desc.charAt(start)) {
      case 'V':
        if (canBeVoid) {
          return start + 1;
        } else {
          throw new IllegalArgumentException("Invalid descriptor: " + desc);
        }
      case 'Z':
      case 'C':
      case 'B':
      case 'S':
      case 'I':
      case 'F':
      case 'J':
      case 'D':
        return start + 1;
      case '[':
        index = start + 1;
        while (index < desc.length() && desc.charAt(index) == '[') {
          ++index;
        }
        if (index < desc.length()) {
          return checkDesc(desc, index, false);
        } else {
          throw new IllegalArgumentException("Invalid descriptor: " + desc);
        }
      case 'L':
        index = desc.indexOf(';', start);
        if (index == -1 || index - start < 2) {
          throw new IllegalArgumentException("Invalid descriptor: " + desc);
        }
        try {
          checkInternalName(desc, start + 1, index, null);
        } catch (IllegalArgumentException _) {
          throw new IllegalArgumentException("Invalid descriptor: " + desc);
        }
        return index + 1;
      default:
        throw new IllegalArgumentException("Invalid descriptor: " + desc);
    }
  }

  /**
   * Checks that the given string is a valid method descriptor.
   *
   * @param desc the string to be checked.
   */

  static void checkMethodDesc (final String desc) {
    if (desc == null || desc.length() == 0) {
      throw new IllegalArgumentException(
        "Invalid method descriptor (must not be null or empty)");
    }
    if (desc.charAt(0) != '(' || desc.length() < 3) {
      throw new IllegalArgumentException("Invalid descriptor: " + desc);
    }
    int start = 1;
    if (desc.charAt(start) != ')') {
      do {
        if (desc.charAt(start) == 'V') {
          throw new IllegalArgumentException("Invalid descriptor: " + desc);
        }
        start = checkDesc(desc, start, false);
      } while (start < desc.length() && desc.charAt(start) != ')');
    }
    start = checkDesc(desc, start + 1, true);
    if (start != desc.length()) {
      throw new IllegalArgumentException("Invalid descriptor: " + desc);
    }
  }

  /**
   * Checks that the given label is not null. This method can also check that
   * the label has been visited.
   *
   * @param label the label to be checked.
   * @param checkVisited <tt>true</tt> to check that the label has been visited.
   * @param msg a message to be used in case of error.
   */

  void checkLabel (
    final Label label,
    final boolean checkVisited,
    final String msg)
  {
    if (label == null) {
      throw new IllegalArgumentException(
        "Invalid " + msg + " (must not be null)");
    }
    if (checkVisited && labels.get(label) == null) {
      throw new IllegalArgumentException(
        "Invalid " + msg + " (must be visited first)");
    }
  }
}
