/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.classfile;

import org.mozilla.javascript.ObjToIntMap;
import org.mozilla.javascript.ObjArray;
import org.mozilla.javascript.UintMap;

import java.io.*;
import java.util.Arrays;

/**
 * ClassFileWriter
 *
 * A ClassFileWriter is used to write a Java class file. Methods are
 * provided to create fields and methods, and within methods to write
 * Java bytecodes.
 *
 * @author Roger Lawrence
 */
public class ClassFileWriter {

    /**
     * Thrown for cases where the error in generating the class file is
     * due to a program size constraints rather than a likely bug in the
     * compiler.
     */
    public static class ClassFileFormatException extends RuntimeException {

        private static final long serialVersionUID = 1263998431033790599L;

        ClassFileFormatException(String message) {
            super(message);
        }
    }

    /**
     * Construct a ClassFileWriter for a class.
     *
     * @param className the name of the class to write, including
     *        full package qualification.
     * @param superClassName the name of the superclass of the class
     *        to write, including full package qualification.
     * @param sourceFileName the name of the source file to use for
     *        producing debug information, or null if debug information
     *        is not desired
     */
    public ClassFileWriter(String className, String superClassName,
                           String sourceFileName)
    {
        generatedClassName = className;
        itsConstantPool = new ConstantPool(this);
        itsThisClassIndex = itsConstantPool.addClass(className);
        itsSuperClassIndex = itsConstantPool.addClass(superClassName);
        if (sourceFileName != null)
            itsSourceFileNameIndex = itsConstantPool.addUtf8(sourceFileName);
        // All "new" implementations are supposed to output ACC_SUPER as a
        // class flag. This is specified in the first JVM spec, so it should
        // be old enough that it's okay to always set it.
        itsFlags = ACC_PUBLIC | ACC_SUPER;
    }

    public final String getClassName()
    {
        return generatedClassName;
    }

    /**
     * Add an interface implemented by this class.
     *
     * This method may be called multiple times for classes that
     * implement multiple interfaces.
     *
     * @param interfaceName a name of an interface implemented
     *        by the class being written, including full package
     *        qualification.
     */
    public void addInterface(String interfaceName) {
        short interfaceIndex = itsConstantPool.addClass(interfaceName);
        itsInterfaces.add(Short.valueOf(interfaceIndex));
    }

    public static final short
        ACC_PUBLIC = 0x0001,
        ACC_PRIVATE = 0x0002,
        ACC_PROTECTED = 0x0004,
        ACC_STATIC = 0x0008,
        ACC_FINAL = 0x0010,
        ACC_SUPER = 0x0020,
        ACC_SYNCHRONIZED = 0x0020,
        ACC_VOLATILE = 0x0040,
        ACC_TRANSIENT = 0x0080,
        ACC_NATIVE = 0x0100,
        ACC_ABSTRACT = 0x0400;

    /**
     * Set the class's flags.
     *
     * Flags must be a set of the following flags, bitwise or'd
     * together:
     *      ACC_PUBLIC
     *      ACC_PRIVATE
     *      ACC_PROTECTED
     *      ACC_FINAL
     *      ACC_ABSTRACT
     * TODO: check that this is the appropriate set
     * @param flags the set of class flags to set
     */
    public void setFlags(short flags) {
        itsFlags = flags;
    }

    static String getSlashedForm(String name)
    {
        return name.replace('.', '/');
    }

    /**
     * Convert Java class name in dot notation into
     * "Lname-with-dots-replaced-by-slashes;" form suitable for use as
     * JVM type signatures.
     */
    public static String classNameToSignature(String name)
    {
        int nameLength = name.length();
        int colonPos = 1 + nameLength;
        char[] buf = new char[colonPos + 1];
        buf[0] = 'L';
        buf[colonPos] = ';';
        name.getChars(0, nameLength, buf, 1);
        for (int i = 1; i != colonPos; ++i) {
            if (buf[i] == '.') {
                buf[i] = '/';
            }
        }
        return new String(buf, 0, colonPos + 1);
    }

    /**
     * Add a field to the class.
     *
     * @param fieldName the name of the field
     * @param type the type of the field using ...
     * @param flags the attributes of the field, such as ACC_PUBLIC, etc.
     *        bitwise or'd together
     */
    public void addField(String fieldName, String type, short flags) {
        short fieldNameIndex = itsConstantPool.addUtf8(fieldName);
        short typeIndex = itsConstantPool.addUtf8(type);
        itsFields.add(new ClassFileField(fieldNameIndex, typeIndex, flags));
    }

    /**
     * Add a field to the class.
     *
     * @param fieldName the name of the field
     * @param type the type of the field using ...
     * @param flags the attributes of the field, such as ACC_PUBLIC, etc.
     *        bitwise or'd together
     * @param value an initial integral value
     */
    public void addField(String fieldName, String type, short flags,
                         int value)
    {
        short fieldNameIndex = itsConstantPool.addUtf8(fieldName);
        short typeIndex = itsConstantPool.addUtf8(type);
        ClassFileField field = new ClassFileField(fieldNameIndex, typeIndex,
                                                  flags);
        field.setAttributes(itsConstantPool.addUtf8("ConstantValue"),
                            (short)0,
                            (short)0,
                            itsConstantPool.addConstant(value));
        itsFields.add(field);
    }

    /**
     * Add a field to the class.
     *
     * @param fieldName the name of the field
     * @param type the type of the field using ...
     * @param flags the attributes of the field, such as ACC_PUBLIC, etc.
     *        bitwise or'd together
     * @param value an initial long value
     */
    public void addField(String fieldName, String type, short flags,
                         long value)
    {
        short fieldNameIndex = itsConstantPool.addUtf8(fieldName);
        short typeIndex = itsConstantPool.addUtf8(type);
        ClassFileField field = new ClassFileField(fieldNameIndex, typeIndex,
                                                  flags);
        field.setAttributes(itsConstantPool.addUtf8("ConstantValue"),
                            (short)0,
                            (short)2,
                            itsConstantPool.addConstant(value));
        itsFields.add(field);
    }

    /**
     * Add a field to the class.
     *
     * @param fieldName the name of the field
     * @param type the type of the field using ...
     * @param flags the attributes of the field, such as ACC_PUBLIC, etc.
     *        bitwise or'd together
     * @param value an initial double value
     */
    public void addField(String fieldName, String type, short flags,
                         double value)
    {
        short fieldNameIndex = itsConstantPool.addUtf8(fieldName);
        short typeIndex = itsConstantPool.addUtf8(type);
        ClassFileField field = new ClassFileField(fieldNameIndex, typeIndex,
                                                  flags);
        field.setAttributes(itsConstantPool.addUtf8("ConstantValue"),
                            (short)0,
                            (short)2,
                            itsConstantPool.addConstant(value));
        itsFields.add(field);
    }

    /**
     * Add Information about java variable to use when generating the local
     * variable table.
     *
     * @param name variable name.
     * @param type variable type as bytecode descriptor string.
     * @param startPC the starting bytecode PC where this variable is live,
     *                 or -1 if it does not have a Java register.
     * @param register the Java register number of variable
     *                 or -1 if it does not have a Java register.
     */
    public void addVariableDescriptor(String name, String type, int startPC, int register)
    {
        int nameIndex = itsConstantPool.addUtf8(name);
        int descriptorIndex = itsConstantPool.addUtf8(type);
        int [] chunk = { nameIndex, descriptorIndex, startPC, register };
        if (itsVarDescriptors == null) {
            itsVarDescriptors = new ObjArray();
        }
        itsVarDescriptors.add(chunk);
    }

    /**
     * Add a method and begin adding code.
     *
     * This method must be called before other methods for adding code,
     * exception tables, etc. can be invoked.
     *
     * @param methodName the name of the method
     * @param type a string representing the type
     * @param flags the attributes of the field, such as ACC_PUBLIC, etc.
     *        bitwise or'd together
     */
    public void startMethod(String methodName, String type, short flags) {
        short methodNameIndex = itsConstantPool.addUtf8(methodName);
        short typeIndex = itsConstantPool.addUtf8(type);
        itsCurrentMethod = new ClassFileMethod(methodName, methodNameIndex,
                                               type, typeIndex, flags);
        itsJumpFroms = new UintMap();
        itsMethods.add(itsCurrentMethod);
        addSuperBlockStart(0);
    }

    /**
     * Complete generation of the method.
     *
     * After this method is called, no more code can be added to the
     * method begun with <code>startMethod</code>.
     *
     * @param maxLocals the maximum number of local variable slots
     *        (a.k.a. Java registers) used by the method
     */
    public void stopMethod(short maxLocals) {
        if (itsCurrentMethod == null)
            throw new IllegalStateException("No method to stop");

        fixLabelGotos();

        itsMaxLocals = maxLocals;

        StackMapTable stackMap = null;
        if (GenerateStackMap) {
            finalizeSuperBlockStarts();
            stackMap = new StackMapTable();
            stackMap.generate();
        }

        int lineNumberTableLength = 0;
        if (itsLineNumberTable != null) {
            // 6 bytes for the attribute header
            // 2 bytes for the line number count
            // 4 bytes for each entry
            lineNumberTableLength = 6 + 2 + (itsLineNumberTableTop * 4);
        }

        int variableTableLength = 0;
        if (itsVarDescriptors != null) {
            // 6 bytes for the attribute header
            // 2 bytes for the variable count
            // 10 bytes for each entry
            variableTableLength = 6 + 2 + (itsVarDescriptors.size() * 10);
        }

        int stackMapTableLength = 0;
        if (stackMap != null) {
            int stackMapWriteSize = stackMap.computeWriteSize();
            if (stackMapWriteSize > 0) {
                stackMapTableLength = 6 + stackMapWriteSize;
            }
        }

        int attrLength = 2 +                    // attribute_name_index
                         4 +                    // attribute_length
                         2 +                    // max_stack
                         2 +                    // max_locals
                         4 +                    // code_length
                         itsCodeBufferTop +
                         2 +                    // exception_table_length
                         (itsExceptionTableTop * 8) +
                         2 +                    // attributes_count
                         lineNumberTableLength +
                         variableTableLength +
                         stackMapTableLength;

        if (attrLength > 65536) {
            // See http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html,
            // section 4.10, "The amount of code per non-native, non-abstract
            // method is limited to 65536 bytes...
            throw new ClassFileFormatException(
                "generated bytecode for method exceeds 64K limit.");
        }
        byte[] codeAttribute = new byte[attrLength];
        int index = 0;
        int codeAttrIndex = itsConstantPool.addUtf8("Code");
        index = putInt16(codeAttrIndex, codeAttribute, index);
        attrLength -= 6;                 // discount the attribute header
        index = putInt32(attrLength, codeAttribute, index);
        index = putInt16(itsMaxStack, codeAttribute, index);
        index = putInt16(itsMaxLocals, codeAttribute, index);
        index = putInt32(itsCodeBufferTop, codeAttribute, index);
        System.arraycopy(itsCodeBuffer, 0, codeAttribute, index,
                         itsCodeBufferTop);
        index += itsCodeBufferTop;

        if (itsExceptionTableTop > 0) {
            index = putInt16(itsExceptionTableTop, codeAttribute, index);
            for (int i = 0; i < itsExceptionTableTop; i++) {
                ExceptionTableEntry ete = itsExceptionTable[i];
                short startPC = (short)getLabelPC(ete.itsStartLabel);
                short endPC = (short)getLabelPC(ete.itsEndLabel);
                short handlerPC = (short)getLabelPC(ete.itsHandlerLabel);
                short catchType = ete.itsCatchType;
                if (startPC == -1)
                    throw new IllegalStateException("start label not defined");
                if (endPC == -1)
                    throw new IllegalStateException("end label not defined");
                if (handlerPC == -1)
                    throw new IllegalStateException(
                        "handler label not defined");

                index = putInt16(startPC, codeAttribute, index);
                index = putInt16(endPC, codeAttribute, index);
                index = putInt16(handlerPC, codeAttribute, index);
                index = putInt16(catchType, codeAttribute, index);
            }
        }
        else {
            // write 0 as exception table length
            index = putInt16(0, codeAttribute, index);
        }

        int attributeCount = 0;
        if (itsLineNumberTable != null)
            attributeCount++;
        if (itsVarDescriptors != null)
            attributeCount++;
        if (stackMapTableLength > 0) {
            attributeCount++;
        }
        index = putInt16(attributeCount, codeAttribute, index);

        if (itsLineNumberTable != null) {
            int lineNumberTableAttrIndex
                    = itsConstantPool.addUtf8("LineNumberTable");
            index = putInt16(lineNumberTableAttrIndex, codeAttribute, index);
            int tableAttrLength = 2 + (itsLineNumberTableTop * 4);
            index = putInt32(tableAttrLength, codeAttribute, index);
            index = putInt16(itsLineNumberTableTop, codeAttribute, index);
            for (int i = 0; i < itsLineNumberTableTop; i++) {
                index = putInt32(itsLineNumberTable[i], codeAttribute, index);
            }
        }

        if (itsVarDescriptors != null) {
            int variableTableAttrIndex
                    = itsConstantPool.addUtf8("LocalVariableTable");
            index = putInt16(variableTableAttrIndex, codeAttribute, index);
            int varCount = itsVarDescriptors.size();
            int tableAttrLength = 2 + (varCount * 10);
            index = putInt32(tableAttrLength, codeAttribute, index);
            index = putInt16(varCount, codeAttribute, index);
            for (int i = 0; i < varCount; i++) {
                int[] chunk = (int[])itsVarDescriptors.get(i);
                int nameIndex       = chunk[0];
                int descriptorIndex = chunk[1];
                int startPC         = chunk[2];
                int register        = chunk[3];
                int length = itsCodeBufferTop - startPC;

                index = putInt16(startPC, codeAttribute, index);
                index = putInt16(length, codeAttribute, index);
                index = putInt16(nameIndex, codeAttribute, index);
                index = putInt16(descriptorIndex, codeAttribute, index);
                index = putInt16(register, codeAttribute, index);
            }
        }

        if (stackMapTableLength > 0) {
            int stackMapTableAttrIndex =
                    itsConstantPool.addUtf8("StackMapTable");
            int start = index;
            index = putInt16(stackMapTableAttrIndex, codeAttribute, index);
            index = stackMap.write(codeAttribute, index);
        }

        itsCurrentMethod.setCodeAttribute(codeAttribute);

        itsExceptionTable = null;
        itsExceptionTableTop = 0;
        itsLineNumberTableTop = 0;
        itsCodeBufferTop = 0;
        itsCurrentMethod = null;
        itsMaxStack = 0;
        itsStackTop = 0;
        itsLabelTableTop = 0;
        itsFixupTableTop = 0;
        itsVarDescriptors = null;
        itsSuperBlockStarts = null;
        itsSuperBlockStartsTop = 0;
        itsJumpFroms = null;
    }

