/***
 * ASM: a very small and fast Java bytecode manipulation framework
 * Copyright (c) 2000-2007 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 alterrs.asm.util;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

import alterrs.asm.AnnotationVisitor;
import alterrs.asm.Attribute;
import alterrs.asm.Label;
import alterrs.asm.MethodAdapter;
import alterrs.asm.MethodVisitor;
import alterrs.asm.Opcodes;
import alterrs.asm.Type;
import alterrs.asm.tree.MethodNode;
import alterrs.asm.tree.analysis.Analyzer;
import alterrs.asm.tree.analysis.BasicVerifier;

/**
 * A {@link MethodAdapter} that checks that its methods are properly used. More
 * precisely this method 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. This adapter can also perform some basic data flow checks (more
 * precisely those that can be performed without the full class hierarchy - see
 * {@link alterrs.asm.tree.analysis.BasicVerifier}). For instance in a method
 * whose signature is <tt>void m ()</tt>, the invalid instruction IRETURN, or
 * the invalid sequence IADD L2I will be detected if the data flow checks are
 * enabled. These checks are enabled by using the {@link
 * CheckMethodAdapter(int,String,String, com.alterrs.asm.MethodVisitor ,Map)}
 * constructor. They are not performed if any other constructor is used.
 * 
 * @author Eric Bruneton
 */
public class CheckMethodAdapter extends MethodAdapter {

	/**
	 * The class version number.
	 */
	public int version;

	/**
	 * <tt>true</tt> if the visitCode method has been called.
	 */
	private boolean startCode;

	/**
	 * <tt>true</tt> if the visitMaxs method has been called.
	 */
	private boolean endCode;

	/**
	 * <tt>true</tt> if the visitEnd method has been called.
	 */
	private boolean endMethod;

	/**
	 * The already visited labels. This map associate Integer values to Label
	 * keys.
	 */
	private final Map labels;

	/**
	 * Code of the visit method to be used for each opcode.
	 */
	private static final int[] TYPE;

	/**
	 * The Label.status field.
	 */
	private static Field labelStatusField;

	static {
		String s = "BBBBBBBBBBBBBBBBCCIAADDDDDAAAAAAAAAAAAAAAAAAAABBBBBBBBDD"
				+ "DDDAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
				+ "BBBBBBBBBBBBBBBBBBBJBBBBBBBBBBBBBBBBBBBBHHHHHHHHHHHHHHHHD"
				+ "KLBBBBBBFFFFGGGGGECEBBEEBBAMHHAA";
		TYPE = new int[s.length()];
		for (int i = 0; i < TYPE.length; ++i) {
			TYPE[i] = s.charAt(i) - 'A' - 1;
		}
	}

	// code to generate the above string
	// public static void main (String[] args) {
	// 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
	// 5, //INVOKEDYNAMIC
	// 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
	// };
	// for (int i = 0; i < TYPE.length; ++i) {
	// System.out.print((char)(TYPE[i] + 1 + 'A'));
	// }
	// System.out.println();
	// }

	/**
	 * Constructs a new {@link CheckMethodAdapter} object. This method adapter
	 * will not perform any data flow check (see {@link
	 * CheckMethodAdapter(int,String,String, com.alterrs.asm.MethodVisitor
	 * ,Map)}).
	 * 
	 * @param mv
	 *            the method visitor to which this adapter must delegate calls.
	 */
	public CheckMethodAdapter(final MethodVisitor mv) {
		this(mv, new HashMap());
	}

	/**
	 * Constructs a new {@link CheckMethodAdapter} object. This method adapter
	 * will not perform any data flow check (see {@link
	 * CheckMethodAdapter(int,String,String,MethodVisitor,Map)}).
	 * 
	 * @param mv
	 *            the method visitor to which this adapter must delegate calls.
	 * @param labels
	 *            a map of already visited labels (in other methods).
	 */
	public CheckMethodAdapter(final MethodVisitor mv, final Map labels) {
		super(mv);
		this.labels = labels;
	}