    /**
     * Add the single-byte opcode to the current method.
     *
     * @param theOpCode the opcode of the bytecode
     */
    public void add(int theOpCode) {
        if (opcodeCount(theOpCode) != 0)
            throw new IllegalArgumentException("Unexpected operands");
        int newStack = itsStackTop + stackChange(theOpCode);
        if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack);
        if (DEBUGCODE)
            System.out.println("Add " + bytecodeStr(theOpCode));
        addToCodeBuffer(theOpCode);
        itsStackTop = (short)newStack;
        if (newStack > itsMaxStack) itsMaxStack = (short)newStack;
        if (DEBUGSTACK) {
            System.out.println("After "+bytecodeStr(theOpCode)
                               +" stack = "+itsStackTop);
        }
        if (theOpCode == ByteCode.ATHROW) {
            addSuperBlockStart(itsCodeBufferTop);
        }
    }

    /**
     * Add a single-operand opcode to the current method.
     *
     * @param theOpCode the opcode of the bytecode
     * @param theOperand the operand of the bytecode
     */
    public void add(int theOpCode, int theOperand) {
        if (DEBUGCODE) {
            System.out.println("Add "+bytecodeStr(theOpCode)
                               +", "+Integer.toHexString(theOperand));
        }
        int newStack = itsStackTop + stackChange(theOpCode);
        if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack);

        switch (theOpCode) {
            case ByteCode.GOTO :
                // This is necessary because dead code is seemingly being
                // generated and Sun's verifier is expecting type state to be
                // placed even at dead blocks of code.
                addSuperBlockStart(itsCodeBufferTop + 3);
                // fallthru...
            case ByteCode.IFEQ :
            case ByteCode.IFNE :
            case ByteCode.IFLT :
            case ByteCode.IFGE :
            case ByteCode.IFGT :
            case ByteCode.IFLE :
            case ByteCode.IF_ICMPEQ :
            case ByteCode.IF_ICMPNE :
            case ByteCode.IF_ICMPLT :
            case ByteCode.IF_ICMPGE :
            case ByteCode.IF_ICMPGT :
            case ByteCode.IF_ICMPLE :
            case ByteCode.IF_ACMPEQ :
            case ByteCode.IF_ACMPNE :
            case ByteCode.JSR :
            case ByteCode.IFNULL :
            case ByteCode.IFNONNULL : {
                    if ((theOperand & 0x80000000) != 0x80000000) {
                        if ((theOperand < 0) || (theOperand > 65535))
                            throw new IllegalArgumentException(
                                "Bad label for branch");
                    }
                    int branchPC = itsCodeBufferTop;
                    addToCodeBuffer(theOpCode);
                    if ((theOperand & 0x80000000) != 0x80000000) {
                            // hard displacement
                        addToCodeInt16(theOperand);
                        int target = theOperand + branchPC;
                        addSuperBlockStart(target);
                        itsJumpFroms.put(target, branchPC);
                    }
                    else {  // a label
                        int targetPC = getLabelPC(theOperand);
                        if (DEBUGLABELS) {
                            int theLabel = theOperand & 0x7FFFFFFF;
                            System.out.println("Fixing branch to " +
                                               theLabel + " at " + targetPC +
                                               " from " + branchPC);
                        }
                        if (targetPC != -1) {
                            int offset = targetPC - branchPC;
                            addToCodeInt16(offset);
                            addSuperBlockStart(targetPC);
                            itsJumpFroms.put(targetPC, branchPC);
                        }
                        else {
                            addLabelFixup(theOperand, branchPC + 1);
                            addToCodeInt16(0);
                        }
                    }
                }
                break;

            case ByteCode.BIPUSH :
                if ((byte)theOperand != theOperand)
                    throw new IllegalArgumentException("out of range byte");
                addToCodeBuffer(theOpCode);
                addToCodeBuffer((byte)theOperand);
                break;

            case ByteCode.SIPUSH :
                if ((short)theOperand != theOperand)
                    throw new IllegalArgumentException("out of range short");
                addToCodeBuffer(theOpCode);
                   addToCodeInt16(theOperand);
                break;

            case ByteCode.NEWARRAY :
                if (!(0 <= theOperand && theOperand < 256))
                    throw new IllegalArgumentException("out of range index");
                addToCodeBuffer(theOpCode);
                addToCodeBuffer(theOperand);
                break;

            case ByteCode.GETFIELD :
            case ByteCode.PUTFIELD :
                if (!(0 <= theOperand && theOperand < 65536))
                    throw new IllegalArgumentException("out of range field");
                addToCodeBuffer(theOpCode);
                addToCodeInt16(theOperand);
                break;

            case ByteCode.LDC :
            case ByteCode.LDC_W :
            case ByteCode.LDC2_W :
                if (!(0 <= theOperand && theOperand < 65536))
                    throw new IllegalArgumentException("out of range index");
                if (theOperand >= 256
                    || theOpCode == ByteCode.LDC_W
                    || theOpCode == ByteCode.LDC2_W)
                {
                    if (theOpCode == ByteCode.LDC) {
                        addToCodeBuffer(ByteCode.LDC_W);
                    } else {
                        addToCodeBuffer(theOpCode);
                    }
                    addToCodeInt16(theOperand);
                } else {
                    addToCodeBuffer(theOpCode);
                    addToCodeBuffer(theOperand);
                }
                break;

            case ByteCode.RET :
            case ByteCode.ILOAD :
            case ByteCode.LLOAD :
            case ByteCode.FLOAD :
            case ByteCode.DLOAD :
            case ByteCode.ALOAD :
            case ByteCode.ISTORE :
            case ByteCode.LSTORE :
            case ByteCode.FSTORE :
            case ByteCode.DSTORE :
            case ByteCode.ASTORE :
                if (!(0 <= theOperand && theOperand < 65536))
                    throw new ClassFileFormatException("out of range variable");
                if (theOperand >= 256) {
                    addToCodeBuffer(ByteCode.WIDE);
                    addToCodeBuffer(theOpCode);
                    addToCodeInt16(theOperand);
                }
                else {
                    addToCodeBuffer(theOpCode);
                    addToCodeBuffer(theOperand);
                }
                break;

            default :
                throw new IllegalArgumentException(
                    "Unexpected opcode for 1 operand");
        }

        itsStackTop = (short)newStack;
        if (newStack > itsMaxStack) itsMaxStack = (short)newStack;
        if (DEBUGSTACK) {
            System.out.println("After "+bytecodeStr(theOpCode)
                               +" stack = "+itsStackTop);
        }
    }

    /**
     * Generate the load constant bytecode for the given integer.
     *
     * @param k the constant
     */
    public void addLoadConstant(int k) {
        switch (k) {
            case 0: add(ByteCode.ICONST_0); break;
            case 1: add(ByteCode.ICONST_1); break;
            case 2: add(ByteCode.ICONST_2); break;
            case 3: add(ByteCode.ICONST_3); break;
            case 4: add(ByteCode.ICONST_4); break;
            case 5: add(ByteCode.ICONST_5); break;
            default:
                add(ByteCode.LDC, itsConstantPool.addConstant(k));
                break;
        }
    }

    /**
     * Generate the load constant bytecode for the given long.
     *
     * @param k the constant
     */
    public void addLoadConstant(long k) {
        add(ByteCode.LDC2_W, itsConstantPool.addConstant(k));
    }

    /**
     * Generate the load constant bytecode for the given float.
     *
     * @param k the constant
     */
    public void addLoadConstant(float k) {
        add(ByteCode.LDC, itsConstantPool.addConstant(k));
    }

    /**
     * Generate the load constant bytecode for the given double.
     *
     * @param k the constant
     */
    public void addLoadConstant(double k) {
        add(ByteCode.LDC2_W, itsConstantPool.addConstant(k));
    }

    /**
     * Generate the load constant bytecode for the given string.
     *
     * @param k the constant
     */
    public void addLoadConstant(String k) {
        add(ByteCode.LDC, itsConstantPool.addConstant(k));
    }

    /**
     * Add the given two-operand bytecode to the current method.
     *
     * @param theOpCode the opcode of the bytecode
     * @param theOperand1 the first operand of the bytecode
     * @param theOperand2 the second operand of the bytecode
     */
    public void add(int theOpCode, int theOperand1, int theOperand2) {
        if (DEBUGCODE) {
            System.out.println("Add "+bytecodeStr(theOpCode)
                               +", "+Integer.toHexString(theOperand1)
                               +", "+Integer.toHexString(theOperand2));
        }
        int newStack = itsStackTop + stackChange(theOpCode);
        if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack);

        if (theOpCode == ByteCode.IINC) {
            if (!(0 <= theOperand1 && theOperand1 < 65536))
                throw new ClassFileFormatException("out of range variable");
            if (!(0 <= theOperand2 && theOperand2 < 65536))
                throw new ClassFileFormatException("out of range increment");

            if (theOperand1 > 255 || theOperand2 < -128 || theOperand2 > 127) {
                addToCodeBuffer(ByteCode.WIDE);
                addToCodeBuffer(ByteCode.IINC);
                addToCodeInt16(theOperand1);
                addToCodeInt16(theOperand2);
            }
            else {
                addToCodeBuffer(ByteCode.IINC);
                addToCodeBuffer(theOperand1);
                addToCodeBuffer(theOperand2);
            }
        }
        else if (theOpCode == ByteCode.MULTIANEWARRAY) {
            if (!(0 <= theOperand1 && theOperand1 < 65536))
                throw new IllegalArgumentException("out of range index");
            if (!(0 <= theOperand2 && theOperand2 < 256))
                throw new IllegalArgumentException("out of range dimensions");

            addToCodeBuffer(ByteCode.MULTIANEWARRAY);
            addToCodeInt16(theOperand1);
            addToCodeBuffer(theOperand2);
        }
        else {
            throw new IllegalArgumentException(
                "Unexpected opcode for 2 operands");
        }
        itsStackTop = (short)newStack;
        if (newStack > itsMaxStack) itsMaxStack = (short)newStack;
        if (DEBUGSTACK) {
            System.out.println("After "+bytecodeStr(theOpCode)
                               +" stack = "+itsStackTop);
        }

    }

    public void add(int theOpCode, String className) {
        if (DEBUGCODE) {
            System.out.println("Add "+bytecodeStr(theOpCode)
                               +", "+className);
        }
        int newStack = itsStackTop + stackChange(theOpCode);
        if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack);
        switch (theOpCode) {
            case ByteCode.NEW :
            case ByteCode.ANEWARRAY :
            case ByteCode.CHECKCAST :
            case ByteCode.INSTANCEOF : {
                short classIndex = itsConstantPool.addClass(className);
                addToCodeBuffer(theOpCode);
                addToCodeInt16(classIndex);
            }
            break;

            default :
                throw new IllegalArgumentException(
                    "bad opcode for class reference");
        }
        itsStackTop = (short)newStack;
        if (newStack > itsMaxStack) itsMaxStack = (short)newStack;
        if (DEBUGSTACK) {
            System.out.println("After "+bytecodeStr(theOpCode)
                               +" stack = "+itsStackTop);
        }
    }


    public void add(int theOpCode, String className, String fieldName,
                    String fieldType)
    {
        if (DEBUGCODE) {
            System.out.println("Add "+bytecodeStr(theOpCode)
                               +", "+className+", "+fieldName+", "+fieldType);
        }
        int newStack = itsStackTop + stackChange(theOpCode);
        char fieldTypeChar = fieldType.charAt(0);
        int fieldSize = (fieldTypeChar == 'J' || fieldTypeChar == 'D')
                        ? 2 : 1;
        switch (theOpCode) {
            case ByteCode.GETFIELD :
            case ByteCode.GETSTATIC :
                newStack += fieldSize;
                break;
            case ByteCode.PUTSTATIC :
            case ByteCode.PUTFIELD :
                newStack -= fieldSize;
                break;
            default :
                throw new IllegalArgumentException(
                    "bad opcode for field reference");
        }
        if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack);
        short fieldRefIndex = itsConstantPool.addFieldRef(className,
                                             fieldName, fieldType);
        addToCodeBuffer(theOpCode);
        addToCodeInt16(fieldRefIndex);

        itsStackTop = (short)newStack;
        if (newStack > itsMaxStack) itsMaxStack = (short)newStack;
        if (DEBUGSTACK) {
            System.out.println("After "+bytecodeStr(theOpCode)
                               +" stack = "+itsStackTop);
        }
    }

    public void addInvoke(int theOpCode, String className, String methodName,
                          String methodType)
    {
        if (DEBUGCODE) {
            System.out.println("Add "+bytecodeStr(theOpCode)
                               +", "+className+", "+methodName+", "
                               +methodType);
        }
        int parameterInfo = sizeOfParameters(methodType);
        int parameterCount = parameterInfo >>> 16;
        int stackDiff = (short)parameterInfo;

        int newStack = itsStackTop + stackDiff;
        newStack += stackChange(theOpCode);     // adjusts for 'this'
        if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack);

        switch (theOpCode) {
            case ByteCode.INVOKEVIRTUAL :
            case ByteCode.INVOKESPECIAL :
            case ByteCode.INVOKESTATIC :
            case ByteCode.INVOKEINTERFACE : {
                    addToCodeBuffer(theOpCode);
                    if (theOpCode == ByteCode.INVOKEINTERFACE) {
                        short ifMethodRefIndex
                                    = itsConstantPool.addInterfaceMethodRef(
                                               className, methodName,
                                               methodType);
                        addToCodeInt16(ifMethodRefIndex);
                        addToCodeBuffer(parameterCount + 1);
                        addToCodeBuffer(0);
                    }
                    else {
                        short methodRefIndex = itsConstantPool.addMethodRef(
                                               className, methodName,
                                               methodType);
                        addToCodeInt16(methodRefIndex);
                    }
                }
                break;

            default :
                throw new IllegalArgumentException(
                    "bad opcode for method reference");
        }
        itsStackTop = (short)newStack;
        if (newStack > itsMaxStack) itsMaxStack = (short)newStack;
        if (DEBUGSTACK) {
            System.out.println("After "+bytecodeStr(theOpCode)
                               +" stack = "+itsStackTop);
        }
    }

    /**
     * Generate code to load the given integer on stack.
     *
     * @param k the constant
     */
    public void addPush(int k)
    {
        if ((byte)k == k) {
            if (k == -1) {
                add(ByteCode.ICONST_M1);
            } else if (0 <= k && k <= 5) {
                add((byte)(ByteCode.ICONST_0 + k));
            } else {
                add(ByteCode.BIPUSH, (byte)k);
            }
        } else if ((short)k == k) {
            add(ByteCode.SIPUSH, (short)k);
        } else {
            addLoadConstant(k);
        }
    }

    public void addPush(boolean k)
    {
        add(k ? ByteCode.ICONST_1 : ByteCode.ICONST_0);
    }

    /**
     * Generate code to load the given long on stack.
     *
     * @param k the constant
     */
    public void addPush(long k)
    {
        int ik = (int)k;
        if (ik == k) {
            addPush(ik);
            add(ByteCode.I2L);
        } else {
            addLoadConstant(k);
        }
    }

    /**
     * Generate code to load the given double on stack.
     *
     * @param k the constant
     */
    public void addPush(double k)
    {
        if (k == 0.0) {
            // zero
            add(ByteCode.DCONST_0);
            if (1.0 / k < 0) {
                // Negative zero
                add(ByteCode.DNEG);
            }
        } else if (k == 1.0 || k == -1.0) {
            add(ByteCode.DCONST_1);
            if (k < 0) {
                add(ByteCode.DNEG);
            }
        } else {
            addLoadConstant(k);
        }
    }

    /**
     * Generate the code to leave on stack the given string even if the
     * string encoding exeeds the class file limit for single string constant
     *
     * @param k the constant
     */
    public void addPush(String k) {
        int length = k.length();
        int limit = itsConstantPool.getUtfEncodingLimit(k, 0, length);
        if (limit == length) {
            addLoadConstant(k);
            return;
        }
        // Split string into picies fitting the UTF limit and generate code for
        // StringBuilder sb = new StringBuilder(length);
        // sb.append(loadConstant(piece_1));
        // ...
        // sb.append(loadConstant(piece_N));
        // sb.toString();
        final String SB = "java/lang/StringBuilder";
        add(ByteCode.NEW, SB);
        add(ByteCode.DUP);
        addPush(length);
        addInvoke(ByteCode.INVOKESPECIAL, SB, "<init>", "(I)V");
        int cursor = 0;
        for (;;) {
            add(ByteCode.DUP);
            String s = k.substring(cursor, limit);
            addLoadConstant(s);
            addInvoke(ByteCode.INVOKEVIRTUAL, SB, "append",
                      "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
            add(ByteCode.POP);
            if (limit == length) {
                break;
            }
            cursor = limit;
            limit = itsConstantPool.getUtfEncodingLimit(k, limit, length);
        }
        addInvoke(ByteCode.INVOKEVIRTUAL, SB, "toString",
                  "()Ljava/lang/String;");
    }

    /**
     * Check if k fits limit on string constant size imposed by class file
     * format.
     *
     * @param k the string constant
     */
    public boolean isUnderStringSizeLimit(String k)
    {
        return itsConstantPool.isUnderUtfEncodingLimit(k);
    }

    /**
     * Store integer from stack top into the given local.
     *
     * @param local number of local register
     */
    public void addIStore(int local)
    {
        xop(ByteCode.ISTORE_0, ByteCode.ISTORE, local);
    }

    /**
     * Store long from stack top into the given local.
     *
     * @param local number of local register
     */
    public void addLStore(int local)
    {
        xop(ByteCode.LSTORE_0, ByteCode.LSTORE, local);
    }

    /**
     * Store float from stack top into the given local.
     *
     * @param local number of local register
     */
    public void addFStore(int local)
    {
        xop(ByteCode.FSTORE_0, ByteCode.FSTORE, local);
    }

    /**
     * Store double from stack top into the given local.
     *
     * @param local number of local register
     */
    public void addDStore(int local)
    {
        xop(ByteCode.DSTORE_0, ByteCode.DSTORE, local);
    }

    /**
     * Store object from stack top into the given local.
     *
     * @param local number of local register
     */
    public void addAStore(int local)
    {
        xop(ByteCode.ASTORE_0, ByteCode.ASTORE, local);
    }

    /**
     * Load integer from the given local into stack.
     *
     * @param local number of local register
     */
    public void addILoad(int local)
    {
        xop(ByteCode.ILOAD_0, ByteCode.ILOAD, local);
    }

    /**
     * Load long from the given local into stack.
     *
     * @param local number of local register
     */
    public void addLLoad(int local)
    {
        xop(ByteCode.LLOAD_0, ByteCode.LLOAD, local);
    }

    /**
     * Load float from the given local into stack.
     *
     * @param local number of local register
     */
    public void addFLoad(int local)
    {
        xop(ByteCode.FLOAD_0, ByteCode.FLOAD, local);
    }

    /**
     * Load double from the given local into stack.
     *
     * @param local number of local register
     */
    public void addDLoad(int local)
    {
        xop(ByteCode.DLOAD_0, ByteCode.DLOAD, local);
    }

    /**
     * Load object from the given local into stack.
     *
     * @param local number of local register
     */
    public void addALoad(int local)
    {
        xop(ByteCode.ALOAD_0, ByteCode.ALOAD, local);
    }

    /**
     * Load "this" into stack.
     */
    public void addLoadThis()
    {
        add(ByteCode.ALOAD_0);
    }

    private void xop(int shortOp, int op, int local)
    {
        switch (local) {
          case 0:
            add(shortOp);
            break;
          case 1:
            add(shortOp + 1);
            break;
          case 2:
            add(shortOp + 2);
            break;
          case 3:
            add(shortOp + 3);
            break;
          default:
            add(op, local);
        }
    }

    public int addTableSwitch(int low, int high)
    {
        if (DEBUGCODE) {
            System.out.println("Add "+bytecodeStr(ByteCode.TABLESWITCH)
                               +" "+low+" "+high);
        }
        if (low > high)
            throw new ClassFileFormatException("Bad bounds: "+low+' '+ high);

        int newStack = itsStackTop + stackChange(ByteCode.TABLESWITCH);
        if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack);

        int entryCount = high - low + 1;
        int padSize = 3 & ~itsCodeBufferTop; // == 3 - itsCodeBufferTop % 4

        int N = addReservedCodeSpace(1 + padSize + 4 * (1 + 2 + entryCount));
        int switchStart = N;
        itsCodeBuffer[N++] = (byte)ByteCode.TABLESWITCH;
        while (padSize != 0) {
            itsCodeBuffer[N++] = 0;
            --padSize;
        }
        N += 4; // skip default offset
        N = putInt32(low, itsCodeBuffer, N);
        putInt32(high, itsCodeBuffer, N);

        itsStackTop = (short)newStack;
        if (newStack > itsMaxStack) itsMaxStack = (short)newStack;
        if (DEBUGSTACK) {
            System.out.println("After "+bytecodeStr(ByteCode.TABLESWITCH)
                               +" stack = "+itsStackTop);
        }

        return switchStart;
    }

    public final void markTableSwitchDefault(int switchStart)
    {
        addSuperBlockStart(itsCodeBufferTop);
        itsJumpFroms.put(itsCodeBufferTop, switchStart);
        setTableSwitchJump(switchStart, -1, itsCodeBufferTop);
    }

    public final void markTableSwitchCase(int switchStart, int caseIndex)
    {
        addSuperBlockStart(itsCodeBufferTop);
        itsJumpFroms.put(itsCodeBufferTop, switchStart);
        setTableSwitchJump(switchStart, caseIndex, itsCodeBufferTop);
    }

    public final void markTableSwitchCase(int switchStart, int caseIndex,
                                          int stackTop)
    {
        if (!(0 <= stackTop && stackTop <= itsMaxStack))
            throw new IllegalArgumentException("Bad stack index: "+stackTop);
        itsStackTop = (short)stackTop;
        addSuperBlockStart(itsCodeBufferTop);
        itsJumpFroms.put(itsCodeBufferTop, switchStart);
        setTableSwitchJump(switchStart, caseIndex, itsCodeBufferTop);
    }

    /**
     * Set a jump case for a tableswitch instruction. The jump target should
     * be marked as a super block start for stack map generation.
     */
    public void setTableSwitchJump(int switchStart, int caseIndex,
                                   int jumpTarget)
    {
        if (!(0 <= jumpTarget && jumpTarget <= itsCodeBufferTop))
            throw new IllegalArgumentException("Bad jump target: "+jumpTarget);
        if (!(caseIndex >= -1))
            throw new IllegalArgumentException("Bad case index: "+caseIndex);

        int padSize = 3 & ~switchStart; // == 3 - switchStart % 4
        int caseOffset;
        if (caseIndex < 0) {
            // default label
            caseOffset = switchStart + 1 + padSize;
        } else {
            caseOffset = switchStart + 1 + padSize + 4 * (3 + caseIndex);
        }
        if (!(0 <= switchStart
              && switchStart <= itsCodeBufferTop - 4 * 4 - padSize - 1))
        {
            throw new IllegalArgumentException(
                switchStart+" is outside a possible range of tableswitch"
                +" in already generated code");
        }
        if ((0xFF & itsCodeBuffer[switchStart]) != ByteCode.TABLESWITCH) {
            throw new IllegalArgumentException(
                switchStart+" is not offset of tableswitch statement");
        }
        if (!(0 <= caseOffset && caseOffset + 4 <= itsCodeBufferTop)) {
            // caseIndex >= -1 does not guarantee that caseOffset >= 0 due
            // to a possible overflow.
            throw new ClassFileFormatException(
                "Too big case index: "+caseIndex);
        }
        // ALERT: perhaps check against case bounds?
        putInt32(jumpTarget - switchStart, itsCodeBuffer, caseOffset);
    }

    public int acquireLabel()
    {
        int top = itsLabelTableTop;
        if (itsLabelTable == null || top == itsLabelTable.length) {
            if (itsLabelTable == null) {
                itsLabelTable = new int[MIN_LABEL_TABLE_SIZE];
            }else {
                int[] tmp = new int[itsLabelTable.length * 2];
                System.arraycopy(itsLabelTable, 0, tmp, 0, top);
                itsLabelTable = tmp;
            }
        }
        itsLabelTableTop = top + 1;
        itsLabelTable[top] = -1;
        return top | 0x80000000;
    }

    public void markLabel(int label)
    {
        if (!(label < 0))
            throw new IllegalArgumentException("Bad label, no biscuit");

        label &= 0x7FFFFFFF;
        if (label > itsLabelTableTop)
            throw new IllegalArgumentException("Bad label");

        if (itsLabelTable[label] != -1) {
            throw new IllegalStateException("Can only mark label once");
        }

        itsLabelTable[label] = itsCodeBufferTop;
    }

    public void markLabel(int label, short stackTop)
    {
        markLabel(label);
        itsStackTop = stackTop;
    }

    public void markHandler(int theLabel) {
        itsStackTop = 1;
        markLabel(theLabel);
    }

    public int getLabelPC(int label)
    {
        if (!(label < 0))
            throw new IllegalArgumentException("Bad label, no biscuit");
        label &= 0x7FFFFFFF;
        if (!(label < itsLabelTableTop))
            throw new IllegalArgumentException("Bad label");
        return itsLabelTable[label];
    }

    private void addLabelFixup(int label, int fixupSite)
    {
        if (!(label < 0))
            throw new IllegalArgumentException("Bad label, no biscuit");
        label &= 0x7FFFFFFF;
        if (!(label < itsLabelTableTop))
            throw new IllegalArgumentException("Bad label");
        int top = itsFixupTableTop;
        if (itsFixupTable == null || top == itsFixupTable.length) {
            if (itsFixupTable == null) {
                itsFixupTable = new long[MIN_FIXUP_TABLE_SIZE];
            }else {
                long[] tmp = new long[itsFixupTable.length * 2];
                System.arraycopy(itsFixupTable, 0, tmp, 0, top);
                itsFixupTable = tmp;
            }
        }
        itsFixupTableTop = top + 1;
        itsFixupTable[top] = ((long)label << 32) | fixupSite;
    }

    private  void fixLabelGotos()
    {
        byte[] codeBuffer = itsCodeBuffer;
        for (int i = 0; i < itsFixupTableTop; i++) {
            long fixup = itsFixupTable[i];
            int label = (int)(fixup >> 32);
            int fixupSite = (int)fixup;
            int pc = itsLabelTable[label];
            if (pc == -1) {
                // Unlocated label
                throw new RuntimeException();
            }
            // -1 to get delta from instruction start
            addSuperBlockStart(pc);
            itsJumpFroms.put(pc, fixupSite - 1);
            int offset = pc - (fixupSite - 1);
            if ((short)offset != offset) {
                throw new ClassFileFormatException
                    ("Program too complex: too big jump offset");
            }
            codeBuffer[fixupSite] = (byte)(offset >> 8);
            codeBuffer[fixupSite + 1] = (byte)offset;
        }
        itsFixupTableTop = 0;
    }

    /**
     * Get the current offset into the code of the current method.
     *
     * @return an integer representing the offset
     */
    public int getCurrentCodeOffset() {
        return itsCodeBufferTop;
    }

    public short getStackTop() {
        return itsStackTop;
    }

    public void setStackTop(short n) {
        itsStackTop = n;
    }

    public void adjustStackTop(int delta) {
        int newStack = itsStackTop + delta;
        if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack);
        itsStackTop = (short)newStack;
        if (newStack > itsMaxStack) itsMaxStack = (short)newStack;
        if (DEBUGSTACK) {
            System.out.println("After "+"adjustStackTop("+delta+")"
                               +" stack = "+itsStackTop);
        }
    }

    private void addToCodeBuffer(int b)
    {
        int N = addReservedCodeSpace(1);
        itsCodeBuffer[N] = (byte)b;
    }

    private void addToCodeInt16(int value)
    {
        int N = addReservedCodeSpace(2);
        putInt16(value, itsCodeBuffer, N);
    }

    private int addReservedCodeSpace(int size)
    {
        if (itsCurrentMethod == null)
            throw new IllegalArgumentException("No method to add to");
        int oldTop = itsCodeBufferTop;
        int newTop = oldTop + size;
        if (newTop > itsCodeBuffer.length) {
            int newSize = itsCodeBuffer.length * 2;
            if (newTop > newSize) { newSize = newTop; }
            byte[] tmp = new byte[newSize];
            System.arraycopy(itsCodeBuffer, 0, tmp, 0, oldTop);
            itsCodeBuffer = tmp;
        }
        itsCodeBufferTop = newTop;
        return oldTop;
    }

    public void addExceptionHandler(int startLabel, int endLabel,
                                    int handlerLabel, String catchClassName)
    {
        if ((startLabel & 0x80000000) != 0x80000000)
            throw new IllegalArgumentException("Bad startLabel");
        if ((endLabel & 0x80000000) != 0x80000000)
            throw new IllegalArgumentException("Bad endLabel");
        if ((handlerLabel & 0x80000000) != 0x80000000)
            throw new IllegalArgumentException("Bad handlerLabel");

        /*
         * If catchClassName is null, use 0 for the catch_type_index; which
         * means catch everything.  (Even when the verifier has let you throw
         * something other than a Throwable.)
         */
        short catch_type_index = (catchClassName == null)
                                 ? 0
                                 : itsConstantPool.addClass(catchClassName);
        ExceptionTableEntry newEntry = new ExceptionTableEntry(
                                           startLabel,
                                           endLabel,
                                           handlerLabel,
                                           catch_type_index);
        int N = itsExceptionTableTop;
        if (N == 0) {
            itsExceptionTable = new ExceptionTableEntry[ExceptionTableSize];
        } else if (N == itsExceptionTable.length) {
            ExceptionTableEntry[] tmp = new ExceptionTableEntry[N * 2];
            System.arraycopy(itsExceptionTable, 0, tmp, 0, N);
            itsExceptionTable = tmp;
        }
        itsExceptionTable[N] = newEntry;
        itsExceptionTableTop = N + 1;

    }

    public void addLineNumberEntry(short lineNumber) {
        if (itsCurrentMethod == null)
            throw new IllegalArgumentException("No method to stop");
        int N = itsLineNumberTableTop;
        if (N == 0) {
            itsLineNumberTable = new int[LineNumberTableSize];
        } else if (N == itsLineNumberTable.length) {
            int[] tmp = new int[N * 2];
            System.arraycopy(itsLineNumberTable, 0, tmp, 0, N);
            itsLineNumberTable = tmp;
        }
        itsLineNumberTable[N] = (itsCodeBufferTop << 16) + lineNumber;
        itsLineNumberTableTop = N + 1;
    }

    /**
     * A stack map table is a code attribute introduced in Java 6 that
     * gives type information at key points in the method body (namely, at
     * the beginning of each super block after the first). Each frame of a
     * stack map table contains the state of local variable and operand stack
     * for a given super block.
     */
    final class StackMapTable {
        StackMapTable() {
            superBlocks = null;
            locals = stack = null;
            workList = null;
            rawStackMap = null;
            localsTop = 0;
            stackTop = 0;
            workListTop = 0;
            rawStackMapTop = 0;
            wide = false;
        }

        void generate() {
            superBlocks = new SuperBlock[itsSuperBlockStartsTop];
            int[] initialLocals = createInitialLocals();

            for (int i = 0; i < itsSuperBlockStartsTop; i++) {
                int start = itsSuperBlockStarts[i];
                int end;
                if (i == itsSuperBlockStartsTop - 1) {
                    end = itsCodeBufferTop;
                } else {
                    end = itsSuperBlockStarts[i + 1];
                }
                superBlocks[i] = new SuperBlock(i, start, end, initialLocals);
            }

            if (DEBUGSTACKMAP) {
                System.out.println("super blocks: ");
                for (int i = 0;
                     i < superBlocks.length && superBlocks[i] != null; i++) {
                    System.out.println("sb " + i + ": [" +
                                       superBlocks[i].getStart() + ", " +
                                       superBlocks[i].getEnd() + ")");
                }
            }

            superBlockDeps = getSuperBlockDependencies();

            verify();

            if (DEBUGSTACKMAP) {
                System.out.println("type information:");
                for (int i = 0; i < superBlocks.length; i++) {
                    SuperBlock sb = superBlocks[i];
                    System.out.println("sb " + i + ":");
                    TypeInfo.print(sb.getLocals(), sb.getStack(),
                                   itsConstantPool);
                }
            }
        }

        private SuperBlock getSuperBlockFromOffset(int offset) {
            for (int i = 0; i < superBlocks.length; i++) {
                SuperBlock sb = superBlocks[i];
                if (sb == null) {
                    break;
                } else if (offset >= sb.getStart() && offset < sb.getEnd()) {
                    return sb;
                }
            }
            throw new IllegalArgumentException("bad offset: " + offset);
        }

        /**
         * Determine whether or not an opcode is an actual end to a super
         * block. This includes any returns or unconditional jumps.
         */
        private boolean isSuperBlockEnd(int opcode) {
            switch (opcode) {
                case ByteCode.ARETURN:
                case ByteCode.FRETURN:
                case ByteCode.IRETURN:
                case ByteCode.LRETURN:
                case ByteCode.RETURN:
                case ByteCode.ATHROW:
                case ByteCode.GOTO:
                case ByteCode.GOTO_W:
                case ByteCode.TABLESWITCH:
                case ByteCode.LOOKUPSWITCH:
                    return true;
                default:
                    return false;
            }
        }

        /**
         * Calculate partial dependencies for super blocks.
         *
         * This is used as a workaround for dead code that is generated. Only
         * one dependency per super block is given.
         */
        private SuperBlock[] getSuperBlockDependencies() {
            SuperBlock[] deps = new SuperBlock[superBlocks.length];

            for (int i = 0; i < itsExceptionTableTop; i++) {
                ExceptionTableEntry ete = itsExceptionTable[i];
                short startPC = (short) getLabelPC(ete.itsStartLabel);
                short handlerPC = (short) getLabelPC(ete.itsHandlerLabel);
                SuperBlock handlerSB = getSuperBlockFromOffset(handlerPC);
                SuperBlock dep = getSuperBlockFromOffset(startPC);
                deps[handlerSB.getIndex()] = dep;
            }
            int[] targetPCs = itsJumpFroms.getKeys();
            for (int i = 0; i < targetPCs.length; i++) {
                int targetPC = targetPCs[i];
                int branchPC = itsJumpFroms.getInt(targetPC, -1);
                SuperBlock branchSB = getSuperBlockFromOffset(branchPC);
                SuperBlock targetSB = getSuperBlockFromOffset(targetPC);
                deps[targetSB.getIndex()] = branchSB;
            }

            return deps;
        }

        /**
         * Get the target super block of a branch instruction.
         *
         * @param bci the index of the branch instruction in the code buffer
         */
        private SuperBlock getBranchTarget(int bci) {
            int target;
            if ((itsCodeBuffer[bci] & 0xFF) == ByteCode.GOTO_W) {
                target = bci + getOperand(bci + 1, 4);
            } else {
                target = bci + (short) getOperand(bci + 1, 2);
            }
            return getSuperBlockFromOffset(target);
        }

        /**
         * Determine whether or not an opcode is a conditional or unconditional
         * jump.
         */
        private boolean isBranch(int opcode) {
            switch (opcode) {
                case ByteCode.GOTO:
                case ByteCode.GOTO_W:
                case ByteCode.IFEQ:
                case ByteCode.IFGE:
                case ByteCode.IFGT:
                case ByteCode.IFLE:
                case ByteCode.IFLT:
                case ByteCode.IFNE:
                case ByteCode.IFNONNULL:
                case ByteCode.IFNULL:
                case ByteCode.IF_ACMPEQ:
                case ByteCode.IF_ACMPNE:
                case ByteCode.IF_ICMPEQ:
                case ByteCode.IF_ICMPGE:
                case ByteCode.IF_ICMPGT:
                case ByteCode.IF_ICMPLE:
                case ByteCode.IF_ICMPLT:
                case ByteCode.IF_ICMPNE:
                    return true;
                default:
                    return false;
            }
        }

        private int getOperand(int offset) {
            return getOperand(offset, 1);
        }

        /**
         * Extract a logical operand from the byte code.
         *
         * This is used, for example, to get branch offsets.
         */
        private int getOperand(int start, int size) {
            int result = 0;
            if (size > 4) {
                throw new IllegalArgumentException("bad operand size");
            }
            for (int i = 0; i < size; i++) {
                result = (result << 8) | (itsCodeBuffer[start + i] & 0xFF);
            }
            return result;
        }

        /**
         * Calculate initial local variable and op stack types for each super
         * block in the method.
         */
        private void verify() {
            int[] initialLocals = createInitialLocals();
            superBlocks[0].merge(initialLocals, initialLocals.length,
                                 new int[0], 0, itsConstantPool);

            // Start from the top of the method and queue up block dependencies
            // as they come along.
            workList = new SuperBlock[] { superBlocks[0] };
            workListTop = 1;
            executeWorkList();

            // Replace dead code with no-ops.
            for (int i = 0; i < superBlocks.length; i++) {
                SuperBlock sb = superBlocks[i];
                if (!sb.isInitialized()) {
                    killSuperBlock(sb);
                }
            }
            executeWorkList();
        }

        /**
         * Replace the contents of a super block with no-ops.
         *
         * The above description is not strictly true; the last instruction is
         * an athrow instruction. This technique is borrowed from ASM's
         * developer guide: http://asm.ow2.org/doc/developer-guide.html#deadcode
         *
         * The proposed algorithm fills a block with nop, ending it with an
         * athrow. The stack map generated would be empty locals with an
         * exception on the stack. In theory, it shouldn't matter what the
         * locals are, as long as the stack has an exception for the athrow bit.
         * However, it turns out that if the code being modified falls into an
         * exception handler, it causes problems. Therefore, if it does, then
         * we steal the locals from the exception block.
         *
         * If the block itself is an exception handler, we remove it from the
         * exception table to simplify block dependencies.
         */
        private void killSuperBlock(SuperBlock sb) {
            int[] locals = new int[0];
            int[] stack = new int[] { TypeInfo.OBJECT("java/lang/Throwable",
                                                      itsConstantPool) };

            // If the super block is handled by any exception handler, use its
            // locals as the killed block's locals. Ignore uninitialized
            // handlers, because they will also be killed and removed from the
            // exception table.
            for (int i = 0; i < itsExceptionTableTop; i++) {
                ExceptionTableEntry ete = itsExceptionTable[i];
                int eteStart = getLabelPC(ete.itsStartLabel);
                int eteEnd = getLabelPC(ete.itsEndLabel);
                int handlerPC = getLabelPC(ete.itsHandlerLabel);
                SuperBlock handlerSB = getSuperBlockFromOffset(handlerPC);
                if ((sb.getStart() > eteStart && sb.getStart() < eteEnd) ||
                    (eteStart > sb.getStart() && eteStart < sb.getEnd()) &&
                    handlerSB.isInitialized()) {
                    locals = handlerSB.getLocals();
                    break;
                }
            }

            // Remove any exception table entry whose handler is the killed
            // block. This removes block dependencies to make stack maps for
            // dead blocks easier to create.
            for (int i = 0; i < itsExceptionTableTop; i++) {
                ExceptionTableEntry ete = itsExceptionTable[i];
                int eteStart = getLabelPC(ete.itsStartLabel);
                if (eteStart == sb.getStart()) {
                    for (int j = i + 1; j < itsExceptionTableTop; j++) {
                        itsExceptionTable[j - 1] = itsExceptionTable[j];
                    }
                    itsExceptionTableTop--;
                    i--;
                }
            }

            sb.merge(locals, locals.length, stack, stack.length,
                     itsConstantPool);

            int end = sb.getEnd() - 1;
            itsCodeBuffer[end] = (byte) ByteCode.ATHROW;
            for (int bci = sb.getStart(); bci < end; bci++) {
                itsCodeBuffer[bci] = (byte) ByteCode.NOP;
            }
        }

        private void executeWorkList() {
            while (workListTop > 0) {
                SuperBlock work = workList[--workListTop];
                work.setInQueue(false);
                locals = work.getLocals();
                stack = work.getStack();
                localsTop = locals.length;
                stackTop = stack.length;
                executeBlock(work);
            }
        }

        /**
         * Simulate the local variable and op stack for a super block.
         */
        private void executeBlock(SuperBlock work) {
            int bc = 0;
            int next = 0;

            if (DEBUGSTACKMAP) {
                System.out.println("working on sb " + work.getIndex());
                System.out.println("initial type state:");
                TypeInfo.print(locals, localsTop, stack, stackTop,
                               itsConstantPool);
            }

            for (int bci = work.getStart(); bci < work.getEnd(); bci += next) {
                bc = itsCodeBuffer[bci] & 0xFF;
                next = execute(bci);

                // If we have a branch to some super block, we need to merge
                // the current state of the local table and op stack with what's
                // currently stored as the initial state of the super block. If
                // something actually changed, we need to add it to the work
                // list.
                if (isBranch(bc)) {
                    SuperBlock targetSB = getBranchTarget(bci);
                    if (DEBUGSTACKMAP) {
                        System.out.println("sb " + work.getIndex() +
                                           " points to sb " +
                                           targetSB.getIndex() +
                                           " (offset " + bci + " -> " +
                                           targetSB.getStart() + ")");
                        System.out.println("type state at " + bci + ":");
                        TypeInfo.print(locals, localsTop, stack, stackTop,
                                       itsConstantPool);
                    }
                    flowInto(targetSB);
                    if (DEBUGSTACKMAP) {
                        System.out.println("type state of " + targetSB +
                                           " after merge:");
                        TypeInfo.print(targetSB.getLocals(),
                                       targetSB.getStack(), itsConstantPool);
                    }
                } else if (bc == ByteCode.TABLESWITCH) {
                    int switchStart = bci + 1 + (3 & ~bci); // 3 - bci % 4
                    int defaultOffset = getOperand(switchStart, 4);
                    SuperBlock targetSB =
                            getSuperBlockFromOffset(bci + defaultOffset);
                    if (DEBUGSTACK) {
                        System.out.println("merging sb " + work.getIndex() +
                                           " with sb " + targetSB.getIndex());
                    }
                    flowInto(targetSB);
                    int low = getOperand(switchStart + 4, 4);
                    int high = getOperand(switchStart + 8, 4);
                    int numCases = high - low + 1;
                    int caseBase = switchStart + 12;
                    for (int i = 0; i < numCases; i++) {
                        int label = bci + getOperand(caseBase + 4 * i, 4);
                        targetSB = getSuperBlockFromOffset(label);
                        if (DEBUGSTACKMAP) {
                            System.out.println("merging sb " +
                                               work.getIndex() + " with sb " +
                                               targetSB.getIndex());
                        }
                        flowInto(targetSB);
                    }
                }

                for (int i = 0; i < itsExceptionTableTop; i++) {
                    ExceptionTableEntry ete = itsExceptionTable[i];
                    short startPC = (short) getLabelPC(ete.itsStartLabel);
                    short endPC = (short) getLabelPC(ete.itsEndLabel);
                    if (bci < startPC || bci >= endPC) {
                        continue;
                    }
                    short handlerPC =
                            (short) getLabelPC(ete.itsHandlerLabel);
                    SuperBlock sb = getSuperBlockFromOffset(handlerPC);
                    int exceptionType;

                    if (ete.itsCatchType == 0) {
                        exceptionType = TypeInfo.OBJECT(
                            itsConstantPool.addClass("java/lang/Throwable"));
                    } else {
                        exceptionType = TypeInfo.OBJECT(ete.itsCatchType);
                    }
                    sb.merge(locals, localsTop, new int[] { exceptionType }, 1,
                             itsConstantPool);
                    addToWorkList(sb);
                }
            }

            if (DEBUGSTACKMAP) {
                System.out.println("end of sb " + work.getIndex() + ":");
                TypeInfo.print(locals, localsTop, stack, stackTop,
                               itsConstantPool);
            }

            // Check the last instruction to see if it is a true end of a
            // super block (ie., if the instruction is a return). If it
            // isn't, we need to continue processing the next chunk.
            if (!isSuperBlockEnd(bc)) {
                int nextIndex = work.getIndex() + 1;
                if (nextIndex < superBlocks.length) {
                    if (DEBUGSTACKMAP) {
                        System.out.println("continuing from sb " +
                                           work.getIndex() + " into sb " +
                                           nextIndex);
                    }
                    flowInto(superBlocks[nextIndex]);
                }
            }
        }

        /**
         * Perform a merge of type state and add the super block to the work
         * list if the merge changed anything.
         */
        private void flowInto(SuperBlock sb) {
            if (sb.merge(locals, localsTop, stack, stackTop, itsConstantPool)) {
                addToWorkList(sb);
            }
        }

        private void addToWorkList(SuperBlock sb) {
            if (!sb.isInQueue()) {
                sb.setInQueue(true);
                sb.setInitialized(true);
                if (workListTop == workList.length) {
                    SuperBlock[] tmp = new SuperBlock[workListTop * 2];
                    System.arraycopy(workList, 0, tmp, 0, workListTop);
                    workList = tmp;
                }
                workList[workListTop++] = sb;
            }
        }

        /**
         * Execute a single byte code instruction.
         *
         * @param bci the index of the byte code instruction to execute
         * @return the length of the byte code instruction
         */
        private int execute(int bci) {
            int bc = itsCodeBuffer[bci] & 0xFF;
            int type, type2, index;
            int length = 0;
            long lType, lType2;
            String className;

            switch (bc) {
                case ByteCode.NOP:
                case ByteCode.IINC:
                case ByteCode.GOTO:
                case ByteCode.GOTO_W:
                    // No change
                    break;
                case ByteCode.CHECKCAST:
                    pop();
                    push(TypeInfo.OBJECT(getOperand(bci + 1, 2)));
                    break;
                case ByteCode.IASTORE: // pop; pop; pop
                case ByteCode.LASTORE:
                case ByteCode.FASTORE:
                case ByteCode.DASTORE:
                case ByteCode.AASTORE:
                case ByteCode.BASTORE:
                case ByteCode.CASTORE:
                case ByteCode.SASTORE:
                    pop();
                case ByteCode.PUTFIELD: // pop; pop
                case ByteCode.IF_ICMPEQ:
                case ByteCode.IF_ICMPNE:
                case ByteCode.IF_ICMPLT:
                case ByteCode.IF_ICMPGE:
                case ByteCode.IF_ICMPGT:
                case ByteCode.IF_ICMPLE:
                case ByteCode.IF_ACMPEQ:
                case ByteCode.IF_ACMPNE:
                    pop();
                case ByteCode.IFEQ: // pop
                case ByteCode.IFNE:
                case ByteCode.IFLT:
                case ByteCode.IFGE:
                case ByteCode.IFGT:
                case ByteCode.IFLE:
                case ByteCode.IFNULL:
                case ByteCode.IFNONNULL:
                case ByteCode.POP:
                case ByteCode.MONITORENTER:
                case ByteCode.MONITOREXIT:
                case ByteCode.PUTSTATIC:
                    pop();
                    break;
                case ByteCode.POP2:
                    pop2();
                    break;
                case ByteCode.ACONST_NULL:
                    push(TypeInfo.NULL);
                    break;
                case ByteCode.IALOAD: // pop; pop; push(INTEGER)
                case ByteCode.BALOAD:
                case ByteCode.CALOAD:
                case ByteCode.SALOAD:
                case ByteCode.IADD:
                case ByteCode.ISUB:
                case ByteCode.IMUL:
                case ByteCode.IDIV:
                case ByteCode.IREM:
                case ByteCode.ISHL:
                case ByteCode.ISHR:
                case ByteCode.IUSHR:
                case ByteCode.IAND:
                case ByteCode.IOR:
                case ByteCode.IXOR:
                case ByteCode.LCMP:
                case ByteCode.FCMPL:
                case ByteCode.FCMPG:
                case ByteCode.DCMPL:
                case ByteCode.DCMPG:
                    pop();
                case ByteCode.INEG: // pop; push(INTEGER)
                case ByteCode.L2I:
                case ByteCode.F2I:
                case ByteCode.D2I:
                case ByteCode.I2B:
                case ByteCode.I2C:
                case ByteCode.I2S:
                case ByteCode.ARRAYLENGTH:
                case ByteCode.INSTANCEOF:
                    pop();
                case ByteCode.ICONST_M1: // push(INTEGER)
                case ByteCode.ICONST_0:
                case ByteCode.ICONST_1:
                case ByteCode.ICONST_2:
                case ByteCode.ICONST_3:
                case ByteCode.ICONST_4:
                case ByteCode.ICONST_5:
                case ByteCode.ILOAD:
                case ByteCode.ILOAD_0:
                case ByteCode.ILOAD_1:
                case ByteCode.ILOAD_2:
                case ByteCode.ILOAD_3:
                case ByteCode.BIPUSH:
                case ByteCode.SIPUSH:
                    push(TypeInfo.INTEGER);
                    break;
                case ByteCode.LALOAD: // pop; pop; push(LONG)
                case ByteCode.LADD:
                case ByteCode.LSUB:
                case ByteCode.LMUL:
                case ByteCode.LDIV:
                case ByteCode.LREM:
                case ByteCode.LSHL:
                case ByteCode.LSHR:
                case ByteCode.LUSHR:
                case ByteCode.LAND:
                case ByteCode.LOR:
                case ByteCode.LXOR:
                    pop();
                case ByteCode.LNEG: // pop; push(LONG)
                case ByteCode.I2L:
                case ByteCode.F2L:
                case ByteCode.D2L:
                    pop();
                case ByteCode.LCONST_0: // push(LONG)
                case ByteCode.LCONST_1:
                case ByteCode.LLOAD:
                case ByteCode.LLOAD_0:
                case ByteCode.LLOAD_1:
                case ByteCode.LLOAD_2:
                case ByteCode.LLOAD_3:
                    push(TypeInfo.LONG);
                    break;
                case ByteCode.FALOAD: // pop; pop; push(FLOAT)
                case ByteCode.FADD:
                case ByteCode.FSUB:
                case ByteCode.FMUL:
                case ByteCode.FDIV:
                case ByteCode.FREM:
                    pop();
                case ByteCode.FNEG: // pop; push(FLOAT)
                case ByteCode.I2F:
                case ByteCode.L2F:
                case ByteCode.D2F:
                    pop();
                case ByteCode.FCONST_0: // push(FLOAT)
                case ByteCode.FCONST_1:
                case ByteCode.FCONST_2:
                case ByteCode.FLOAD:
                case ByteCode.FLOAD_0:
                case ByteCode.FLOAD_1:
                case ByteCode.FLOAD_2:
                case ByteCode.FLOAD_3:
                    push(TypeInfo.FLOAT);
                    break;
                case ByteCode.DALOAD: // pop; pop; push(DOUBLE)
                case ByteCode.DADD:
                case ByteCode.DSUB:
                case ByteCode.DMUL:
                case ByteCode.DDIV:
                case ByteCode.DREM:
                    pop();
                case ByteCode.DNEG: // pop; push(DOUBLE)
                case ByteCode.I2D:
                case ByteCode.L2D:
                case ByteCode.F2D:
                    pop();
                case ByteCode.DCONST_0: // push(DOUBLE)
                case ByteCode.DCONST_1:
                case ByteCode.DLOAD:
                case ByteCode.DLOAD_0:
                case ByteCode.DLOAD_1:
                case ByteCode.DLOAD_2:
                case ByteCode.DLOAD_3:
                    push(TypeInfo.DOUBLE);
                    break;
                case ByteCode.ISTORE:
                    executeStore(getOperand(bci + 1, wide ? 2 : 1), TypeInfo.INTEGER);
                    break;
                case ByteCode.ISTORE_0:
                case ByteCode.ISTORE_1:
                case ByteCode.ISTORE_2:
                case ByteCode.ISTORE_3:
                    executeStore(bc - ByteCode.ISTORE_0, TypeInfo.INTEGER);
                    break;
                case ByteCode.LSTORE:
                    executeStore(getOperand(bci + 1, wide ? 2 : 1), TypeInfo.LONG);
                    break;
                case ByteCode.LSTORE_0:
                case ByteCode.LSTORE_1:
                case ByteCode.LSTORE_2:
                case ByteCode.LSTORE_3:
                    executeStore(bc - ByteCode.LSTORE_0, TypeInfo.LONG);
                    break;
                case ByteCode.FSTORE:
                    executeStore(getOperand(bci + 1, wide ? 2 : 1), TypeInfo.FLOAT);
                    break;
                case ByteCode.FSTORE_0:
                case ByteCode.FSTORE_1:
                case ByteCode.FSTORE_2:
                case ByteCode.FSTORE_3:
                    executeStore(bc - ByteCode.FSTORE_0, TypeInfo.FLOAT);
                    break;
                case ByteCode.DSTORE:
                    executeStore(getOperand(bci + 1, wide ? 2 : 1), TypeInfo.DOUBLE);
                    break;
                case ByteCode.DSTORE_0:
                case ByteCode.DSTORE_1:
                case ByteCode.DSTORE_2:
                case ByteCode.DSTORE_3:
                    executeStore(bc - ByteCode.DSTORE_0, TypeInfo.DOUBLE);
                    break;
                case ByteCode.ALOAD:
                    executeALoad(getOperand(bci + 1, wide ? 2 : 1));
                    break;
                case ByteCode.ALOAD_0:
                case ByteCode.ALOAD_1:
                case ByteCode.ALOAD_2:
                case ByteCode.ALOAD_3:
                    executeALoad(bc - ByteCode.ALOAD_0);
                    break;
                case ByteCode.ASTORE:
                    executeAStore(getOperand(bci + 1, wide ? 2 : 1));
                    break;
                case ByteCode.ASTORE_0:
                case ByteCode.ASTORE_1:
                case ByteCode.ASTORE_2:
                case ByteCode.ASTORE_3:
                    executeAStore(bc - ByteCode.ASTORE_0);
                    break;
                case ByteCode.IRETURN:
                case ByteCode.LRETURN:
                case ByteCode.FRETURN:
                case ByteCode.DRETURN:
                case ByteCode.ARETURN:
                case ByteCode.RETURN:
                    clearStack();
                    break;
                case ByteCode.ATHROW:
                    type = pop();
                    clearStack();
                    push(type);
                    break;
                case ByteCode.SWAP:
                    type = pop();
                    type2 = pop();
                    push(type);
                    push(type2);
                    break;
                case ByteCode.LDC:
                case ByteCode.LDC_W:
                case ByteCode.LDC2_W:
                    if (bc == ByteCode.LDC) {
                        index = getOperand(bci + 1);
                    } else {
                        index = getOperand(bci + 1, 2);
                    }
                    byte constType = itsConstantPool.getConstantType(index);
                    switch (constType) {
                        case ConstantPool.CONSTANT_Double:
                            push(TypeInfo.DOUBLE);
                            break;
                        case ConstantPool.CONSTANT_Float:
                            push(TypeInfo.FLOAT);
                            break;
                        case ConstantPool.CONSTANT_Long:
                            push(TypeInfo.LONG);
                            break;
                        case ConstantPool.CONSTANT_Integer:
                            push(TypeInfo.INTEGER);
                            break;
                        case ConstantPool.CONSTANT_String:
                            push(TypeInfo.OBJECT("java/lang/String",
                                                 itsConstantPool));
                            break;
                        default:
                            throw new IllegalArgumentException(
                                "bad const type " + constType);
                    }
                    break;
                case ByteCode.NEW:
                    push(TypeInfo.UNINITIALIZED_VARIABLE(bci));
                    break;
                case ByteCode.NEWARRAY:
                    pop();
                    char componentType =
                            arrayTypeToName(itsCodeBuffer[bci + 1]);
                    index = itsConstantPool.addClass("[" + componentType);
                    push(TypeInfo.OBJECT((short) index));
                    break;
                case ByteCode.ANEWARRAY:
                    index = getOperand(bci + 1, 2);
                    className = (String) itsConstantPool.getConstantData(index);
                    pop();
                    push(TypeInfo.OBJECT("[L" + className + ';',
                                         itsConstantPool));
                    break;
                case ByteCode.INVOKEVIRTUAL:
                case ByteCode.INVOKESPECIAL:
                case ByteCode.INVOKESTATIC:
                case ByteCode.INVOKEINTERFACE:
                    index = getOperand(bci + 1, 2);
                    FieldOrMethodRef m = (FieldOrMethodRef)
                            itsConstantPool.getConstantData(index);
                    String methodType = m.getType();
                    String methodName = m.getName();
                    int parameterCount = sizeOfParameters(methodType) >>> 16;
                    for (int i = 0; i < parameterCount; i++) {
                        pop();
                    }
                    if (bc != ByteCode.INVOKESTATIC) {
                        int instType = pop();
                        int tag = TypeInfo.getTag(instType);
                        if (tag == TypeInfo.UNINITIALIZED_VARIABLE(0) ||
                            tag == TypeInfo.UNINITIALIZED_THIS) {
                            if ("<init>".equals(methodName)) {
                                int newType =
                                        TypeInfo.OBJECT(itsThisClassIndex);
                                initializeTypeInfo(instType, newType);
                            } else {
                                throw new IllegalStateException("bad instance");
                            }
                        }
                    }
                    int rParen = methodType.indexOf(')');
                    String returnType = methodType.substring(rParen + 1);
                    returnType = descriptorToInternalName(returnType);
                    if (!returnType.equals("V")) {
                        push(TypeInfo.fromType(returnType, itsConstantPool));
                    }
                    break;
                case ByteCode.GETFIELD:
                    pop();
                case ByteCode.GETSTATIC:
                    index = getOperand(bci + 1, 2);
                    FieldOrMethodRef f = (FieldOrMethodRef)
                            itsConstantPool.getConstantData(index);
                    String fieldType = descriptorToInternalName(f.getType());
                    push(TypeInfo.fromType(fieldType, itsConstantPool));
                    break;
                case ByteCode.DUP:
                    type = pop();
                    push(type);
                    push(type);
                    break;
                case ByteCode.DUP_X1:
                    type = pop();
                    type2 = pop();
                    push(type);
                    push(type2);
                    push(type);
                    break;
                case ByteCode.DUP_X2:
                    type = pop();
                    lType = pop2();
                    push(type);
                    push2(lType);
                    push(type);
                    break;
                case ByteCode.DUP2:
                    lType = pop2();
                    push2(lType);
                    push2(lType);
                    break;
                case ByteCode.DUP2_X1:
                    lType = pop2();
                    type = pop();
                    push2(lType);
                    push(type);
                    push2(lType);
                    break;
                case ByteCode.DUP2_X2:
                    lType = pop2();
                    lType2 = pop2();
                    push2(lType);
                    push2(lType2);
                    push2(lType);
                    break;
                case ByteCode.TABLESWITCH:
                    int switchStart = bci + 1 + (3 & ~bci);
                    int low = getOperand(switchStart + 4, 4);
                    int high = getOperand(switchStart + 8, 4);
                    length = 4 * (high - low + 4) + switchStart - bci;
                    pop();
                    break;
                case ByteCode.AALOAD:
                    pop();
                    int typeIndex = pop() >>> 8;
                    className =
                            (String) itsConstantPool.getConstantData(typeIndex);
                    String arrayType = className;
                    if (arrayType.charAt(0) != '[') {
                        throw new IllegalStateException("bad array type");
                    }
                    String elementDesc = arrayType.substring(1);
                    String elementType = descriptorToInternalName(elementDesc);
                    typeIndex = itsConstantPool.addClass(elementType);
                    push(TypeInfo.OBJECT(typeIndex));
                    break;
                case ByteCode.WIDE:
                    // Alters behaviour of next instruction
                    wide = true;
                    break;
                case ByteCode.MULTIANEWARRAY:
                case ByteCode.LOOKUPSWITCH:
                    // Currently not used in any part of Rhino, so ignore it
                case ByteCode.JSR: // TODO: JSR is deprecated
                case ByteCode.RET:
                case ByteCode.JSR_W:
                default:
                    throw new IllegalArgumentException("bad opcode: " + bc);
            }

            if (length == 0) {
                length = opcodeLength(bc, wide);
            }
            if (wide && bc != ByteCode.WIDE) {
                wide = false;
            }
            return length;
        }

        private void executeALoad(int localIndex) {
            int type = getLocal(localIndex);
            int tag = TypeInfo.getTag(type);
            if (tag == TypeInfo.OBJECT_TAG ||
                tag == TypeInfo.UNINITIALIZED_THIS ||
                tag == TypeInfo.UNINITIALIZED_VAR_TAG ||
                tag == TypeInfo.NULL) {
                push(type);
            } else {
                throw new IllegalStateException("bad local variable type: " +
                                                type + " at index: " +
                                                localIndex);
            }
        }

        private void executeAStore(int localIndex) {
            setLocal(localIndex, pop());
        }

        private void executeStore(int localIndex, int typeInfo) {
            pop();
            setLocal(localIndex, typeInfo);
        }

        /**
         * Change an UNINITIALIZED_OBJECT or UNINITIALIZED_THIS to the proper
         * type of the object. This occurs when the proper constructor is
         * invoked.
         */
        private void initializeTypeInfo(int prevType, int newType) {
            initializeTypeInfo(prevType, newType, locals, localsTop);
            initializeTypeInfo(prevType, newType, stack, stackTop);
        }

        private void initializeTypeInfo(int prevType, int newType, int[] data,
                                        int dataTop) {
            for (int i = 0; i < dataTop; i++) {
                if (data[i] == prevType) {
                    data[i] = newType;
                }
            }
        }

        private int getLocal(int localIndex) {
            if (localIndex < localsTop) {
                return locals[localIndex];
            } else {
                return TypeInfo.TOP;
            }
        }

        private void setLocal(int localIndex, int typeInfo) {
            if (localIndex >= localsTop) {
                int[] tmp = new int[localIndex + 1];
                System.arraycopy(locals, 0, tmp, 0, localsTop);
                locals = tmp;
                localsTop = localIndex + 1;
            }
            locals[localIndex] = typeInfo;
        }

        private void push(int typeInfo) {
            if (stackTop == stack.length) {
                int[] tmp = new int[Math.max(stackTop * 2, 4)];
                System.arraycopy(stack, 0, tmp, 0, stackTop);
                stack = tmp;
            }
            stack[stackTop++] = typeInfo;
        }

        private int pop() {
            return stack[--stackTop];
        }

        /**
         * Push two words onto the op stack.
         *
         * This is only meant to be used as a complement to pop2(), and both
         * methods are helpers for the more complex DUP operations.
         */
        private void push2(long typeInfo) {
            push((int) (typeInfo & 0xFFFFFF));
            typeInfo >>>= 32;
            if (typeInfo != 0) {
                push((int) (typeInfo & 0xFFFFFF));
            }
        }

        /**
         * Pop two words from the op stack.
         *
         * If the top of the stack is a DOUBLE or LONG, then the bottom 32 bits
         * reflects the appropriate type and the top 32 bits are 0. Otherwise,
         * the top 32 bits are the first word on the stack and the lower 32
         * bits are the second word on the stack.
         */
        private long pop2() {
            long type = pop();
            if (TypeInfo.isTwoWords((int) type)) {
                return type;
            } else {
                return type << 32 | (pop() & 0xFFFFFF);
            }
        }

        private void clearStack() {
            stackTop = 0;
        }

        /**
         * Compute the output size of the stack map table.
         *
         * Because this would share much in common with actual writing of the
         * stack map table, we instead just write the stack map table to a
         * buffer and return the size from it. The buffer is later used in
         * the actual writing of bytecode.
         */
        int computeWriteSize() {
            // Allocate a buffer that can handle the worst case size of the
            // stack map to prevent lots of reallocations.
            int writeSize = getWorstCaseWriteSize();
            rawStackMap = new byte[writeSize];
            computeRawStackMap();
            return rawStackMapTop + 2;
        }

        int write(byte[] data, int offset) {
            offset = putInt32(rawStackMapTop + 2, data, offset);
            offset = putInt16(superBlocks.length - 1, data, offset);
            System.arraycopy(rawStackMap, 0, data, offset, rawStackMapTop);
            return offset + rawStackMapTop;
        }

        /**
         * Compute a space-optimal stack map table.
         */
        private void computeRawStackMap() {
            SuperBlock prev = superBlocks[0];
            int[] prevLocals = prev.getTrimmedLocals();
            int prevOffset = -1;
            for (int i = 1; i < superBlocks.length; i++) {
                SuperBlock current = superBlocks[i];
                int[] currentLocals = current.getTrimmedLocals();
                int[] currentStack = current.getStack();
                int offsetDelta = current.getStart() - prevOffset - 1;

                if (currentStack.length == 0) {
                    int last = prevLocals.length > currentLocals.length ?
                            currentLocals.length : prevLocals.length;
                    int delta = Math.abs(prevLocals.length -
                                         currentLocals.length);
                    int j;
                    // Compare locals until one is different or the end of a
                    // local variable array is reached
                    for (j = 0; j < last; j++) {
                        if (prevLocals[j] != currentLocals[j]) {
                            break;
                        }
                    }
                    if (j == currentLocals.length && delta == 0) {
                        // All of the compared locals are equal and the local
                        // arrays are of equal size
                        writeSameFrame(currentLocals, offsetDelta);
                    } else if (j == currentLocals.length && delta <= 3) {
                        // All of the compared locals are equal and the current
                        // frame has less locals than the previous frame
                        writeChopFrame(delta, offsetDelta);
                    } else if (j == prevLocals.length && delta <= 3) {
                        // All of the compared locals are equal and the current
                        // frame has more locals than the previous frame
                        writeAppendFrame(currentLocals, delta, offsetDelta);
                    } else {
                        // Not all locals were compared were equal, so a full
                        // frame is necessary
                        writeFullFrame(currentLocals, currentStack,
                                       offsetDelta);
                    }
                } else if (currentStack.length == 1) {
                    if (Arrays.equals(prevLocals, currentLocals)) {
                       writeSameLocalsOneStackItemFrame(currentLocals,
                                                        currentStack,
                                                        offsetDelta);
                    } else {
                        // Output a full frame, since no other frame types have
                        // one operand stack item.
                        writeFullFrame(currentLocals, currentStack,
                                       offsetDelta);
                    }
                } else {
                    // Any stack map frame that has more than one operand stack
                    // item has to be a full frame. All other frame types have
                    // at most one item on the stack.
                    writeFullFrame(currentLocals, currentStack, offsetDelta);
                }

                prev = current;
                prevLocals = currentLocals;
                prevOffset = current.getStart();
            }
        }

        /**
         * Get the worst case write size of the stack map table.
         *
         * This computes how much full frames would take, if each full frame
         * contained the maximum number of locals and stack operands, and each
         * verification type was 3 bytes.
         */
        private int getWorstCaseWriteSize() {
            return (superBlocks.length - 1) * (7 + itsMaxLocals * 3 +
                                               itsMaxStack * 3);
        }

        private void writeSameFrame(int[] locals, int offsetDelta) {
            if (offsetDelta <= 63) {
                // Output a same_frame frame. Despite the name,
                // the operand stack may differ, but the current
                // operand stack must be empty.
                rawStackMap[rawStackMapTop++] = (byte) offsetDelta;
            } else {
                // Output a same_frame_extended frame. Similar to
                // the above, except with a larger offset delta.
                rawStackMap[rawStackMapTop++] = (byte) 251;
                rawStackMapTop = putInt16(offsetDelta, rawStackMap,
                                          rawStackMapTop);
            }
        }

        private void writeSameLocalsOneStackItemFrame(int[] locals,
                                                      int[] stack,
                                                      int offsetDelta) {
            if (offsetDelta <= 63) {
                // Output a same_locals_1_stack_item frame. Similar
                // to same_frame, only with one item on the operand
                // stack instead of zero.
                rawStackMap[rawStackMapTop++] = (byte) (64 + offsetDelta);
            } else {
                // Output a same_locals_1_stack_item_extended frame.
                // Similar to same_frame_extended, only with one
                // item on the operand stack instead of zero.
                rawStackMap[rawStackMapTop++] = (byte) 247;
                rawStackMapTop = putInt16(offsetDelta, rawStackMap,
                                          rawStackMapTop);
            }
            writeType(stack[0]);
        }

        private void writeFullFrame(int[] locals, int[] stack,
                                    int offsetDelta) {
            rawStackMap[rawStackMapTop++] = (byte) 255;
            rawStackMapTop = putInt16(offsetDelta, rawStackMap, rawStackMapTop);
            rawStackMapTop = putInt16(locals.length, rawStackMap,
                                      rawStackMapTop);
            rawStackMapTop = writeTypes(locals);
            rawStackMapTop = putInt16(stack.length, rawStackMap,
                                      rawStackMapTop);
            rawStackMapTop = writeTypes(stack);
        }

        private void writeAppendFrame(int[] locals, int localsDelta,
                                      int offsetDelta) {
            int start = locals.length - localsDelta;
            rawStackMap[rawStackMapTop++] = (byte) (251 + localsDelta);
            rawStackMapTop = putInt16(offsetDelta, rawStackMap, rawStackMapTop);
            rawStackMapTop = writeTypes(locals, start);
        }

        private void writeChopFrame(int localsDelta, int offsetDelta) {
            rawStackMap[rawStackMapTop++] = (byte) (251 - localsDelta);
            rawStackMapTop = putInt16(offsetDelta, rawStackMap, rawStackMapTop);
        }

        private int writeTypes(int[] types) {
            return writeTypes(types, 0);
        }

        private int writeTypes(int[] types, int start) {
            int startOffset = rawStackMapTop;
            for (int i = start; i < types.length; i++) {
                rawStackMapTop = writeType(types[i]);
            }
            return rawStackMapTop;
        }

        private int writeType(int type) {
            int tag = type & 0xFF;
            rawStackMap[rawStackMapTop++] = (byte) tag;
            if (tag == TypeInfo.OBJECT_TAG ||
                tag == TypeInfo.UNINITIALIZED_VAR_TAG) {
                rawStackMapTop = putInt16(type >>> 8, rawStackMap,
                                          rawStackMapTop);
            }
            return rawStackMapTop;
        }

        // Intermediate operand stack and local variable state. During
        // execution of a block, these are initialized to copies of the initial
        // block type state and are modified by the actual stack/local
        // emulation.
        private int[] locals;
        private int localsTop;
        private int[] stack;
        private int stackTop;

        private SuperBlock[] workList;
        private int workListTop;

        private SuperBlock[] superBlocks;
        private SuperBlock[] superBlockDeps;

        private byte[] rawStackMap;
        private int rawStackMapTop;

        private boolean wide;

        static final boolean DEBUGSTACKMAP = false;
    }

    /**
     * Convert a newarray operand into an internal type.
     */
    private static char arrayTypeToName(int type) {
        switch (type) {
            case ByteCode.T_BOOLEAN:
                return 'Z';
            case ByteCode.T_CHAR:
                return 'C';
            case ByteCode.T_FLOAT:
                return 'F';
            case ByteCode.T_DOUBLE:
                return 'D';
            case ByteCode.T_BYTE:
                return 'B';
            case ByteCode.T_SHORT:
                return 'S';
            case ByteCode.T_INT:
                return 'I';
            case ByteCode.T_LONG:
                return 'J';
            default:
                throw new IllegalArgumentException("bad operand");
        }
    }

    /**
     * Convert a class descriptor into an internal name.
     *
     * For example, descriptor Ljava/lang/Object; becomes java/lang/Object.
     */
    private static String classDescriptorToInternalName(String descriptor) {
        return descriptor.substring(1, descriptor.length() - 1);
    }

    /**
     * Convert a non-method type descriptor into an internal type.
     *
     * @param descriptor the simple type descriptor to convert
     */
    private static String descriptorToInternalName(String descriptor) {
        switch (descriptor.charAt(0)) {
            case 'B':
            case 'C':
            case 'D':
            case 'F':
            case 'I':
            case 'J':
            case 'S':
            case 'Z':
            case 'V':
            case '[':
                return descriptor;
            case 'L':
                return classDescriptorToInternalName(descriptor);
            default:
                throw new IllegalArgumentException("bad descriptor:" +
                                                   descriptor);
        }
    }

    /**
     * Compute the initial local variable array for the current method.
     *
     * Creates an array of the size of the method's max locals, regardless of
     * the number of parameters in the method.
     */
    private int[] createInitialLocals() {
        int[] initialLocals = new int[itsMaxLocals];
        int localsTop = 0;
        // Instance methods require the first local variable in the array
        // to be "this". However, if the method being created is a
        // constructor, aka the method is <init>, then the type of "this"
        // should be StackMapTable.UNINITIALIZED_THIS
        if ((itsCurrentMethod.getFlags() & ACC_STATIC) == 0) {
            if ("<init>".equals(itsCurrentMethod.getName())) {
                initialLocals[localsTop++] = TypeInfo.UNINITIALIZED_THIS;
            } else {
                initialLocals[localsTop++] = TypeInfo.OBJECT(itsThisClassIndex);
            }
        }

        // No error checking should be necessary, sizeOfParameters does this
        String type = itsCurrentMethod.getType();
        int lParenIndex = type.indexOf('(');
        int rParenIndex = type.indexOf(')');
        if (lParenIndex != 0 || rParenIndex < 0) {
            throw new IllegalArgumentException("bad method type");
        }
        int start = lParenIndex + 1;
        StringBuilder paramType = new StringBuilder();
        while (start < rParenIndex) {
            switch (type.charAt(start)) {
                case 'B':
                case 'C':
                case 'D':
                case 'F':
                case 'I':
                case 'J':
                case 'S':
                case 'Z':
                    paramType.append(type.charAt(start));
                    ++start;
                    break;
                case 'L':
                    int end = type.indexOf(';', start) + 1;
                    String name = type.substring(start, end);
                    paramType.append(name);
                    start = end;
                    break;
                case '[':
                    paramType.append('[');
                    ++start;
                    continue;
            }
            String internalType =
                    descriptorToInternalName(paramType.toString());
            int typeInfo = TypeInfo.fromType(internalType, itsConstantPool);
            initialLocals[localsTop++] = typeInfo;
            if (TypeInfo.isTwoWords(typeInfo)) {
                localsTop++;
            }
            paramType.setLength(0);
        }
        return initialLocals;
    }

    /**
     * Write the class file to the OutputStream.
     *
     * @param oStream the stream to write to
     * @throws IOException if writing to the stream produces an exception
     */
    public void write(OutputStream oStream)
        throws IOException
    {
        byte[] array = toByteArray();
        oStream.write(array);
    }

    private int getWriteSize()
    {
        int size = 0;

        if (itsSourceFileNameIndex != 0) {
            itsConstantPool.addUtf8("SourceFile");
        }

        size += 8; //writeLong(FileHeaderConstant);
        size += itsConstantPool.getWriteSize();
        size += 2; //writeShort(itsFlags);
        size += 2; //writeShort(itsThisClassIndex);
        size += 2; //writeShort(itsSuperClassIndex);
        size += 2; //writeShort(itsInterfaces.size());
        size += 2 * itsInterfaces.size();

        size += 2; //writeShort(itsFields.size());
        for (int i = 0; i < itsFields.size(); i++) {
            size += ((ClassFileField)(itsFields.get(i))).getWriteSize();
        }

        size += 2; //writeShort(itsMethods.size());
        for (int i = 0; i < itsMethods.size(); i++) {
            size += ((ClassFileMethod)(itsMethods.get(i))).getWriteSize();
        }

        if (itsSourceFileNameIndex != 0) {
            size += 2; //writeShort(1);  attributes count
            size += 2; //writeShort(sourceFileAttributeNameIndex);
            size += 4; //writeInt(2);
            size += 2; //writeShort(itsSourceFileNameIndex);
        }else {
            size += 2; //out.writeShort(0);  no attributes
        }

        return size;
    }

    /**
     * Get the class file as array of bytesto the OutputStream.
     */
    public byte[] toByteArray()
    {
        int dataSize = getWriteSize();
        byte[] data = new byte[dataSize];
        int offset = 0;

        short sourceFileAttributeNameIndex = 0;
        if (itsSourceFileNameIndex != 0) {
            sourceFileAttributeNameIndex = itsConstantPool.addUtf8(
                                               "SourceFile");
        }

        offset = putInt32(FileHeaderConstant, data, offset);
        offset = putInt16(MinorVersion, data, offset);
        offset = putInt16(MajorVersion, data, offset);
        offset = itsConstantPool.write(data, offset);
        offset = putInt16(itsFlags, data, offset);
        offset = putInt16(itsThisClassIndex, data, offset);
        offset = putInt16(itsSuperClassIndex, data, offset);
        offset = putInt16(itsInterfaces.size(), data, offset);
        for (int i = 0; i < itsInterfaces.size(); i++) {
            int interfaceIndex = ((Short)(itsInterfaces.get(i))).shortValue();
            offset = putInt16(interfaceIndex, data, offset);
        }
        offset = putInt16(itsFields.size(), data, offset);
        for (int i = 0; i < itsFields.size(); i++) {
            ClassFileField field = (ClassFileField)itsFields.get(i);
            offset = field.write(data, offset);
        }
        offset = putInt16(itsMethods.size(), data, offset);
        for (int i = 0; i < itsMethods.size(); i++) {
            ClassFileMethod method = (ClassFileMethod)itsMethods.get(i);
            offset = method.write(data, offset);
        }
        if (itsSourceFileNameIndex != 0) {
            offset = putInt16(1, data, offset); // attributes count
            offset = putInt16(sourceFileAttributeNameIndex, data, offset);
            offset = putInt32(2, data, offset);
            offset = putInt16(itsSourceFileNameIndex, data, offset);
        } else {
            offset = putInt16(0, data, offset); // no attributes
        }

        if (offset != dataSize) {
            // Check getWriteSize is consistent with write!
            throw new RuntimeException();
        }

        return data;
    }

    static int putInt64(long value, byte[] array, int offset)
    {
        offset = putInt32((int)(value >>> 32), array, offset);
        return putInt32((int)value, array, offset);
    }

    private static void badStack(int value)
    {
        String s;
        if (value < 0) { s = "Stack underflow: "+value; }
        else { s = "Too big stack: "+value; }
        throw new IllegalStateException(s);
    }

    /*
        Really weird. Returns an int with # parameters in hi 16 bits, and
        stack difference removal of parameters from stack and pushing the
        result (it does not take into account removal of this in case of
        non-static methods).
        If Java really supported references we wouldn't have to be this
        perverted.
    */
    private static int sizeOfParameters(String pString)
    {
        int length = pString.length();
        int rightParenthesis = pString.lastIndexOf(')');
        if (3 <= length /* minimal signature takes at least 3 chars: ()V */
            && pString.charAt(0) == '('
            && 1 <= rightParenthesis && rightParenthesis + 1 < length)
        {
            boolean ok = true;
            int index = 1;
            int stackDiff = 0;
            int count = 0;
        stringLoop:
            while (index != rightParenthesis) {
                switch (pString.charAt(index)) {
                    default:
                        ok = false;
                        break stringLoop;
                    case 'J' :
                    case 'D' :
                        --stackDiff;
                        // fall thru
                    case 'B' :
                    case 'S' :
                    case 'C' :
                    case 'I' :
                    case 'Z' :
                    case 'F' :
                        --stackDiff;
                        ++count;
                        ++index;
                        continue;
                    case '[' :
                        ++index;
                        int c = pString.charAt(index);
                        while (c == '[') {
                            ++index;
                            c = pString.charAt(index);
                        }
                        switch (c) {
                            default:
                                ok = false;
                                break stringLoop;
                            case 'J' :
                            case 'D' :
                            case 'B' :
                            case 'S' :
                            case 'C' :
                            case 'I' :
                            case 'Z' :
                            case 'F' :
                                --stackDiff;
                                ++count;
                                ++index;
                                continue;
                            case 'L':
                                // fall thru
                        }
                          // fall thru
                    case 'L' : {
                        --stackDiff;
                        ++count;
                        ++index;
                        int semicolon = pString.indexOf(';',  index);
                        if (!(index + 1 <= semicolon
                            && semicolon < rightParenthesis))
                        {
                            ok = false;
                            break stringLoop;
                        }
                        index = semicolon + 1;
                        continue;
                    }
                }
            }
            if (ok) {
                switch (pString.charAt(rightParenthesis + 1)) {
                    default:
                        ok = false;
                        break;
                    case 'J' :
                    case 'D' :
                        ++stackDiff;
                        // fall thru
                    case 'B' :
                    case 'S' :
                    case 'C' :
                    case 'I' :
                    case 'Z' :
                    case 'F' :
                    case 'L' :
                    case '[' :
                        ++stackDiff;
                        // fall thru
                    case 'V' :
                        break;
                }
                if (ok) {
                    return ((count << 16) | (0xFFFF & stackDiff));
                }
            }
        }
        throw new IllegalArgumentException(
            "Bad parameter signature: "+pString);
    }

    static int putInt16(int value, byte[] array, int offset)
    {
        array[offset + 0] = (byte)(value >>> 8);
        array[offset + 1] = (byte)value;
        return offset + 2;
    }

    static int putInt32(int value, byte[] array, int offset)
    {
        array[offset + 0] = (byte)(value >>> 24);
        array[offset + 1] = (byte)(value >>> 16);
        array[offset + 2] = (byte)(value >>> 8);
        array[offset + 3] = (byte)value;
        return offset + 4;
    }

    /**
     * Size of a bytecode instruction, counting the opcode and its operands.
     *
     * This is different from opcodeCount, since opcodeCount counts logical
     * operands.
     */
    static int opcodeLength(int opcode, boolean wide) {
        switch (opcode) {
            case ByteCode.AALOAD:
            case ByteCode.AASTORE:
            case ByteCode.ACONST_NULL:
            case ByteCode.ALOAD_0:
            case ByteCode.ALOAD_1:
            case ByteCode.ALOAD_2:
            case ByteCode.ALOAD_3:
            case ByteCode.ARETURN:
            case ByteCode.ARRAYLENGTH:
            case ByteCode.ASTORE_0:
            case ByteCode.ASTORE_1:
            case ByteCode.ASTORE_2:
            case ByteCode.ASTORE_3:
            case ByteCode.ATHROW:
            case ByteCode.BALOAD:
            case ByteCode.BASTORE:
            case ByteCode.BREAKPOINT:
            case ByteCode.CALOAD:
            case ByteCode.CASTORE:
            case ByteCode.D2F:
            case ByteCode.D2I:
            case ByteCode.D2L:
            case ByteCode.DADD:
            case ByteCode.DALOAD:
            case ByteCode.DASTORE:
            case ByteCode.DCMPG:
            case ByteCode.DCMPL:
            case ByteCode.DCONST_0:
            case ByteCode.DCONST_1:
            case ByteCode.DDIV:
            case ByteCode.DLOAD_0:
            case ByteCode.DLOAD_1:
            case ByteCode.DLOAD_2:
            case ByteCode.DLOAD_3:
            case ByteCode.DMUL:
            case ByteCode.DNEG:
            case ByteCode.DREM:
            case ByteCode.DRETURN:
            case ByteCode.DSTORE_0:
            case ByteCode.DSTORE_1:
            case ByteCode.DSTORE_2:
            case ByteCode.DSTORE_3:
            case ByteCode.DSUB:
            case ByteCode.DUP:
            case ByteCode.DUP2:
            case ByteCode.DUP2_X1:
            case ByteCode.DUP2_X2:
            case ByteCode.DUP_X1:
            case ByteCode.DUP_X2:
            case ByteCode.F2D:
            case ByteCode.F2I:
            case ByteCode.F2L:
            case ByteCode.FADD:
            case ByteCode.FALOAD:
            case ByteCode.FASTORE:
            case ByteCode.FCMPG:
            case ByteCode.FCMPL:
            case ByteCode.FCONST_0:
            case ByteCode.FCONST_1:
            case ByteCode.FCONST_2:
            case ByteCode.FDIV:
            case ByteCode.FLOAD_0:
            case ByteCode.FLOAD_1:
            case ByteCode.FLOAD_2:
            case ByteCode.FLOAD_3:
            case ByteCode.FMUL:
            case ByteCode.FNEG:
            case ByteCode.FREM:
            case ByteCode.FRETURN:
            case ByteCode.FSTORE_0:
            case ByteCode.FSTORE_1:
            case ByteCode.FSTORE_2:
            case ByteCode.FSTORE_3:
            case ByteCode.FSUB:
            case ByteCode.I2B:
            case ByteCode.I2C:
            case ByteCode.I2D:
            case ByteCode.I2F:
            case ByteCode.I2L:
            case ByteCode.I2S:
            case ByteCode.IADD:
            case ByteCode.IALOAD:
            case ByteCode.IAND:
            case ByteCode.IASTORE:
            case ByteCode.ICONST_0:
            case ByteCode.ICONST_1:
            case ByteCode.ICONST_2:
            case ByteCode.ICONST_3:
            case ByteCode.ICONST_4:
            case ByteCode.ICONST_5:
            case ByteCode.ICONST_M1:
            case ByteCode.IDIV:
            case ByteCode.ILOAD_0:
            case ByteCode.ILOAD_1:
            case ByteCode.ILOAD_2:
            case ByteCode.ILOAD_3:
            case ByteCode.IMPDEP1:
            case ByteCode.IMPDEP2:
            case ByteCode.IMUL:
            case ByteCode.INEG:
            case ByteCode.IOR:
            case ByteCode.IREM:
            case ByteCode.IRETURN:
            case ByteCode.ISHL:
            case ByteCode.ISHR:
            case ByteCode.ISTORE_0:
            case ByteCode.ISTORE_1:
            case ByteCode.ISTORE_2:
            case ByteCode.ISTORE_3:
            case ByteCode.ISUB:
            case ByteCode.IUSHR:
            case ByteCode.IXOR:
            case ByteCode.L2D:
            case ByteCode.L2F:
            case ByteCode.L2I:
            case ByteCode.LADD:
            case ByteCode.LALOAD:
            case ByteCode.LAND:
            case ByteCode.LASTORE:
            case ByteCode.LCMP:
            case ByteCode.LCONST_0:
            case ByteCode.LCONST_1:
            case ByteCode.LDIV:
            case ByteCode.LLOAD_0:
            case ByteCode.LLOAD_1:
            case ByteCode.LLOAD_2:
            case ByteCode.LLOAD_3:
            case ByteCode.LMUL:
            case ByteCode.LNEG:
            case ByteCode.LOR:
            case ByteCode.LREM:
            case ByteCode.LRETURN:
            case ByteCode.LSHL:
            case ByteCode.LSHR:
            case ByteCode.LSTORE_0:
            case ByteCode.LSTORE_1:
            case ByteCode.LSTORE_2:
            case ByteCode.LSTORE_3:
            case ByteCode.LSUB:
            case ByteCode.LUSHR:
            case ByteCode.LXOR:
            case ByteCode.MONITORENTER:
            case ByteCode.MONITOREXIT:
            case ByteCode.NOP:
            case ByteCode.POP:
            case ByteCode.POP2:
            case ByteCode.RETURN:
            case ByteCode.SALOAD:
            case ByteCode.SASTORE:
            case ByteCode.SWAP:
            case ByteCode.WIDE:
                return 1;
            case ByteCode.BIPUSH:
            case ByteCode.LDC:
            case ByteCode.NEWARRAY:
                return 2;
            case ByteCode.ALOAD:
            case ByteCode.ASTORE:
            case ByteCode.DLOAD:
            case ByteCode.DSTORE:
            case ByteCode.FLOAD:
            case ByteCode.FSTORE:
            case ByteCode.ILOAD:
            case ByteCode.ISTORE:
            case ByteCode.LLOAD:
            case ByteCode.LSTORE:
            case ByteCode.RET:
                return wide ? 3 : 2;

            case ByteCode.ANEWARRAY:
            case ByteCode.CHECKCAST:
            case ByteCode.GETFIELD:
            case ByteCode.GETSTATIC:
            case ByteCode.GOTO:
            case ByteCode.IFEQ:
            case ByteCode.IFGE:
            case ByteCode.IFGT:
            case ByteCode.IFLE:
            case ByteCode.IFLT:
            case ByteCode.IFNE:
            case ByteCode.IFNONNULL:
            case ByteCode.IFNULL:
            case ByteCode.IF_ACMPEQ:
            case ByteCode.IF_ACMPNE:
            case ByteCode.IF_ICMPEQ:
            case ByteCode.IF_ICMPGE:
            case ByteCode.IF_ICMPGT:
            case ByteCode.IF_ICMPLE:
            case ByteCode.IF_ICMPLT:
            case ByteCode.IF_ICMPNE:
            case ByteCode.INSTANCEOF:
            case ByteCode.INVOKESPECIAL:
            case ByteCode.INVOKESTATIC:
            case ByteCode.INVOKEVIRTUAL:
            case ByteCode.JSR:
            case ByteCode.LDC_W:
            case ByteCode.LDC2_W:
            case ByteCode.NEW:
            case ByteCode.PUTFIELD:
            case ByteCode.PUTSTATIC:
            case ByteCode.SIPUSH:
                return 3;

            case ByteCode.IINC:
                return wide ? 5 : 3;

            case ByteCode.MULTIANEWARRAY:
                return 4;

            case ByteCode.GOTO_W:
            case ByteCode.INVOKEINTERFACE:
            case ByteCode.JSR_W:
                return 5;

            /*
            case ByteCode.LOOKUPSWITCH:
            case ByteCode.TABLESWITCH:
                return -1;
            */
        }
        throw new IllegalArgumentException("Bad opcode: " + opcode);
    }

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

            case ByteCode.IINC:
            case ByteCode.MULTIANEWARRAY:
                return 2;

            case ByteCode.LOOKUPSWITCH:
            case ByteCode.TABLESWITCH:
                return -1;
        }
        throw new IllegalArgumentException("Bad opcode: "+opcode);
    }

    /**
     *  The effect on the operand stack of a given opcode.
     */
    static int stackChange(int opcode)
    {
        // For INVOKE... accounts only for popping this (unless static),
        // ignoring parameters and return type
        switch (opcode) {
            case ByteCode.DASTORE:
            case ByteCode.LASTORE:
                return -4;

            case ByteCode.AASTORE:
            case ByteCode.BASTORE:
            case ByteCode.CASTORE:
            case ByteCode.DCMPG:
            case ByteCode.DCMPL:
            case ByteCode.FASTORE:
            case ByteCode.IASTORE:
            case ByteCode.LCMP:
            case ByteCode.SASTORE:
                return -3;

            case ByteCode.DADD:
            case ByteCode.DDIV:
            case ByteCode.DMUL:
            case ByteCode.DREM:
            case ByteCode.DRETURN:
            case ByteCode.DSTORE:
            case ByteCode.DSTORE_0:
            case ByteCode.DSTORE_1:
            case ByteCode.DSTORE_2:
            case ByteCode.DSTORE_3:
            case ByteCode.DSUB:
            case ByteCode.IF_ACMPEQ:
            case ByteCode.IF_ACMPNE:
            case ByteCode.IF_ICMPEQ:
            case ByteCode.IF_ICMPGE:
            case ByteCode.IF_ICMPGT:
            case ByteCode.IF_ICMPLE:
            case ByteCode.IF_ICMPLT:
            case ByteCode.IF_ICMPNE:
            case ByteCode.LADD:
            case ByteCode.LAND:
            case ByteCode.LDIV:
            case ByteCode.LMUL:
            case ByteCode.LOR:
            case ByteCode.LREM:
            case ByteCode.LRETURN:
            case ByteCode.LSTORE:
            case ByteCode.LSTORE_0:
            case ByteCode.LSTORE_1:
            case ByteCode.LSTORE_2:
            case ByteCode.LSTORE_3:
            case ByteCode.LSUB:
            case ByteCode.LXOR:
            case ByteCode.POP2:
                return -2;

            case ByteCode.AALOAD:
            case ByteCode.ARETURN:
            case ByteCode.ASTORE:
            case ByteCode.ASTORE_0:
            case ByteCode.ASTORE_1:
            case ByteCode.ASTORE_2:
            case ByteCode.ASTORE_3:
            case ByteCode.ATHROW:
            case ByteCode.BALOAD:
            case ByteCode.CALOAD:
            case ByteCode.D2F:
            case ByteCode.D2I:
            case ByteCode.FADD:
            case ByteCode.FALOAD:
            case ByteCode.FCMPG:
            case ByteCode.FCMPL:
            case ByteCode.FDIV:
            case ByteCode.FMUL:
            case ByteCode.FREM:
            case ByteCode.FRETURN:
            case ByteCode.FSTORE:
            case ByteCode.FSTORE_0:
            case ByteCode.FSTORE_1:
            case ByteCode.FSTORE_2:
            case ByteCode.FSTORE_3:
            case ByteCode.FSUB:
            case ByteCode.GETFIELD:
            case ByteCode.IADD:
            case ByteCode.IALOAD:
            case ByteCode.IAND:
            case ByteCode.IDIV:
            case ByteCode.IFEQ:
            case ByteCode.IFGE:
            case ByteCode.IFGT:
            case ByteCode.IFLE:
            case ByteCode.IFLT:
            case ByteCode.IFNE:
            case ByteCode.IFNONNULL:
            case ByteCode.IFNULL:
            case ByteCode.IMUL:
            case ByteCode.INVOKEINTERFACE:       //
            case ByteCode.INVOKESPECIAL:         // but needs to account for
            case ByteCode.INVOKEVIRTUAL:         // pops 'this' (unless static)
            case ByteCode.IOR:
            case ByteCode.IREM:
            case ByteCode.IRETURN:
            case ByteCode.ISHL:
            case ByteCode.ISHR:
            case ByteCode.ISTORE:
            case ByteCode.ISTORE_0:
            case ByteCode.ISTORE_1:
            case ByteCode.ISTORE_2:
            case ByteCode.ISTORE_3:
            case ByteCode.ISUB:
            case ByteCode.IUSHR:
            case ByteCode.IXOR:
            case ByteCode.L2F:
            case ByteCode.L2I:
            case ByteCode.LOOKUPSWITCH:
            case ByteCode.LSHL:
            case ByteCode.LSHR:
            case ByteCode.LUSHR:
            case ByteCode.MONITORENTER:
            case ByteCode.MONITOREXIT:
            case ByteCode.POP:
            case ByteCode.PUTFIELD:
            case ByteCode.SALOAD:
            case ByteCode.TABLESWITCH:
                return -1;

            case ByteCode.ANEWARRAY:
            case ByteCode.ARRAYLENGTH:
            case ByteCode.BREAKPOINT:
            case ByteCode.CHECKCAST:
            case ByteCode.D2L:
            case ByteCode.DALOAD:
            case ByteCode.DNEG:
            case ByteCode.F2I:
            case ByteCode.FNEG:
            case ByteCode.GETSTATIC:
            case ByteCode.GOTO:
            case ByteCode.GOTO_W:
            case ByteCode.I2B:
            case ByteCode.I2C:
            case ByteCode.I2F:
            case ByteCode.I2S:
            case ByteCode.IINC:
            case ByteCode.IMPDEP1:
            case ByteCode.IMPDEP2:
            case ByteCode.INEG:
            case ByteCode.INSTANCEOF:
            case ByteCode.INVOKESTATIC:
            case ByteCode.L2D:
            case ByteCode.LALOAD:
            case ByteCode.LNEG:
            case ByteCode.NEWARRAY:
            case ByteCode.NOP:
            case ByteCode.PUTSTATIC:
            case ByteCode.RET:
            case ByteCode.RETURN:
            case ByteCode.SWAP:
            case ByteCode.WIDE:
                return 0;

            case ByteCode.ACONST_NULL:
            case ByteCode.ALOAD:
            case ByteCode.ALOAD_0:
            case ByteCode.ALOAD_1:
            case ByteCode.ALOAD_2:
            case ByteCode.ALOAD_3:
            case ByteCode.BIPUSH:
            case ByteCode.DUP:
            case ByteCode.DUP_X1:
            case ByteCode.DUP_X2:
            case ByteCode.F2D:
            case ByteCode.F2L:
            case ByteCode.FCONST_0:
            case ByteCode.FCONST_1:
            case ByteCode.FCONST_2:
            case ByteCode.FLOAD:
            case ByteCode.FLOAD_0:
            case ByteCode.FLOAD_1:
            case ByteCode.FLOAD_2:
            case ByteCode.FLOAD_3:
            case ByteCode.I2D:
            case ByteCode.I2L:
            case ByteCode.ICONST_0:
            case ByteCode.ICONST_1:
            case ByteCode.ICONST_2:
            case ByteCode.ICONST_3:
            case ByteCode.ICONST_4:
            case ByteCode.ICONST_5:
            case ByteCode.ICONST_M1:
            case ByteCode.ILOAD:
            case ByteCode.ILOAD_0:
            case ByteCode.ILOAD_1:
            case ByteCode.ILOAD_2:
            case ByteCode.ILOAD_3:
            case ByteCode.JSR:
            case ByteCode.JSR_W:
            case ByteCode.LDC:
            case ByteCode.LDC_W:
            case ByteCode.MULTIANEWARRAY:
            case ByteCode.NEW:
            case ByteCode.SIPUSH:
                return 1;

            case ByteCode.DCONST_0:
            case ByteCode.DCONST_1:
            case ByteCode.DLOAD:
            case ByteCode.DLOAD_0:
            case ByteCode.DLOAD_1:
            case ByteCode.DLOAD_2:
            case ByteCode.DLOAD_3:
            case ByteCode.DUP2:
            case ByteCode.DUP2_X1:
            case ByteCode.DUP2_X2:
            case ByteCode.LCONST_0:
            case ByteCode.LCONST_1:
            case ByteCode.LDC2_W:
            case ByteCode.LLOAD:
            case ByteCode.LLOAD_0:
            case ByteCode.LLOAD_1:
            case ByteCode.LLOAD_2:
            case ByteCode.LLOAD_3:
                return 2;
        }
        throw new IllegalArgumentException("Bad opcode: "+opcode);
    }

        /*
         * Number of bytes of operands generated after the opcode.
         * Not in use currently.
         */