	/**
	 * Constructs a new {@link CheckMethodAdapter} object. This method adapter
	 * will perform basic data flow checks. For instance in a method whose
	 * signature is <tt>void m ()</tt>, the invalid instruction IRETURN, or the
	 * invalid sequence IADD L2I will be detected.
	 * 
	 * @param access
	 *            the method's access flags.
	 * @param name
	 *            the method's name.
	 * @param desc
	 *            the method's descriptor (see {@link alterrs.asm.Type Type}).
	 * @param mv
	 *            the method visitor to which this adapter must delegate calls.
	 * @param labels
	 *            a map of already visited labels (in other methods).
	 */
	public CheckMethodAdapter(final int access, final String name,
			final String desc, final MethodVisitor mv, final Map labels) {
		this(new MethodNode(access, name, desc, null, null) {
			public void visitEnd() {
				Analyzer a = new Analyzer(new BasicVerifier());
				try {
					a.analyze("dummy", this);
				} catch (Exception e) {
					if (e instanceof IndexOutOfBoundsException
							&& maxLocals == 0 && maxStack == 0) {
						throw new RuntimeException(
								"Data flow checking option requires valid, non zero maxLocals and maxStack values.");
					}
					e.printStackTrace();
					StringWriter sw = new StringWriter();
					PrintWriter pw = new PrintWriter(sw, true);
					CheckClassAdapter.printAnalyzerResult(this, a, pw);
					pw.close();
					throw new RuntimeException(e.getMessage() + ' '
							+ sw.toString());
				}
				accept(mv);
			}
		}, labels);
	}

	public AnnotationVisitor visitAnnotation(final String desc,
			final boolean visible) {
		checkEndMethod();
		checkDesc(desc, false);
		return new CheckAnnotationAdapter(mv.visitAnnotation(desc, visible));
	}

	public AnnotationVisitor visitAnnotationDefault() {
		checkEndMethod();
		return new CheckAnnotationAdapter(mv.visitAnnotationDefault(), false);
	}

	public AnnotationVisitor visitParameterAnnotation(final int parameter,
			final String desc, final boolean visible) {
		checkEndMethod();
		checkDesc(desc, false);
		return new CheckAnnotationAdapter(mv.visitParameterAnnotation(
				parameter, desc, visible));
	}

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

	public void visitCode() {
		startCode = true;
		mv.visitCode();
	}

	public void visitFrame(final int type, final int nLocal,
			final Object[] local, final int nStack, final Object[] stack) {
		int mLocal;
		int mStack;
		switch (type) {
		case Opcodes.F_NEW:
		case Opcodes.F_FULL:
			mLocal = Integer.MAX_VALUE;
			mStack = Integer.MAX_VALUE;
			break;

		case Opcodes.F_SAME:
			mLocal = 0;
			mStack = 0;
			break;

		case Opcodes.F_SAME1:
			mLocal = 0;
			mStack = 1;
			break;

		case Opcodes.F_APPEND:
		case Opcodes.F_CHOP:
			mLocal = 3;
			mStack = 0;
			break;

		default:
			throw new IllegalArgumentException("Invalid frame type " + type);
		}

		if (nLocal > mLocal) {
			throw new IllegalArgumentException("Invalid nLocal=" + nLocal
					+ " for frame type " + type);
		}
		if (nStack > mStack) {
			throw new IllegalArgumentException("Invalid nStack=" + nStack
					+ " for frame type " + type);
		}

		if (type != Opcodes.F_CHOP) {
			if (nLocal > 0 && (local == null || local.length < nLocal)) {
				throw new IllegalArgumentException(
						"Array local[] is shorter than nLocal");
			}
			for (int i = 0; i < nLocal; ++i) {
				checkFrameValue(local[i]);
			}
		}
		if (nStack > 0 && (stack == null || stack.length < nStack)) {
			throw new IllegalArgumentException(
					"Array stack[] is shorter than nStack");
		}
		for (int i = 0; i < nStack; ++i) {
			checkFrameValue(stack[i]);
		}

		mv.visitFrame(type, nLocal, local, nStack, stack);
	}

	public void visitInsn(final int opcode) {
		checkStartCode();
		checkEndCode();
		checkOpcode(opcode, 0);
		mv.visitInsn(opcode);
	}

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

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

	public void visitTypeInsn(final int opcode, final String type) {
		checkStartCode();
		checkEndCode();
		checkOpcode(opcode, 3);
		checkInternalName(type, "type");
		if (opcode == Opcodes.NEW && type.charAt(0) == '[') {
			throw new IllegalArgumentException(
					"NEW cannot be used to create arrays: " + type);
		}
		mv.visitTypeInsn(opcode, type);
	}

	public void visitFieldInsn(final int opcode, final String owner,
			final String name, final String desc) {
		checkStartCode();
		checkEndCode();
		checkOpcode(opcode, 4);
		checkInternalName(owner, "owner");
		checkUnqualifiedName(version, name, "name");
		checkDesc(desc, false);
		mv.visitFieldInsn(opcode, owner, name, desc);
	}