/*
    int extra(int opcode)
    {
        switch (opcode) {
            case ByteCode.AALOAD:
            case ByteCode.AASTORE:
            case ByteCode.ACONST_NULL:
            case ByteCode.ALOAD_0:
            case ByteCode.ALOAD_1:
            case ByteCode.ALOAD_2:
            case ByteCode.ALOAD_3:
            case ByteCode.ARETURN:
            case ByteCode.ARRAYLENGTH:
            case ByteCode.ASTORE_0:
            case ByteCode.ASTORE_1:
            case ByteCode.ASTORE_2:
            case ByteCode.ASTORE_3:
            case ByteCode.ATHROW:
            case ByteCode.BALOAD:
            case ByteCode.BASTORE:
            case ByteCode.BREAKPOINT:
            case ByteCode.CALOAD:
            case ByteCode.CASTORE:
            case ByteCode.D2F:
            case ByteCode.D2I:
            case ByteCode.D2L:
            case ByteCode.DADD:
            case ByteCode.DALOAD:
            case ByteCode.DASTORE:
            case ByteCode.DCMPG:
            case ByteCode.DCMPL:
            case ByteCode.DCONST_0:
            case ByteCode.DCONST_1:
            case ByteCode.DDIV:
            case ByteCode.DLOAD_0:
            case ByteCode.DLOAD_1:
            case ByteCode.DLOAD_2:
            case ByteCode.DLOAD_3:
            case ByteCode.DMUL:
            case ByteCode.DNEG:
            case ByteCode.DREM:
            case ByteCode.DRETURN:
            case ByteCode.DSTORE_0:
            case ByteCode.DSTORE_1:
            case ByteCode.DSTORE_2:
            case ByteCode.DSTORE_3:
            case ByteCode.DSUB:
            case ByteCode.DUP2:
            case ByteCode.DUP2_X1:
            case ByteCode.DUP2_X2:
            case ByteCode.DUP:
            case ByteCode.DUP_X1:
            case ByteCode.DUP_X2:
            case ByteCode.F2D:
            case ByteCode.F2I:
            case ByteCode.F2L:
            case ByteCode.FADD:
            case ByteCode.FALOAD:
            case ByteCode.FASTORE:
            case ByteCode.FCMPG:
            case ByteCode.FCMPL:
            case ByteCode.FCONST_0:
            case ByteCode.FCONST_1:
            case ByteCode.FCONST_2:
            case ByteCode.FDIV:
            case ByteCode.FLOAD_0:
            case ByteCode.FLOAD_1:
            case ByteCode.FLOAD_2:
            case ByteCode.FLOAD_3:
            case ByteCode.FMUL:
            case ByteCode.FNEG:
            case ByteCode.FREM:
            case ByteCode.FRETURN:
            case ByteCode.FSTORE_0:
            case ByteCode.FSTORE_1:
            case ByteCode.FSTORE_2:
            case ByteCode.FSTORE_3:
            case ByteCode.FSUB:
            case ByteCode.I2B:
            case ByteCode.I2C:
            case ByteCode.I2D:
            case ByteCode.I2F:
            case ByteCode.I2L:
            case ByteCode.I2S:
            case ByteCode.IADD:
            case ByteCode.IALOAD:
            case ByteCode.IAND:
            case ByteCode.IASTORE:
            case ByteCode.ICONST_0:
            case ByteCode.ICONST_1:
            case ByteCode.ICONST_2:
            case ByteCode.ICONST_3:
            case ByteCode.ICONST_4:
            case ByteCode.ICONST_5:
            case ByteCode.ICONST_M1:
            case ByteCode.IDIV:
            case ByteCode.ILOAD_0:
            case ByteCode.ILOAD_1:
            case ByteCode.ILOAD_2:
            case ByteCode.ILOAD_3:
            case ByteCode.IMPDEP1:
            case ByteCode.IMPDEP2:
            case ByteCode.IMUL:
            case ByteCode.INEG:
            case ByteCode.IOR:
            case ByteCode.IREM:
            case ByteCode.IRETURN:
            case ByteCode.ISHL:
            case ByteCode.ISHR:
            case ByteCode.ISTORE_0:
            case ByteCode.ISTORE_1:
            case ByteCode.ISTORE_2:
            case ByteCode.ISTORE_3:
            case ByteCode.ISUB:
            case ByteCode.IUSHR:
            case ByteCode.IXOR:
            case ByteCode.L2D:
            case ByteCode.L2F:
            case ByteCode.L2I:
            case ByteCode.LADD:
            case ByteCode.LALOAD:
            case ByteCode.LAND:
            case ByteCode.LASTORE:
            case ByteCode.LCMP:
            case ByteCode.LCONST_0:
            case ByteCode.LCONST_1:
            case ByteCode.LDIV:
            case ByteCode.LLOAD_0:
            case ByteCode.LLOAD_1:
            case ByteCode.LLOAD_2:
            case ByteCode.LLOAD_3:
            case ByteCode.LMUL:
            case ByteCode.LNEG:
            case ByteCode.LOR:
            case ByteCode.LREM:
            case ByteCode.LRETURN:
            case ByteCode.LSHL:
            case ByteCode.LSHR:
            case ByteCode.LSTORE_0:
            case ByteCode.LSTORE_1:
            case ByteCode.LSTORE_2:
            case ByteCode.LSTORE_3:
            case ByteCode.LSUB:
            case ByteCode.LUSHR:
            case ByteCode.LXOR:
            case ByteCode.MONITORENTER:
            case ByteCode.MONITOREXIT:
            case ByteCode.NOP:
            case ByteCode.POP2:
            case ByteCode.POP:
            case ByteCode.RETURN:
            case ByteCode.SALOAD:
            case ByteCode.SASTORE:
            case ByteCode.SWAP:
            case ByteCode.WIDE:
                return 0;

            case ByteCode.ALOAD:
            case ByteCode.ASTORE:
            case ByteCode.BIPUSH:
            case ByteCode.DLOAD:
            case ByteCode.DSTORE:
            case ByteCode.FLOAD:
            case ByteCode.FSTORE:
            case ByteCode.ILOAD:
            case ByteCode.ISTORE:
            case ByteCode.LDC:
            case ByteCode.LLOAD:
            case ByteCode.LSTORE:
            case ByteCode.NEWARRAY:
            case ByteCode.RET:
                return 1;

            case ByteCode.ANEWARRAY:
            case ByteCode.CHECKCAST:
            case ByteCode.GETFIELD:
            case ByteCode.GETSTATIC:
            case ByteCode.GOTO:
            case ByteCode.IFEQ:
            case ByteCode.IFGE:
            case ByteCode.IFGT:
            case ByteCode.IFLE:
            case ByteCode.IFLT:
            case ByteCode.IFNE:
            case ByteCode.IFNONNULL:
            case ByteCode.IFNULL:
            case ByteCode.IF_ACMPEQ:
            case ByteCode.IF_ACMPNE:
            case ByteCode.IF_ICMPEQ:
            case ByteCode.IF_ICMPGE:
            case ByteCode.IF_ICMPGT:
            case ByteCode.IF_ICMPLE:
            case ByteCode.IF_ICMPLT:
            case ByteCode.IF_ICMPNE:
            case ByteCode.IINC:
            case ByteCode.INSTANCEOF:
            case ByteCode.INVOKEINTERFACE:
            case ByteCode.INVOKESPECIAL:
            case ByteCode.INVOKESTATIC:
            case ByteCode.INVOKEVIRTUAL:
            case ByteCode.JSR:
            case ByteCode.LDC2_W:
            case ByteCode.LDC_W:
            case ByteCode.NEW:
            case ByteCode.PUTFIELD:
            case ByteCode.PUTSTATIC:
            case ByteCode.SIPUSH:
                return 2;

            case ByteCode.MULTIANEWARRAY:
                return 3;

            case ByteCode.GOTO_W:
            case ByteCode.JSR_W:
                return 4;

            case ByteCode.LOOKUPSWITCH:    // depends on alignment
            case ByteCode.TABLESWITCH: // depends on alignment
                return -1;
        }
        throw new IllegalArgumentException("Bad opcode: "+opcode);
    }
*/

    @SuppressWarnings("unused")
    private static String bytecodeStr(int code)
    {
        if (DEBUGSTACK || DEBUGCODE) {
            switch (code) {
                case ByteCode.NOP:              return "nop";
                case ByteCode.ACONST_NULL:      return "aconst_null";
                case ByteCode.ICONST_M1:        return "iconst_m1";
                case ByteCode.ICONST_0:         return "iconst_0";
                case ByteCode.ICONST_1:         return "iconst_1";
                case ByteCode.ICONST_2:         return "iconst_2";
                case ByteCode.ICONST_3:         return "iconst_3";
                case ByteCode.ICONST_4:         return "iconst_4";
                case ByteCode.ICONST_5:         return "iconst_5";
                case ByteCode.LCONST_0:         return "lconst_0";
                case ByteCode.LCONST_1:         return "lconst_1";
                case ByteCode.FCONST_0:         return "fconst_0";
                case ByteCode.FCONST_1:         return "fconst_1";
                case ByteCode.FCONST_2:         return "fconst_2";
                case ByteCode.DCONST_0:         return "dconst_0";
                case ByteCode.DCONST_1:         return "dconst_1";
                case ByteCode.BIPUSH:           return "bipush";
                case ByteCode.SIPUSH:           return "sipush";
                case ByteCode.LDC:              return "ldc";
                case ByteCode.LDC_W:            return "ldc_w";
                case ByteCode.LDC2_W:           return "ldc2_w";
                case ByteCode.ILOAD:            return "iload";
                case ByteCode.LLOAD:            return "lload";
                case ByteCode.FLOAD:            return "fload";
                case ByteCode.DLOAD:            return "dload";
                case ByteCode.ALOAD:            return "aload";
                case ByteCode.ILOAD_0:          return "iload_0";
                case ByteCode.ILOAD_1:          return "iload_1";
                case ByteCode.ILOAD_2:          return "iload_2";
                case ByteCode.ILOAD_3:          return "iload_3";
                case ByteCode.LLOAD_0:          return "lload_0";
                case ByteCode.LLOAD_1:          return "lload_1";
                case ByteCode.LLOAD_2:          return "lload_2";
                case ByteCode.LLOAD_3:          return "lload_3";
                case ByteCode.FLOAD_0:          return "fload_0";
                case ByteCode.FLOAD_1:          return "fload_1";
                case ByteCode.FLOAD_2:          return "fload_2";
                case ByteCode.FLOAD_3:          return "fload_3";
                case ByteCode.DLOAD_0:          return "dload_0";
                case ByteCode.DLOAD_1:          return "dload_1";
                case ByteCode.DLOAD_2:          return "dload_2";
                case ByteCode.DLOAD_3:          return "dload_3";
                case ByteCode.ALOAD_0:          return "aload_0";
                case ByteCode.ALOAD_1:          return "aload_1";
                case ByteCode.ALOAD_2:          return "aload_2";
                case ByteCode.ALOAD_3:          return "aload_3";
                case ByteCode.IALOAD:           return "iaload";
                case ByteCode.LALOAD:           return "laload";
                case ByteCode.FALOAD:           return "faload";
                case ByteCode.DALOAD:           return "daload";
                case ByteCode.AALOAD:           return "aaload";
                case ByteCode.BALOAD:           return "baload";
                case ByteCode.CALOAD:           return "caload";
                case ByteCode.SALOAD:           return "saload";
                case ByteCode.ISTORE:           return "istore";
                case ByteCode.LSTORE:           return "lstore";
                case ByteCode.FSTORE:           return "fstore";
                case ByteCode.DSTORE:           return "dstore";
                case ByteCode.ASTORE:           return "astore";
                case ByteCode.ISTORE_0:         return "istore_0";
                case ByteCode.ISTORE_1:         return "istore_1";
                case ByteCode.ISTORE_2:         return "istore_2";
                case ByteCode.ISTORE_3:         return "istore_3";
                case ByteCode.LSTORE_0:         return "lstore_0";
                case ByteCode.LSTORE_1:         return "lstore_1";
                case ByteCode.LSTORE_2:         return "lstore_2";
                case ByteCode.LSTORE_3:         return "lstore_3";
                case ByteCode.FSTORE_0:         return "fstore_0";
                case ByteCode.FSTORE_1:         return "fstore_1";
                case ByteCode.FSTORE_2:         return "fstore_2";
                case ByteCode.FSTORE_3:         return "fstore_3";
                case ByteCode.DSTORE_0:         return "dstore_0";
                case ByteCode.DSTORE_1:         return "dstore_1";
                case ByteCode.DSTORE_2:         return "dstore_2";
                case ByteCode.DSTORE_3:         return "dstore_3";
                case ByteCode.ASTORE_0:         return "astore_0";
                case ByteCode.ASTORE_1:         return "astore_1";
                case ByteCode.ASTORE_2:         return "astore_2";
                case ByteCode.ASTORE_3:         return "astore_3";
                case ByteCode.IASTORE:          return "iastore";
                case ByteCode.LASTORE:          return "lastore";
                case ByteCode.FASTORE:          return "fastore";
                case ByteCode.DASTORE:          return "dastore";
                case ByteCode.AASTORE:          return "aastore";
                case ByteCode.BASTORE:          return "bastore";
                case ByteCode.CASTORE:          return "castore";
                case ByteCode.SASTORE:          return "sastore";
                case ByteCode.POP:              return "pop";
                case ByteCode.POP2:             return "pop2";
                case ByteCode.DUP:              return "dup";
                case ByteCode.DUP_X1:           return "dup_x1";
                case ByteCode.DUP_X2:           return "dup_x2";
                case ByteCode.DUP2:             return "dup2";
                case ByteCode.DUP2_X1:          return "dup2_x1";
                case ByteCode.DUP2_X2:          return "dup2_x2";
                case ByteCode.SWAP:             return "swap";
                case ByteCode.IADD:             return "iadd";
                case ByteCode.LADD:             return "ladd";
                case ByteCode.FADD:             return "fadd";
                case ByteCode.DADD:             return "dadd";
                case ByteCode.ISUB:             return "isub";
                case ByteCode.LSUB:             return "lsub";
                case ByteCode.FSUB:             return "fsub";
                case ByteCode.DSUB:             return "dsub";
                case ByteCode.IMUL:             return "imul";
                case ByteCode.LMUL:             return "lmul";
                case ByteCode.FMUL:             return "fmul";
                case ByteCode.DMUL:             return "dmul";
                case ByteCode.IDIV:             return "idiv";
                case ByteCode.LDIV:             return "ldiv";
                case ByteCode.FDIV:             return "fdiv";
                case ByteCode.DDIV:             return "ddiv";
                case ByteCode.IREM:             return "irem";
                case ByteCode.LREM:             return "lrem";
                case ByteCode.FREM:             return "frem";
                case ByteCode.DREM:             return "drem";
                case ByteCode.INEG:             return "ineg";
                case ByteCode.LNEG:             return "lneg";
                case ByteCode.FNEG:             return "fneg";
                case ByteCode.DNEG:             return "dneg";
                case ByteCode.ISHL:             return "ishl";
                case ByteCode.LSHL:             return "lshl";
                case ByteCode.ISHR:             return "ishr";
                case ByteCode.LSHR:             return "lshr";
                case ByteCode.IUSHR:            return "iushr";
                case ByteCode.LUSHR:            return "lushr";
                case ByteCode.IAND:             return "iand";
                case ByteCode.LAND:             return "land";
                case ByteCode.IOR:              return "ior";
                case ByteCode.LOR:              return "lor";
                case ByteCode.IXOR:             return "ixor";
                case ByteCode.LXOR:             return "lxor";
                case ByteCode.IINC:             return "iinc";
                case ByteCode.I2L:              return "i2l";
                case ByteCode.I2F:              return "i2f";
                case ByteCode.I2D:              return "i2d";
                case ByteCode.L2I:              return "l2i";
                case ByteCode.L2F:              return "l2f";
                case ByteCode.L2D:              return "l2d";
                case ByteCode.F2I:              return "f2i";
                case ByteCode.F2L:              return "f2l";
                case ByteCode.F2D:              return "f2d";
                case ByteCode.D2I:              return "d2i";
                case ByteCode.D2L:              return "d2l";
                case ByteCode.D2F:              return "d2f";
                case ByteCode.I2B:              return "i2b";
                case ByteCode.I2C:              return "i2c";
                case ByteCode.I2S:              return "i2s";
                case ByteCode.LCMP:             return "lcmp";
                case ByteCode.FCMPL:            return "fcmpl";
                case ByteCode.FCMPG:            return "fcmpg";
                case ByteCode.DCMPL:            return "dcmpl";
                case ByteCode.DCMPG:            return "dcmpg";
                case ByteCode.IFEQ:             return "ifeq";
                case ByteCode.IFNE:             return "ifne";
                case ByteCode.IFLT:             return "iflt";
                case ByteCode.IFGE:             return "ifge";
                case ByteCode.IFGT:             return "ifgt";
                case ByteCode.IFLE:             return "ifle";
                case ByteCode.IF_ICMPEQ:        return "if_icmpeq";
                case ByteCode.IF_ICMPNE:        return "if_icmpne";
                case ByteCode.IF_ICMPLT:        return "if_icmplt";
                case ByteCode.IF_ICMPGE:        return "if_icmpge";
                case ByteCode.IF_ICMPGT:        return "if_icmpgt";
                case ByteCode.IF_ICMPLE:        return "if_icmple";
                case ByteCode.IF_ACMPEQ:        return "if_acmpeq";
                case ByteCode.IF_ACMPNE:        return "if_acmpne";
                case ByteCode.GOTO:             return "goto";
                case ByteCode.JSR:              return "jsr";
                case ByteCode.RET:              return "ret";
                case ByteCode.TABLESWITCH:      return "tableswitch";
                case ByteCode.LOOKUPSWITCH:     return "lookupswitch";
                case ByteCode.IRETURN:          return "ireturn";
                case ByteCode.LRETURN:          return "lreturn";
                case ByteCode.FRETURN:          return "freturn";
                case ByteCode.DRETURN:          return "dreturn";
                case ByteCode.ARETURN:          return "areturn";
                case ByteCode.RETURN:           return "return";
                case ByteCode.GETSTATIC:        return "getstatic";
                case ByteCode.PUTSTATIC:        return "putstatic";
                case ByteCode.GETFIELD:         return "getfield";
                case ByteCode.PUTFIELD:         return "putfield";
                case ByteCode.INVOKEVIRTUAL:    return "invokevirtual";
                case ByteCode.INVOKESPECIAL:    return "invokespecial";
                case ByteCode.INVOKESTATIC:     return "invokestatic";
                case ByteCode.INVOKEINTERFACE:  return "invokeinterface";
                case ByteCode.NEW:              return "new";
                case ByteCode.NEWARRAY:         return "newarray";
                case ByteCode.ANEWARRAY:        return "anewarray";
                case ByteCode.ARRAYLENGTH:      return "arraylength";
                case ByteCode.ATHROW:           return "athrow";
                case ByteCode.CHECKCAST:        return "checkcast";
                case ByteCode.INSTANCEOF:       return "instanceof";
                case ByteCode.MONITORENTER:     return "monitorenter";
                case ByteCode.MONITOREXIT:      return "monitorexit";
                case ByteCode.WIDE:             return "wide";
                case ByteCode.MULTIANEWARRAY:   return "multianewarray";
                case ByteCode.IFNULL:           return "ifnull";
                case ByteCode.IFNONNULL:        return "ifnonnull";
                case ByteCode.GOTO_W:           return "goto_w";
                case ByteCode.JSR_W:            return "jsr_w";
                case ByteCode.BREAKPOINT:       return "breakpoint";

                case ByteCode.IMPDEP1:          return "impdep1";
                case ByteCode.IMPDEP2:          return "impdep2";
            }
        }
        return "";
    }

    final char[] getCharBuffer(int minimalSize)
    {
        if (minimalSize > tmpCharBuffer.length) {
            int newSize = tmpCharBuffer.length * 2;
            if (minimalSize > newSize) { newSize = minimalSize; }
            tmpCharBuffer = new char[newSize];
        }
        return tmpCharBuffer;
    }

    /**
     * Add a pc as the start of super block.
     *
     * A pc is the beginning of a super block if:
     * - pc == 0
     * - it is the target of a branch instruction
     * - it is the beginning of an exception handler
     * - it is directly after an unconditional jump
     */
    private void addSuperBlockStart(int pc) {
        if (GenerateStackMap) {
            if (itsSuperBlockStarts == null) {
                itsSuperBlockStarts = new int[SuperBlockStartsSize];
            } else if (itsSuperBlockStarts.length == itsSuperBlockStartsTop) {
                int[] tmp = new int[itsSuperBlockStartsTop * 2];
                System.arraycopy(itsSuperBlockStarts, 0, tmp, 0,
                                 itsSuperBlockStartsTop);
                itsSuperBlockStarts = tmp;
            }
            itsSuperBlockStarts[itsSuperBlockStartsTop++] = pc;
        }
    }

    /**
     * Sort the list of recorded super block starts and remove duplicates.
     *
     * Also adds exception handling blocks as block starts, since there is no
     * explicit control flow to these. Used for stack map table generation.
     */
    private void finalizeSuperBlockStarts() {
        if (GenerateStackMap) {
            for (int i = 0; i < itsExceptionTableTop; i++) {
                ExceptionTableEntry ete = itsExceptionTable[i];
                short handlerPC = (short) getLabelPC(ete.itsHandlerLabel);
                addSuperBlockStart(handlerPC);
            }
            Arrays.sort(itsSuperBlockStarts, 0, itsSuperBlockStartsTop);
            int prev = itsSuperBlockStarts[0];
            int copyTo = 1;
            for (int i = 1; i < itsSuperBlockStartsTop; i++) {
                int curr = itsSuperBlockStarts[i];
                if (prev != curr) {
                    if (copyTo != i) {
                        itsSuperBlockStarts[copyTo] = curr;
                    }
                    copyTo++;
                    prev = curr;
                }
            }
            itsSuperBlockStartsTop = copyTo;
            if (itsSuperBlockStarts[copyTo - 1] == itsCodeBufferTop) {
                itsSuperBlockStartsTop--;
            }
        }
    }

    private int[] itsSuperBlockStarts = null;
    private int itsSuperBlockStartsTop = 0;
    private static final int SuperBlockStartsSize = 4;

    // Used to find blocks of code with no dependencies (aka dead code).
    // Necessary for generating type information for dead code, which is
    // expected by the Sun verifier. It is only necessary to store a single
    // jump source to determine if a block is reachable or not.
    private UintMap itsJumpFroms = null;

    private static final int LineNumberTableSize = 16;
    private static final int ExceptionTableSize = 4;

    private static final int MajorVersion;
    private static final int MinorVersion;
    private static final boolean GenerateStackMap;

    static {
        // Figure out which classfile version should be generated. This assumes
        // that the runtime used to compile the JavaScript files is the same as
        // the one used to run them. This is important because there are cases
        // when bytecode is generated at runtime, where it is not easy to pass
        // along what version is necessary. Instead, we grab the version numbers
        // from the bytecode of this class and use that.
        //
        // Based on the version numbers we scrape, we can also determine what
        // bytecode features we need. For example, Java 6 bytecode (classfile
        // version 50) should have stack maps generated.
        InputStream is = null;
        int major = 48, minor = 0;
        try {
            is = ClassFileWriter.class.getResourceAsStream("ClassFileWriter.class");
            if (is == null) {
                is = ClassLoader.getSystemResourceAsStream(
                    "org/mozilla/classfile/ClassFileWriter.class");
            }
            byte[] header = new byte[8];
            // read loop is required since JDK7 will only provide 2 bytes
            // on the first read() - see bug #630111
            int read = 0;
            while (read < 8) {
                int c = is.read(header, read, 8 - read);
                if (c < 0) throw new IOException();
                read += c;
            }
            minor = (header[4] << 8) | (header[5] & 0xff);
            major = (header[6] << 8) | (header[7] & 0xff);
        } catch (Exception e) {
            // Unable to get class file, use default bytecode version
        } finally {
            MinorVersion = minor;
            MajorVersion = major;
            GenerateStackMap = major >= 50;
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                }
            }
        }
    }

    private final static int FileHeaderConstant = 0xCAFEBABE;
    // Set DEBUG flags to true to get better checking and progress info.
    private static final boolean DEBUGSTACK = false;
    private static final boolean DEBUGLABELS = false;
    private static final boolean DEBUGCODE = false;

    private String generatedClassName;

    private ExceptionTableEntry itsExceptionTable[];
    private int itsExceptionTableTop;

    private int itsLineNumberTable[];   // pack start_pc & line_number together
    private int itsLineNumberTableTop;

    private byte[] itsCodeBuffer = new byte[256];
    private int itsCodeBufferTop;

    private ConstantPool itsConstantPool;

    private ClassFileMethod itsCurrentMethod;
    private short itsStackTop;

    private short itsMaxStack;
    private short itsMaxLocals;

    private ObjArray itsMethods = new ObjArray();
    private ObjArray itsFields = new ObjArray();
    private ObjArray itsInterfaces = new ObjArray();

    private short itsFlags;
    private short itsThisClassIndex;
    private short itsSuperClassIndex;
    private short itsSourceFileNameIndex;

    private static final int MIN_LABEL_TABLE_SIZE = 32;
    private int[] itsLabelTable;
    private int itsLabelTableTop;

// itsFixupTable[i] = (label_index << 32) | fixup_site
    private static final int MIN_FIXUP_TABLE_SIZE = 40;
    private long[] itsFixupTable;
    private int itsFixupTableTop;
    private ObjArray itsVarDescriptors;

    private char[] tmpCharBuffer = new char[64];
}

final class ExceptionTableEntry
{

    ExceptionTableEntry(int startLabel, int endLabel,
                        int handlerLabel, short catchType)
    {
        itsStartLabel = startLabel;
        itsEndLabel = endLabel;
        itsHandlerLabel = handlerLabel;
        itsCatchType = catchType;
    }

    int itsStartLabel;
    int itsEndLabel;
    int itsHandlerLabel;
    short itsCatchType;
}

final class ClassFileField
{

    ClassFileField(short nameIndex, short typeIndex, short flags)
    {
        itsNameIndex = nameIndex;
        itsTypeIndex = typeIndex;
        itsFlags = flags;
        itsHasAttributes = false;
    }

    void setAttributes(short attr1, short attr2, short attr3, int index)
    {
        itsHasAttributes = true;
        itsAttr1 = attr1;
        itsAttr2 = attr2;
        itsAttr3 = attr3;
        itsIndex = index;
    }

    int write(byte[] data, int offset)
    {
        offset = ClassFileWriter.putInt16(itsFlags, data, offset);
        offset = ClassFileWriter.putInt16(itsNameIndex, data, offset);
        offset = ClassFileWriter.putInt16(itsTypeIndex, data, offset);
        if (!itsHasAttributes) {
            // write 0 attributes
            offset = ClassFileWriter.putInt16(0, data, offset);
        } else {
            offset = ClassFileWriter.putInt16(1, data, offset);
            offset = ClassFileWriter.putInt16(itsAttr1, data, offset);
            offset = ClassFileWriter.putInt16(itsAttr2, data, offset);
            offset = ClassFileWriter.putInt16(itsAttr3, data, offset);
            offset = ClassFileWriter.putInt16(itsIndex, data, offset);
        }
        return offset;
    }