	public void visitMethodInsn(final int opcode, final String owner,
			final String name, final String desc) {
		checkStartCode();
		checkEndCode();
		checkOpcode(opcode, 5);
		checkMethodIdentifier(version, name, "name");
		checkInternalName(owner, "owner");
		checkMethodDesc(desc);
		if (opcode == Opcodes.INVOKEDYNAMIC
				&& owner != Opcodes.INVOKEDYNAMIC_OWNER) {
			throw new IllegalArgumentException(
					"INVOKEDYNAMIC cannot be used with another owner than INVOKEDYNAMIC_OWNER");
		}
		mv.visitMethodInsn(opcode, owner, name, desc);
	}

	public void visitJumpInsn(final int opcode, final Label label) {
		checkStartCode();
		checkEndCode();
		checkOpcode(opcode, 6);
		checkLabel(label, false, "label");
		checkNonDebugLabel(label);
		mv.visitJumpInsn(opcode, label);
	}

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

	public void visitLdcInsn(final Object cst) {
		checkStartCode();
		checkEndCode();
		if (!(cst instanceof Type)) {
			checkConstant(cst);
		}
		mv.visitLdcInsn(cst);
	}

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

	public void visitTableSwitchInsn(final int min, final int max,
			final Label dflt, final Label[] labels) {
		checkStartCode();
		checkEndCode();
		if (max < min) {
			throw new IllegalArgumentException("Max = " + max
					+ " must be greater than or equal to min = " + min);
		}
		checkLabel(dflt, false, "default label");
		checkNonDebugLabel(dflt);
		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);
			checkNonDebugLabel(labels[i]);
		}
		mv.visitTableSwitchInsn(min, max, dflt, labels);
	}

	public void visitLookupSwitchInsn(final Label dflt, final int[] keys,
			final Label[] labels) {
		checkEndCode();
		checkStartCode();
		checkLabel(dflt, false, "default label");
		checkNonDebugLabel(dflt);
		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);
			checkNonDebugLabel(labels[i]);
		}
		mv.visitLookupSwitchInsn(dflt, keys, labels);
	}

	public void visitMultiANewArrayInsn(final String desc, final int dims) {
		checkStartCode();
		checkEndCode();
		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);
		}
		mv.visitMultiANewArrayInsn(desc, dims);
	}

	public void visitTryCatchBlock(final Label start, final Label end,
			final Label handler, final String type) {
		checkStartCode();
		checkEndCode();
		checkLabel(start, false, "start label");
		checkLabel(end, false, "end label");
		checkLabel(handler, false, "handler label");
		checkNonDebugLabel(start);
		checkNonDebugLabel(end);
		checkNonDebugLabel(handler);
		if (labels.get(start) != null || labels.get(end) != null
				|| labels.get(handler) != null) {
			throw new IllegalStateException(
					"Try catch blocks must be visited before their labels");
		}
		if (type != null) {
			checkInternalName(type, "type");
		}
		mv.visitTryCatchBlock(start, end, handler, type);
	}

	public void visitLocalVariable(final String name, final String desc,
			final String signature, final Label start, final Label end,
			final int index) {
		checkStartCode();
		checkEndCode();
		checkUnqualifiedName(version, 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)");
		}
		mv.visitLocalVariable(name, desc, signature, start, end, index);
	}

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

	public void visitMaxs(final int maxStack, final int maxLocals) {
		checkStartCode();
		checkEndCode();
		endCode = true;
		checkUnsignedShort(maxStack, "Invalid max stack");
		checkUnsignedShort(maxLocals, "Invalid max locals");
		mv.visitMaxs(maxStack, maxLocals);
	}

	public void visitEnd() {
		checkEndMethod();
		endMethod = true;
		mv.visitEnd();
	}

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

	/**
	 * Checks that the visitCode method has been called.
	 */
	void checkStartCode() {
		if (!startCode) {
			throw new IllegalStateException(
					"Cannot visit instructions before visitCode has been called.");
		}
	}

	/**
	 * Checks that the visitMaxs method has not been called.
	 */
	void checkEndCode() {
		if (endCode) {
			throw new IllegalStateException(
					"Cannot visit instructions after visitMaxs has been called.");
		}
	}

	/**
	 * Checks that the visitEnd method has not been called.
	 */
	void checkEndMethod() {
		if (endMethod) {
			throw new IllegalStateException(
					"Cannot visit elements after visitEnd has been called.");
		}
	}

	/**
	 * Checks a stack frame value.
	 * 
	 * @param value
	 *            the value to be checked.
	 */
	static void checkFrameValue(final Object value) {
		if (value == Opcodes.TOP || value == Opcodes.INTEGER
				|| value == Opcodes.FLOAT || value == Opcodes.LONG
				|| value == Opcodes.DOUBLE || value == Opcodes.NULL
				|| value == Opcodes.UNINITIALIZED_THIS) {
			return;
		}
		if (value instanceof String) {
			checkInternalName((String) value, "Invalid stack frame value");
			return;
		}
		if (!(value instanceof Label)) {
			throw new IllegalArgumentException("Invalid stack frame value: "
					+ value);
		}
	}

	/**
	 * 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 Integer}, a{@link Float}, a
	 * {@link Long}, a {@link Double} or a {@link 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 unqualified name.
	 * 
	 * @param version
	 *            the class version.
	 * @param name
	 *            the string to be checked.
	 * @param msg
	 *            a message to be used in case of error.
	 */
	static void checkUnqualifiedName(int version, final String name,
			final String msg) {
		if ((version & 0xFFFF) < Opcodes.V1_5) {
			checkIdentifier(name, msg);
		} else {
			for (int i = 0; i < name.length(); ++i) {
				if (".;[/".indexOf(name.charAt(i)) != -1) {
					throw new IllegalArgumentException("Invalid " + msg
							+ " (must be a valid unqualified name): " + name);
				}
			}
		}
	}

	/**
	 * 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 version
	 *            the class version.
	 * @param name
	 *            the string to be checked.
	 * @param msg
	 *            a message to be used in case of error.
	 */
	static void checkMethodIdentifier(int version, final String name,
			final String msg) {
		if (name == null || name.length() == 0) {
			throw new IllegalArgumentException("Invalid " + msg
					+ " (must not be null or empty)");
		}
		if ("<init>".equals(name) || "<clinit>".equals(name)) {
			return;
		}
		if ((version & 0xFFFF) >= Opcodes.V1_5) {
			for (int i = 0; i < name.length(); ++i) {
				if (".;[/<>".indexOf(name.charAt(i)) != -1) {
					throw new IllegalArgumentException("Invalid " + msg
							+ " (must be a valid unqualified name): " + name);
				}
			}
			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) {
		if (name == null || name.length() == 0) {
			throw new IllegalArgumentException("Invalid " + msg
					+ " (must not be null or empty)");
		}
		if (name.charAt(0) == '[') {
			checkDesc(name, false);
		} else {
			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) {
		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 a class signature.
	 * 
	 * @param signature
	 *            a string containing the signature that must be checked.
	 */
	static void checkClassSignature(final String signature) {
		// ClassSignature:
		// FormalTypeParameters? ClassTypeSignature ClassTypeSignature*

		int pos = 0;
		if (getChar(signature, 0) == '<') {
			pos = checkFormalTypeParameters(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 index "
					+ pos);
		}
	}

	/**
	 * Checks a method signature.
	 * 
	 * @param signature
	 *            a string containing the signature that must be checked.
	 */
	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.
	 */
	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 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 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 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);
		}
		return pos + 1;
	}

	/**
	 * 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 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;
		}
		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;
		}
		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;
	}

	/**
	 * 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)");
		}
	}

	/**
	 * Checks that the given lavel is not a label used only for debug purposes.
	 * 
	 * @param label
	 *            the label to be checked.
	 */
	private static void checkNonDebugLabel(final Label label) {
		Field f = getLabelStatusField();
		int status = 0;
		try {
			status = f == null ? 0 : ((Integer) f.get(label)).intValue();
		} catch (IllegalAccessException e) {
			throw new Error("Internal error");
		}
		if ((status & 0x01) != 0) {
			throw new IllegalArgumentException(
					"Labels used for debug info cannot be reused for control flow");
		}
	}

	/**
	 * Returns the Field object corresponding to the Label.status field.
	 * 
	 * @return the Field object corresponding to the Label.status field.
	 */
	private static Field getLabelStatusField() {
		if (labelStatusField == null) {
			labelStatusField = getLabelField("a");
			if (labelStatusField == null) {
				labelStatusField = getLabelField("status");
			}
		}
		return labelStatusField;
	}

	/**
	 * Returns the field of the Label class whose name is given.
	 * 
	 * @param name
	 *            a field name.
	 * @return the field of the Label class whose name is given, or null.
	 */
	private static Field getLabelField(final String name) {
		try {
			Field f = Label.class.getDeclaredField(name);
			f.setAccessible(true);
			return f;
		} catch (NoSuchFieldException e) {
			return null;
		}
	}
}