    int getWriteSize()
    {
        int size = 2 * 3;
        if (!itsHasAttributes) {
            size += 2;
        } else {
            size += 2 + 2 * 4;
        }
        return size;
    }

    private short itsNameIndex;
    private short itsTypeIndex;
    private short itsFlags;
    private boolean itsHasAttributes;
    private short itsAttr1, itsAttr2, itsAttr3;
    private int itsIndex;
}

final class ClassFileMethod
{

    ClassFileMethod(String name, short nameIndex, String type, short typeIndex,
                    short flags)
    {
        itsName = name;
        itsNameIndex = nameIndex;
        itsType = type;
        itsTypeIndex = typeIndex;
        itsFlags = flags;
    }

    void setCodeAttribute(byte codeAttribute[])
    {
        itsCodeAttribute = codeAttribute;
    }

    int write(byte[] data, int offset)
    {
        offset = ClassFileWriter.putInt16(itsFlags, data, offset);
        offset = ClassFileWriter.putInt16(itsNameIndex, data, offset);
        offset = ClassFileWriter.putInt16(itsTypeIndex, data, offset);
        // Code attribute only
        offset = ClassFileWriter.putInt16(1, data, offset);
        System.arraycopy(itsCodeAttribute, 0, data, offset,
                         itsCodeAttribute.length);
        offset += itsCodeAttribute.length;
        return offset;
    }

    int getWriteSize()
    {
        return 2 * 4 + itsCodeAttribute.length;
    }

    String getName()
    {
        return itsName;
    }

    String getType()
    {
        return itsType;
    }

    short getFlags()
    {
        return itsFlags;
    }

    private String itsName;
    private String itsType;
    private short itsNameIndex;
    private short itsTypeIndex;
    private short itsFlags;
    private byte[] itsCodeAttribute;

}

final class ConstantPool
{

    ConstantPool(ClassFileWriter cfw)
    {
        this.cfw = cfw;
        itsTopIndex = 1;       // the zero'th entry is reserved
        itsPool = new byte[ConstantPoolSize];
        itsTop = 0;
    }

    private static final int ConstantPoolSize = 256;
    static final byte
        CONSTANT_Class = 7,
        CONSTANT_Fieldref = 9,
        CONSTANT_Methodref = 10,
        CONSTANT_InterfaceMethodref = 11,
        CONSTANT_String = 8,
        CONSTANT_Integer = 3,
        CONSTANT_Float = 4,
        CONSTANT_Long = 5,
        CONSTANT_Double = 6,
        CONSTANT_NameAndType = 12,
        CONSTANT_Utf8 = 1;

    int write(byte[] data, int offset)
    {
        offset = ClassFileWriter.putInt16((short)itsTopIndex, data, offset);
        System.arraycopy(itsPool, 0, data, offset, itsTop);
        offset += itsTop;
        return offset;
    }

    int getWriteSize()
    {
        return 2 + itsTop;
    }

    int addConstant(int k)
    {
        ensure(5);
        itsPool[itsTop++] = CONSTANT_Integer;
        itsTop = ClassFileWriter.putInt32(k, itsPool, itsTop);
        itsPoolTypes.put(itsTopIndex, CONSTANT_Integer);
        return (short)(itsTopIndex++);
    }

    int addConstant(long k)
    {
        ensure(9);
        itsPool[itsTop++] = CONSTANT_Long;
        itsTop = ClassFileWriter.putInt64(k, itsPool, itsTop);
        int index = itsTopIndex;
        itsTopIndex += 2;
        itsPoolTypes.put(index, CONSTANT_Long);
        return index;
    }

    int addConstant(float k)
    {
        ensure(5);
        itsPool[itsTop++] = CONSTANT_Float;
        int bits = Float.floatToIntBits(k);
        itsTop = ClassFileWriter.putInt32(bits, itsPool, itsTop);
        itsPoolTypes.put(itsTopIndex, CONSTANT_Float);
        return itsTopIndex++;
    }

    int addConstant(double k)
    {
        ensure(9);
        itsPool[itsTop++] = CONSTANT_Double;
        long bits = Double.doubleToLongBits(k);
        itsTop = ClassFileWriter.putInt64(bits, itsPool, itsTop);
        int index = itsTopIndex;
        itsTopIndex += 2;
        itsPoolTypes.put(index, CONSTANT_Double);
        return index;
    }

    int addConstant(String k)
    {
        int utf8Index = 0xFFFF & addUtf8(k);
        int theIndex = itsStringConstHash.getInt(utf8Index, -1);
        if (theIndex == -1) {
            theIndex = itsTopIndex++;
            ensure(3);
            itsPool[itsTop++] = CONSTANT_String;
            itsTop = ClassFileWriter.putInt16(utf8Index, itsPool, itsTop);
            itsStringConstHash.put(utf8Index, theIndex);
        }
        itsPoolTypes.put(theIndex, CONSTANT_String);
        return theIndex;
    }

    boolean isUnderUtfEncodingLimit(String s)
    {
        int strLen = s.length();
        if (strLen * 3 <= MAX_UTF_ENCODING_SIZE) {
            return true;
        } else if (strLen > MAX_UTF_ENCODING_SIZE) {
            return false;
        }
        return strLen == getUtfEncodingLimit(s, 0, strLen);
    }

    /**
     * Get maximum i such that <tt>start <= i <= end</tt> and
     * <tt>s.substring(start, i)</tt> fits JVM UTF string encoding limit.
     */
    int getUtfEncodingLimit(String s, int start, int end)
    {
        if ((end - start) * 3 <= MAX_UTF_ENCODING_SIZE) {
            return end;
        }
        int limit = MAX_UTF_ENCODING_SIZE;
        for (int i = start; i != end; i++) {
            int c = s.charAt(i);
            if (0 != c && c <= 0x7F) {
                --limit;
            } else if (c < 0x7FF) {
                limit -= 2;
            } else {
                limit -= 3;
            }
            if (limit < 0) {
                return i;
            }
        }
        return end;
    }

    short addUtf8(String k)
    {
        int theIndex = itsUtf8Hash.get(k, -1);
        if (theIndex == -1) {
            int strLen = k.length();
            boolean tooBigString;
            if (strLen > MAX_UTF_ENCODING_SIZE) {
                tooBigString = true;
            } else {
                tooBigString = false;
                // Ask for worst case scenario buffer when each char takes 3
                // bytes
                ensure(1 + 2 + strLen * 3);
                int top = itsTop;

                itsPool[top++] = CONSTANT_Utf8;
                top += 2; // skip length

                char[] chars = cfw.getCharBuffer(strLen);
                k.getChars(0, strLen, chars, 0);

                for (int i = 0; i != strLen; i++) {
                    int c = chars[i];
                    if (c != 0 && c <= 0x7F) {
                        itsPool[top++] = (byte)c;
                    } else if (c > 0x7FF) {
                        itsPool[top++] = (byte)(0xE0 | (c >> 12));
                        itsPool[top++] = (byte)(0x80 | ((c >> 6) & 0x3F));
                        itsPool[top++] = (byte)(0x80 | (c & 0x3F));
                    } else {
                        itsPool[top++] = (byte)(0xC0 | (c >> 6));
                        itsPool[top++] = (byte)(0x80 | (c & 0x3F));
                    }
                }

                int utfLen = top - (itsTop + 1 + 2);
                if (utfLen > MAX_UTF_ENCODING_SIZE) {
                    tooBigString = true;
                } else {
                    // Write back length
                    itsPool[itsTop + 1] = (byte)(utfLen >>> 8);
                    itsPool[itsTop + 2] = (byte)utfLen;

                    itsTop = top;
                    theIndex = itsTopIndex++;
                    itsUtf8Hash.put(k, theIndex);
                }
            }
            if (tooBigString) {
                throw new IllegalArgumentException("Too big string");
            }
        }
        setConstantData(theIndex, k);
        itsPoolTypes.put(theIndex, CONSTANT_Utf8);
        return (short)theIndex;
    }

    private short addNameAndType(String name, String type)
    {
        short nameIndex = addUtf8(name);
        short typeIndex = addUtf8(type);
        ensure(5);
        itsPool[itsTop++] = CONSTANT_NameAndType;
        itsTop = ClassFileWriter.putInt16(nameIndex, itsPool, itsTop);
        itsTop = ClassFileWriter.putInt16(typeIndex, itsPool, itsTop);
        itsPoolTypes.put(itsTopIndex, CONSTANT_NameAndType);
        return (short)(itsTopIndex++);
    }

    short addClass(String className)
    {
        int theIndex = itsClassHash.get(className, -1);
        if (theIndex == -1) {
            String slashed = className;
            if (className.indexOf('.') > 0) {
                slashed = ClassFileWriter.getSlashedForm(className);
                theIndex = itsClassHash.get(slashed, -1);
                if (theIndex != -1) {
                    itsClassHash.put(className, theIndex);
                }
            }
            if (theIndex == -1) {
                int utf8Index = addUtf8(slashed);
                ensure(3);
                itsPool[itsTop++] = CONSTANT_Class;
                itsTop = ClassFileWriter.putInt16(utf8Index, itsPool, itsTop);
                theIndex = itsTopIndex++;
                itsClassHash.put(slashed, theIndex);
                if (className != slashed) {
                    itsClassHash.put(className, theIndex);
                }
            }
        }
        setConstantData(theIndex, className);
        itsPoolTypes.put(theIndex, CONSTANT_Class);
        return (short)theIndex;
    }

    short addFieldRef(String className, String fieldName, String fieldType)
    {
        FieldOrMethodRef ref = new FieldOrMethodRef(className, fieldName,
                                                    fieldType);

        int theIndex = itsFieldRefHash.get(ref, -1);
        if (theIndex == -1) {
            short ntIndex = addNameAndType(fieldName, fieldType);
            short classIndex = addClass(className);
            ensure(5);
            itsPool[itsTop++] = CONSTANT_Fieldref;
            itsTop = ClassFileWriter.putInt16(classIndex, itsPool, itsTop);
            itsTop = ClassFileWriter.putInt16(ntIndex, itsPool, itsTop);
            theIndex = itsTopIndex++;
            itsFieldRefHash.put(ref, theIndex);
        }
        setConstantData(theIndex, ref);
        itsPoolTypes.put(theIndex, CONSTANT_Fieldref);
        return (short)theIndex;
    }

    short addMethodRef(String className, String methodName,
                       String methodType)
    {
        FieldOrMethodRef ref = new FieldOrMethodRef(className, methodName,
                                                    methodType);

        int theIndex = itsMethodRefHash.get(ref, -1);
        if (theIndex == -1) {
            short ntIndex = addNameAndType(methodName, methodType);
            short classIndex = addClass(className);
            ensure(5);
            itsPool[itsTop++] = CONSTANT_Methodref;
            itsTop = ClassFileWriter.putInt16(classIndex, itsPool, itsTop);
            itsTop = ClassFileWriter.putInt16(ntIndex, itsPool, itsTop);
            theIndex = itsTopIndex++;
            itsMethodRefHash.put(ref, theIndex);
        }
        setConstantData(theIndex, ref);
        itsPoolTypes.put(theIndex, CONSTANT_Methodref);
        return (short)theIndex;
    }

    short addInterfaceMethodRef(String className,
                                String methodName, String methodType)
    {
        short ntIndex = addNameAndType(methodName, methodType);
        short classIndex = addClass(className);
        ensure(5);
        itsPool[itsTop++] = CONSTANT_InterfaceMethodref;
        itsTop = ClassFileWriter.putInt16(classIndex, itsPool, itsTop);
        itsTop = ClassFileWriter.putInt16(ntIndex, itsPool, itsTop);
        FieldOrMethodRef r = new FieldOrMethodRef(className, methodName,
                                                  methodType);
        setConstantData(itsTopIndex, r);
        itsPoolTypes.put(itsTopIndex, CONSTANT_InterfaceMethodref);
        return (short)(itsTopIndex++);
    }

    Object getConstantData(int index)
    {
        return itsConstantData.getObject(index);
    }

    void setConstantData(int index, Object data)
    {
        itsConstantData.put(index, data);
    }

    byte getConstantType(int index)
    {
        return (byte) itsPoolTypes.getInt(index, 0);
    }

    void ensure(int howMuch)
    {
        if (itsTop + howMuch > itsPool.length) {
            int newCapacity = itsPool.length * 2;
            if (itsTop + howMuch > newCapacity) {
                newCapacity = itsTop + howMuch;
            }
            byte[] tmp = new byte[newCapacity];
            System.arraycopy(itsPool, 0, tmp, 0, itsTop);
            itsPool = tmp;
        }
    }

    private ClassFileWriter cfw;

    private static final int MAX_UTF_ENCODING_SIZE = 65535;

    private UintMap itsStringConstHash = new UintMap();
    private ObjToIntMap itsUtf8Hash = new ObjToIntMap();
    private ObjToIntMap itsFieldRefHash = new ObjToIntMap();
    private ObjToIntMap itsMethodRefHash = new ObjToIntMap();
    private ObjToIntMap itsClassHash = new ObjToIntMap();

    private int itsTop;
    private int itsTopIndex;
    private UintMap itsConstantData = new UintMap();
    private UintMap itsPoolTypes = new UintMap();
    private byte itsPool[];
}

final class FieldOrMethodRef
{
    FieldOrMethodRef(String className, String name, String type)
    {
        this.className = className;
        this.name = name;
        this.type = type;
    }

    public String getClassName()
    {
        return className;
    }

    public String getName()
    {
        return name;
    }

    public String getType()
    {
        return type;
    }

    @Override
    public boolean equals(Object obj)
    {
        if (!(obj instanceof FieldOrMethodRef)) { return false; }
        FieldOrMethodRef x = (FieldOrMethodRef)obj;
        return className.equals(x.className)
            && name.equals(x.name)
            && type.equals(x.type);
    }

    @Override
    public int hashCode()
    {
        if (hashCode == -1) {
            int h1 = className.hashCode();
            int h2 = name.hashCode();
            int h3 = type.hashCode();
            hashCode = h1 ^ h2 ^ h3;
        }
        return hashCode;
    }

    private String className;
    private String name;
    private String type;
    private int hashCode = -1;
}

/**
 * A super block is defined as a contiguous chunk of code with a single entry
 * point and multiple exit points (therefore ending in an unconditional jump
 * or the end of the method). This is used to emulate OpenJDK's compiler, which
 * outputs stack map frames at the start of every super block except the method
 * start.
 */
final class SuperBlock {
    SuperBlock(int index, int start, int end, int[] initialLocals) {
        this.index = index;
        this.start = start;
        this.end = end;
        locals = new int[initialLocals.length];
        System.arraycopy(initialLocals, 0, locals, 0, initialLocals.length);
        stack = new int[0];
        isInitialized = false;
        isInQueue = false;
    }

    int getIndex() {
        return index;
    }

    int[] getLocals() {
        int[] copy = new int[locals.length];
        System.arraycopy(locals, 0, copy, 0, locals.length);
        return copy;
    }

    /**
     * Get a copy of the super block's locals without any trailing TOP types.
     *
     * This is useful for actual writing stack maps; during the computation of
     * stack map types, all local arrays have the same size; the max locals for
     * the method. In addition, DOUBLE and LONG types have trailing TOP types
     * because they occupy two words. For writing purposes, these are not
     * useful.
     */
    int[] getTrimmedLocals() {
        int last = locals.length - 1;
        // Exclude all of the trailing TOPs not bound to a DOUBLE/LONG
        while (last >= 0 && locals[last] == TypeInfo.TOP &&
               !TypeInfo.isTwoWords(locals[last - 1])) {
            last--;
        }
        last++;
        // Exclude trailing TOPs following a DOUBLE/LONG
        int size = last;
        for (int i = 0; i < last; i++) {
            if (TypeInfo.isTwoWords(locals[i])) {
                size--;
            }
        }
        int[] copy = new int[size];
        for (int i = 0, j = 0; i < size; i++, j++) {
            copy[i] = locals[j];
            if (TypeInfo.isTwoWords(locals[j])) {
                j++;
            }
        }
        return copy;
    }

    int[] getStack() {
        int[] copy = new int[stack.length];
        System.arraycopy(stack, 0, copy, 0, stack.length);
        return copy;
    }

    boolean merge(int[] locals, int localsTop, int[] stack, int stackTop,
                  ConstantPool pool) {
        if (!isInitialized) {
            System.arraycopy(locals, 0, this.locals, 0, localsTop);
            this.stack = new int[stackTop];
            System.arraycopy(stack, 0, this.stack, 0, stackTop);
            isInitialized = true;
            return true;
        } else if (this.locals.length == localsTop &&
                   this.stack.length == stackTop) {
            boolean localsChanged = mergeState(this.locals, locals, localsTop,
                                               pool);
            boolean stackChanged = mergeState(this.stack, stack, stackTop,
                                              pool);
            return localsChanged || stackChanged;
        } else {
            if (ClassFileWriter.StackMapTable.DEBUGSTACKMAP) {
                System.out.println("bad merge");
                System.out.println("current type state:");
                TypeInfo.print(this.locals, this.stack, pool);
                System.out.println("incoming type state:");
                TypeInfo.print(locals, localsTop, stack, stackTop, pool);
            }
            throw new IllegalArgumentException("bad merge attempt");
        }
    }

    /**
     * Merge an operand stack or local variable array with incoming state.
     *
     * They are treated the same way; by this point, it should already be
     * ensured that the array sizes are the same, which is the only additional
     * constraint that is imposed on merging operand stacks (the local variable
     * array is always the same size).
     */
    private boolean mergeState(int[] current, int[] incoming, int size,
                               ConstantPool pool) {
        boolean changed = false;
        for (int i = 0; i < size; i++) {
            int currentType = current[i];

            current[i] = TypeInfo.merge(current[i], incoming[i], pool);
            if (currentType != current[i]) {
                changed = true;
            }
        }
        return changed;
    }

    int getStart() {
        return start;
    }

    int getEnd() {
        return end;
    }

    @Override
    public String toString() {
        return "sb " + index;
    }

    boolean isInitialized() {
        return isInitialized;
    }

    void setInitialized(boolean b) {
        isInitialized = b;
    }

    boolean isInQueue() {
        return isInQueue;
    }

    void setInQueue(boolean b) {
        isInQueue = b;
    }

    private int index;
    private int start;
    private int end;
    private int[] locals;
    private int[] stack;
    private boolean isInitialized;
    private boolean isInQueue;
}

/**
 * Helper class for internal representations of type information. In most
 * cases, type information can be represented by a constant, but in some
 * cases, a payload is included. Despite the payload coming after the type
 * tag in the output, we store it in bits 8-23 for uniformity; the tag is
 * always in bits 0-7.
 */
final class TypeInfo {
    private TypeInfo() { }

    static final int TOP = 0;
    static final int INTEGER = 1;
    static final int FLOAT = 2;
    static final int DOUBLE = 3;
    static final int LONG = 4;
    static final int NULL = 5;
    static final int UNINITIALIZED_THIS = 6;
    static final int OBJECT_TAG = 7;
    static final int UNINITIALIZED_VAR_TAG = 8;

    static final int OBJECT(int constantPoolIndex) {
        return ((constantPoolIndex & 0xFFFF) << 8) | OBJECT_TAG;
    }

    static final int OBJECT(String type, ConstantPool pool) {
        return OBJECT(pool.addClass(type));
    }

    static final int UNINITIALIZED_VARIABLE(int bytecodeOffset) {
        return ((bytecodeOffset & 0xFFFF) << 8) | UNINITIALIZED_VAR_TAG;
    }

    static final int getTag(int typeInfo) {
        return typeInfo & 0xFF;
    }

    static final int getPayload(int typeInfo) {
        return typeInfo >>> 8;
    }

    /**
     * Treat the result of getPayload as a constant pool index and fetch the
     * corresponding String mapped to it.
     *
     * Only works on OBJECT types.
     */
    static final String getPayloadAsType(int typeInfo, ConstantPool pool) {
        if (getTag(typeInfo) == OBJECT_TAG) {
            return (String) pool.getConstantData(getPayload(typeInfo));
        }
        throw new IllegalArgumentException("expecting object type");
    }

    /**
     * Create type information from an internal type.
     */
    static final int fromType(String type, ConstantPool pool) {
        if (type.length() == 1) {
            switch (type.charAt(0)) {
                case 'B': // sbyte
                case 'C': // unicode char
                case 'S': // short
                case 'Z': // boolean
                case 'I': // all of the above are verified as integers
                    return INTEGER;
                case 'D':
                    return DOUBLE;
                case 'F':
                    return FLOAT;
                case 'J':
                    return LONG;
                default:
                    throw new IllegalArgumentException("bad type");
            }
        }
        return TypeInfo.OBJECT(type, pool);
    }

    static boolean isTwoWords(int type) {
        return type == DOUBLE || type == LONG;
    }

    /**
     * Merge two verification types.
     *
     * In most cases, the verification types must be the same. For example,
     * INTEGER and DOUBLE cannot be merged and an exception will be thrown.
     * The basic rules are:
     *
     * - If the types are equal, simply return one.
     * - If either type is TOP, return TOP.
     * - If either type is NULL, return the other type.
     * - If both types are objects, find the lowest common ancestor in the
     *   class hierarchy.
     *
     * This method uses reflection to traverse the class hierarchy. Therefore,
     * it is assumed that the current class being generated is never the target
     * of a full object-object merge, which would need to load the current
     * class reflectively.
     */
    static int merge(int current, int incoming, ConstantPool pool) {
        int currentTag = getTag(current);
        int incomingTag = getTag(incoming);
        boolean currentIsObject = currentTag == TypeInfo.OBJECT_TAG;
        boolean incomingIsObject = incomingTag == TypeInfo.OBJECT_TAG;

        if (current == incoming || (currentIsObject && incoming == NULL)) {
            return current;
        } else if (currentTag == TypeInfo.TOP ||
            incomingTag == TypeInfo.TOP) {
            return TypeInfo.TOP;
        } else if (current == NULL && incomingIsObject) {
            return incoming;
        } else if (currentIsObject && incomingIsObject) {
            String currentName = getPayloadAsType(current, pool);
            String incomingName = getPayloadAsType(incoming, pool);
            // The class file always has the class and super names in the same
            // spot. The constant order is: class_data, class_name, super_data,
            // super_name.
            String currentlyGeneratedName = (String) pool.getConstantData(2);
            String currentlyGeneratedSuperName =
                    (String) pool.getConstantData(4);

            // If any of the merged types are the class that's currently being
            // generated, automatically start at the super class instead. At
            // this point, we already know the classes are different, so we
            // don't need to handle that case.
            if (currentName.equals(currentlyGeneratedName)) {
                currentName = currentlyGeneratedSuperName;
            }
            if (incomingName.equals(currentlyGeneratedName)) {
                incomingName = currentlyGeneratedSuperName;
            }

            Class<?> currentClass = getClassFromInternalName(currentName);
            Class<?> incomingClass = getClassFromInternalName(incomingName);

            if (currentClass.isAssignableFrom(incomingClass)) {
                return current;
            } else if (incomingClass.isAssignableFrom(currentClass)) {
                return incoming;
            } else if (incomingClass.isInterface() ||
                       currentClass.isInterface()) {
                // For verification purposes, Sun specifies that interfaces are
                // subtypes of Object. Therefore, we know that the merge result
                // involving interfaces where one is not assignable to the
                // other results in Object.
                return OBJECT("java/lang/Object", pool);
            } else {
                Class<?> commonClass = incomingClass.getSuperclass();
                while (commonClass != null) {
                    if (commonClass.isAssignableFrom(currentClass)) {
                        String name = commonClass.getName();
                        name = ClassFileWriter.getSlashedForm(name);
                        return OBJECT(name, pool);
                    }
                    commonClass = commonClass.getSuperclass();
                }
            }
        }
        throw new IllegalArgumentException("bad merge attempt between " +
                                           toString(current, pool) + " and " +
                                           toString(incoming, pool));
    }

    static String toString(int type, ConstantPool pool) {
        int tag = getTag(type);
        switch (tag) {
            case TypeInfo.TOP:
                return "top";
            case TypeInfo.INTEGER:
                return "int";
            case TypeInfo.FLOAT:
                return "float";
            case TypeInfo.DOUBLE:
                return "double";
            case TypeInfo.LONG:
                return "long";
            case TypeInfo.NULL:
                return "null";
            case TypeInfo.UNINITIALIZED_THIS:
                return "uninitialized_this";
            default:
                if (tag == TypeInfo.OBJECT_TAG) {
                    return getPayloadAsType(type, pool);
                } else if (tag == TypeInfo.UNINITIALIZED_VAR_TAG) {
                    return "uninitialized";
                } else {
                    throw new IllegalArgumentException("bad type");
                }
        }
    }

    /**
     * Take an internal name and return a java.lang.Class instance that
     * represents it.
     *
     * For example, given "java/lang/Object", returns the equivalent of
     * Class.forName("java.lang.Object"), but also handles exceptions.
     */
    static Class<?> getClassFromInternalName(String internalName) {
        try {
            return Class.forName(internalName.replace('/', '.'));
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    static String toString(int[] types, ConstantPool pool) {
        return toString(types, types.length, pool);
    }

    static String toString(int[] types, int typesTop, ConstantPool pool) {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for (int i = 0; i < typesTop; i++) {
            if (i > 0) {
                sb.append(", ");
            }
            sb.append(toString(types[i], pool));
        }
        sb.append("]");
        return sb.toString();
    }

    static void print(int[] locals, int[] stack, ConstantPool pool) {
        print(locals, locals.length, stack, stack.length, pool);
    }

    static void print(int[] locals, int localsTop, int[] stack, int stackTop,
                       ConstantPool pool) {
        System.out.print("locals: ");
        System.out.println(toString(locals, localsTop, pool));
        System.out.print("stack: ");
        System.out.println(toString(stack, stackTop, pool));
        System.out.println();
    }
}
