/*
 * Javassist, a Java-bytecode translator toolkit.
 * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License.  Alternatively, the contents of this file may be used under
 * the terms of the GNU Lesser General Public License Version 2.1 or later,
 * or the Apache License Version 2.0.
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 */
package javassist.bytecode.analysis;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import javassist.bytecode.BadBytecode;
import javassist.bytecode.CodeIterator;
import javassist.bytecode.ConstPool;
import javassist.bytecode.Descriptor;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.Opcode;

/**
 * Executor is responsible for modeling the effects of a JVM instruction on a frame.
 *
 * @author Jason T. Greene
 */
public class Executor implements Opcode {
    private final ConstPool constPool;
    private final ClassPool classPool;
    private final Type STRING_TYPE;
    private final Type CLASS_TYPE;
    private final Type THROWABLE_TYPE;
    private int lastPos;

    public Executor(ClassPool classPool, ConstPool constPool) {
        this.constPool = constPool;
        this.classPool = classPool;

        try {
            STRING_TYPE = getType("java.lang.String");
            CLASS_TYPE = getType("java.lang.Class");
            THROWABLE_TYPE = getType("java.lang.Throwable");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * Execute the instruction, modeling the effects on the specified frame and subroutine.
     * If a subroutine is passed, the access flags will be modified if this instruction accesses
     * the local variable table.
     *
     * @param method the method containing the instruction
     * @param pos the position of the instruction in the method
     * @param iter the code iterator used to find the instruction
     * @param frame the frame to modify to represent the result of the instruction
     * @param subroutine the optional subroutine this instruction belongs to.
     * @throws BadBytecode if the bytecode violates the jvm spec
     */
    public void execute(MethodInfo method, int pos, CodeIterator iter, Frame frame, Subroutine subroutine) throws BadBytecode {
        this.lastPos = pos;
        int opcode = iter.byteAt(pos);


        // Declared opcode in order
        switch (opcode) {
            case NOP:
                break;
            case ACONST_NULL:
                frame.push(Type.UNINIT);
                break;
            case ICONST_M1:
            case ICONST_0:
            case ICONST_1:
            case ICONST_2:
            case ICONST_3:
            case ICONST_4:
            case ICONST_5:
                frame.push(Type.INTEGER);
                break;
            case LCONST_0:
            case LCONST_1:
                frame.push(Type.LONG);
                frame.push(Type.TOP);
                break;
            case FCONST_0:
            case FCONST_1:
            case FCONST_2:
                frame.push(Type.FLOAT);
                break;
            case DCONST_0:
            case DCONST_1:
                frame.push(Type.DOUBLE);
                frame.push(Type.TOP);
                break;
            case BIPUSH:
            case SIPUSH:
                frame.push(Type.INTEGER);
                break;
            case LDC:
                evalLDC(iter.byteAt(pos + 1),  frame);
                break;
            case LDC_W :
            case LDC2_W :
                evalLDC(iter.u16bitAt(pos + 1), frame);
                break;
            case ILOAD:
                evalLoad(Type.INTEGER, iter.byteAt(pos + 1), frame, subroutine);
                break;
            case LLOAD:
                evalLoad(Type.LONG, iter.byteAt(pos + 1), frame, subroutine);
                break;
            case FLOAD:
                evalLoad(Type.FLOAT, iter.byteAt(pos + 1), frame, subroutine);
                break;
            case DLOAD:
                evalLoad(Type.DOUBLE, iter.byteAt(pos + 1), frame, subroutine);
                break;
            case ALOAD:
                evalLoad(Type.OBJECT, iter.byteAt(pos + 1), frame, subroutine);
                break;
            case ILOAD_0:
            case ILOAD_1:
            case ILOAD_2:
            case ILOAD_3:
                evalLoad(Type.INTEGER, opcode - ILOAD_0, frame, subroutine);
                break;
            case LLOAD_0:
            case LLOAD_1:
            case LLOAD_2:
            case LLOAD_3:
                evalLoad(Type.LONG, opcode - LLOAD_0, frame, subroutine);
                break;
            case FLOAD_0:
            case FLOAD_1:
            case FLOAD_2:
            case FLOAD_3:
                evalLoad(Type.FLOAT, opcode - FLOAD_0, frame, subroutine);
                break;
            case DLOAD_0:
            case DLOAD_1:
            case DLOAD_2:
            case DLOAD_3:
                evalLoad(Type.DOUBLE, opcode - DLOAD_0, frame, subroutine);
                break;
            case ALOAD_0:
            case ALOAD_1:
            case ALOAD_2:
            case ALOAD_3:
                evalLoad(Type.OBJECT, opcode - ALOAD_0, frame, subroutine);
                break;
            case IALOAD:
                evalArrayLoad(Type.INTEGER, frame);
                break;
            case LALOAD:
                evalArrayLoad(Type.LONG, frame);
                break;
            case FALOAD:
                evalArrayLoad(Type.FLOAT, frame);
                break;
            case DALOAD:
                evalArrayLoad(Type.DOUBLE, frame);
                break;
            case AALOAD:
                evalArrayLoad(Type.OBJECT, frame);
                break;
            case BALOAD:
            case CALOAD:
            case SALOAD:
                evalArrayLoad(Type.INTEGER, frame);
                break;
            case ISTORE:
                evalStore(Type.INTEGER, iter.byteAt(pos + 1), frame, subroutine);
                break;
            case LSTORE:
                evalStore(Type.LONG, iter.byteAt(pos + 1), frame, subroutine);
                break;
            case FSTORE:
                evalStore(Type.FLOAT, iter.byteAt(pos + 1), frame, subroutine);
                break;
            case DSTORE:
                evalStore(Type.DOUBLE, iter.byteAt(pos + 1), frame, subroutine);
                break;
            case ASTORE:
                evalStore(Type.OBJECT, iter.byteAt(pos + 1), frame, subroutine);
                break;
            case ISTORE_0:
            case ISTORE_1:
            case ISTORE_2:
            case ISTORE_3:
                evalStore(Type.INTEGER, opcode - ISTORE_0, frame, subroutine);
                break;
            case LSTORE_0:
            case LSTORE_1:
            case LSTORE_2:
            case LSTORE_3:
                evalStore(Type.LONG, opcode - LSTORE_0, frame, subroutine);
                break;
            case FSTORE_0:
            case FSTORE_1:
            case FSTORE_2:
            case FSTORE_3:
                evalStore(Type.FLOAT, opcode - FSTORE_0, frame, subroutine);
                break;
            case DSTORE_0:
            case DSTORE_1:
            case DSTORE_2:
            case DSTORE_3:
                evalStore(Type.DOUBLE, opcode - DSTORE_0, frame, subroutine);
                break;
            case ASTORE_0:
            case ASTORE_1:
            case ASTORE_2:
            case ASTORE_3:
                evalStore(Type.OBJECT, opcode - ASTORE_0, frame, subroutine);
                break;
            case IASTORE:
                evalArrayStore(Type.INTEGER, frame);
                break;
            case LASTORE:
                evalArrayStore(Type.LONG, frame);
                break;
            case FASTORE:
                evalArrayStore(Type.FLOAT, frame);
                break;
            case DASTORE:
                evalArrayStore(Type.DOUBLE, frame);
                break;
            case AASTORE:
                evalArrayStore(Type.OBJECT, frame);
                break;
            case BASTORE:
            case CASTORE:
            case SASTORE:
                evalArrayStore(Type.INTEGER, frame);
                break;
            case POP:
                if (frame.pop() == Type.TOP)
                    throw new BadBytecode("POP can not be used with a category 2 value, pos = " + pos);
                break;
            case POP2:
                frame.pop();
                frame.pop();
                break;
            case DUP: {
                Type type = frame.peek();
                if (type == Type.TOP)
                    throw new BadBytecode("DUP can not be used with a category 2 value, pos = " + pos);

                frame.push(frame.peek());
                break;
            }
            case DUP_X1:
            case DUP_X2: {
                Type type = frame.peek();
                if (type == Type.TOP)
                    throw new BadBytecode("DUP can not be used with a category 2 value, pos = " + pos);
                int end = frame.getTopIndex();
                int insert = end - (opcode - DUP_X1) - 1;
                frame.push(type);

                while (end > insert) {
                    frame.setStack(end, frame.getStack(end - 1));
                    end--;
                }
                frame.setStack(insert, type);
                break;
            }
            case DUP2:
                frame.push(frame.getStack(frame.getTopIndex() - 1));
                frame.push(frame.getStack(frame.getTopIndex() - 1));
                break;
            case DUP2_X1:
            case DUP2_X2: {
                int end = frame.getTopIndex();
                int insert = end - (opcode - DUP2_X1) - 1;
                Type type1 = frame.getStack(frame.getTopIndex() - 1);
                Type type2 = frame.peek();
                frame.push(type1);
                frame.push(type2);
                while (end > insert) {
                    frame.setStack(end, frame.getStack(end - 2));
                    end--;
                }
                frame.setStack(insert, type2);
                frame.setStack(insert - 1, type1);
                break;
            }
            case SWAP: {
                Type type1 = frame.pop();
                Type type2 = frame.pop();
                if (type1.getSize() == 2 || type2.getSize() == 2)
                    throw new BadBytecode("Swap can not be used with category 2 values, pos = " + pos);
                frame.push(type1);
                frame.push(type2);
                break;
            }

            // Math
            case IADD:
                evalBinaryMath(Type.INTEGER, frame);
                break;
            case LADD:
                evalBinaryMath(Type.LONG, frame);
                break;
            case FADD:
                evalBinaryMath(Type.FLOAT, frame);
                break;
            case DADD:
                evalBinaryMath(Type.DOUBLE, frame);
                break;
            case ISUB:
                evalBinaryMath(Type.INTEGER, frame);
                break;
            case LSUB:
                evalBinaryMath(Type.LONG, frame);
                break;
            case FSUB:
                evalBinaryMath(Type.FLOAT, frame);
                break;
            case DSUB:
                evalBinaryMath(Type.DOUBLE, frame);
                break;
            case IMUL:
                evalBinaryMath(Type.INTEGER, frame);
                break;
            case LMUL:
                evalBinaryMath(Type.LONG, frame);
                break;
            case FMUL:
                evalBinaryMath(Type.FLOAT, frame);
                break;
            case DMUL:
                evalBinaryMath(Type.DOUBLE, frame);
                break;
            case IDIV:
                evalBinaryMath(Type.INTEGER, frame);
                break;
            case LDIV:
                evalBinaryMath(Type.LONG, frame);
                break;
            case FDIV:
                evalBinaryMath(Type.FLOAT, frame);
                break;
            case DDIV:
                evalBinaryMath(Type.DOUBLE, frame);
                break;
            case IREM:
                evalBinaryMath(Type.INTEGER, frame);
                break;
            case LREM:
                evalBinaryMath(Type.LONG, frame);
                break;
            case FREM:
                evalBinaryMath(Type.FLOAT, frame);
                break;
            case DREM:
                evalBinaryMath(Type.DOUBLE, frame);
                break;

            // Unary
            case INEG:
                verifyAssignable(Type.INTEGER, simplePeek(frame));
                break;
            case LNEG:
                verifyAssignable(Type.LONG, simplePeek(frame));
                break;
            case FNEG:
                verifyAssignable(Type.FLOAT, simplePeek(frame));
                break;
            case DNEG:
                verifyAssignable(Type.DOUBLE, simplePeek(frame));
                break;

            // Shifts
            case ISHL:
                evalShift(Type.INTEGER, frame);
                break;
            case LSHL:
                evalShift(Type.LONG, frame);
                break;
            case ISHR:
                evalShift(Type.INTEGER, frame);
                break;
            case LSHR:
                evalShift(Type.LONG, frame);
                break;
            case IUSHR:
                evalShift(Type.INTEGER,frame);
                break;
            case LUSHR:
                evalShift(Type.LONG, frame);
                break;

            // Bitwise Math
            case IAND:
                evalBinaryMath(Type.INTEGER, frame);
                break;
            case LAND:
                evalBinaryMath(Type.LONG, frame);
                break;
            case IOR:
                evalBinaryMath(Type.INTEGER, frame);
                break;
            case LOR:
                evalBinaryMath(Type.LONG, frame);
                break;
            case IXOR:
                evalBinaryMath(Type.INTEGER, frame);
                break;
            case LXOR:
                evalBinaryMath(Type.LONG, frame);
                break;

            case IINC: {
                int index = iter.byteAt(pos + 1);
                verifyAssignable(Type.INTEGER, frame.getLocal(index));
                access(index, Type.INTEGER, subroutine);
                break;
            }

            // Conversion
            case I2L:
                verifyAssignable(Type.INTEGER, simplePop(frame));
                simplePush(Type.LONG, frame);
                break;
            case I2F:
                verifyAssignable(Type.INTEGER, simplePop(frame));
                simplePush(Type.FLOAT, frame);
                break;
            case I2D:
                verifyAssignable(Type.INTEGER, simplePop(frame));
                simplePush(Type.DOUBLE, frame);
                break;
            case L2I:
                verifyAssignable(Type.LONG, simplePop(frame));
                simplePush(Type.INTEGER, frame);
                break;
            case L2F:
                verifyAssignable(Type.LONG, simplePop(frame));
                simplePush(Type.FLOAT, frame);
                break;
            case L2D:
                verifyAssignable(Type.LONG, simplePop(frame));
                simplePush(Type.DOUBLE, frame);
                break;
            case F2I:
                verifyAssignable(Type.FLOAT, simplePop(frame));
                simplePush(Type.INTEGER, frame);
                break;
            case F2L:
                verifyAssignable(Type.FLOAT, simplePop(frame));
                simplePush(Type.LONG, frame);
                break;
            case F2D:
                verifyAssignable(Type.FLOAT, simplePop(frame));
                simplePush(Type.DOUBLE, frame);
                break;
            case D2I:
                verifyAssignable(Type.DOUBLE, simplePop(frame));
                simplePush(Type.INTEGER, frame);
                break;
            case D2L:
                verifyAssignable(Type.DOUBLE, simplePop(frame));
                simplePush(Type.LONG, frame);
                break;
            case D2F:
                verifyAssignable(Type.DOUBLE, simplePop(frame));
                simplePush(Type.FLOAT, frame);
                break;
            case I2B:
            case I2C:
            case I2S:
                verifyAssignable(Type.INTEGER, frame.peek());
                break;
            case LCMP:
                verifyAssignable(Type.LONG, simplePop(frame));
                verifyAssignable(Type.LONG, simplePop(frame));
                frame.push(Type.INTEGER);
                break;
            case FCMPL:
            case FCMPG:
                verifyAssignable(Type.FLOAT, simplePop(frame));
                verifyAssignable(Type.FLOAT, simplePop(frame));
                frame.push(Type.INTEGER);
                break;
            case DCMPL:
            case DCMPG:
                verifyAssignable(Type.DOUBLE, simplePop(frame));
                verifyAssignable(Type.DOUBLE, simplePop(frame));
                frame.push(Type.INTEGER);
                break;

            // Control flow
            case IFEQ:
            case IFNE:
            case IFLT:
            case IFGE:
            case IFGT:
            case IFLE:
                verifyAssignable(Type.INTEGER, simplePop(frame));
                break;
            case IF_ICMPEQ:
            case IF_ICMPNE:
            case IF_ICMPLT:
            case IF_ICMPGE:
            case IF_ICMPGT:
            case IF_ICMPLE:
                verifyAssignable(Type.INTEGER, simplePop(frame));
                verifyAssignable(Type.INTEGER, simplePop(frame));
                break;
            case IF_ACMPEQ:
            case IF_ACMPNE:
                verifyAssignable(Type.OBJECT, simplePop(frame));
                verifyAssignable(Type.OBJECT, simplePop(frame));
                break;
            case GOTO:
                break;
            case JSR:
                frame.push(Type.RETURN_ADDRESS);
                break;
            case RET:
                verifyAssignable(Type.RETURN_ADDRESS, frame.getLocal(iter.byteAt(pos + 1)));
                break;
            case TABLESWITCH:
            case LOOKUPSWITCH:
            case IRETURN:
                verifyAssignable(Type.INTEGER, simplePop(frame));
                break;
            case LRETURN:
                verifyAssignable(Type.LONG, simplePop(frame));
                break;
            case FRETURN:
                verifyAssignable(Type.FLOAT, simplePop(frame));
                break;
            case DRETURN:
                verifyAssignable(Type.DOUBLE, simplePop(frame));
                break;
            case ARETURN:
                try {
                    CtClass returnType = Descriptor.getReturnType(method.getDescriptor(), classPool);
                    verifyAssignable(Type.get(returnType), simplePop(frame));
                } catch (NotFoundException e) {
                   throw new RuntimeException(e);
                }
                break;
            case RETURN:
                break;
            case GETSTATIC:
                evalGetField(opcode, iter.u16bitAt(pos + 1), frame);
                break;
            case PUTSTATIC:
                evalPutField(opcode, iter.u16bitAt(pos + 1), frame);
                break;
            case GETFIELD:
                evalGetField(opcode, iter.u16bitAt(pos + 1), frame);
                break;
            case PUTFIELD:
                evalPutField(opcode, iter.u16bitAt(pos + 1), frame);
                break;
            case INVOKEVIRTUAL:
            case INVOKESPECIAL:
            case INVOKESTATIC:
                evalInvokeMethod(opcode, iter.u16bitAt(pos + 1), frame);
                break;
            case INVOKEINTERFACE:
                evalInvokeIntfMethod(opcode, iter.u16bitAt(pos + 1), frame);
                break;
            case INVOKEDYNAMIC:
                evalInvokeDynamic(opcode, iter.u16bitAt(pos + 1), frame);
                break;
            case NEW:
                frame.push(resolveClassInfo(constPool.getClassInfo(iter.u16bitAt(pos + 1))));
                break;
            case NEWARRAY:
                evalNewArray(pos, iter, frame);
                break;
            case ANEWARRAY:
                evalNewObjectArray(pos, iter, frame);
                break;
            case ARRAYLENGTH: {
                Type array = simplePop(frame);
                if (! array.isArray() && array != Type.UNINIT)
                    throw new BadBytecode("Array length passed a non-array [pos = " + pos + "]: " + array);
                frame.push(Type.INTEGER);
                break;
            }
            case ATHROW:
                verifyAssignable(THROWABLE_TYPE, simplePop(frame));
                break;
            case CHECKCAST:
                verifyAssignable(Type.OBJECT, simplePop(frame));
                frame.push(typeFromDesc(constPool.getClassInfoByDescriptor(iter.u16bitAt(pos + 1))));
                break;
            case INSTANCEOF:
                verifyAssignable(Type.OBJECT, simplePop(frame));
                frame.push(Type.INTEGER);
                break;
            case MONITORENTER:
            case MONITOREXIT:
                verifyAssignable(Type.OBJECT, simplePop(frame));
                break;
            case WIDE:
                evalWide(pos, iter, frame, subroutine);
                break;
            case MULTIANEWARRAY:
                evalNewObjectArray(pos, iter, frame);
                break;
            case IFNULL:
            case IFNONNULL:
                verifyAssignable(Type.OBJECT, simplePop(frame));
                break;
            case GOTO_W:
                break;
            case JSR_W:
                frame.push(Type.RETURN_ADDRESS);
                break;
        }
    }

    private Type zeroExtend(Type type) {
        if (type == Type.SHORT || type == Type.BYTE || type == Type.CHAR || type == Type.BOOLEAN)
            return  Type.INTEGER;

        return type;
    }

    private void evalArrayLoad(Type expectedComponent, Frame frame) throws BadBytecode {
        Type index = frame.pop();
        Type array = frame.pop();

        // Special case, an array defined by aconst_null
        // TODO - we might need to be more inteligent about this
        if (array == Type.UNINIT) {
            verifyAssignable(Type.INTEGER, index);
            if (expectedComponent == Type.OBJECT) {
                simplePush(Type.UNINIT, frame);
            } else {
                simplePush(expectedComponent, frame);
            }
            return;
        }

        Type component = array.getComponent();

        if (component == null)
            throw new BadBytecode("Not an array! [pos = " + lastPos + "]: " + component);

        component = zeroExtend(component);

        verifyAssignable(expectedComponent, component);
        verifyAssignable(Type.INTEGER, index);
        simplePush(component, frame);
    }

    private void evalArrayStore(Type expectedComponent, Frame frame) throws BadBytecode {
        Type value = simplePop(frame);
        Type index = frame.pop();
        Type array = frame.pop();

        if (array == Type.UNINIT) {
            verifyAssignable(Type.INTEGER, index);
            return;
        }

        Type component = array.getComponent();

        if (component == null)
            throw new BadBytecode("Not an array! [pos = " + lastPos + "]: " + component);

        component = zeroExtend(component);

        verifyAssignable(expectedComponent, component);
        verifyAssignable(Type.INTEGER, index);

        // This intentionally only checks for Object on aastore
        // downconverting of an array (no casts)
        // e.g. Object[] blah = new String[];
        //      blah[2] = (Object) "test";
        //      blah[3] = new Integer(); // compiler doesnt catch it (has legal bytecode),
        //                               // but will throw arraystoreexception
        if (expectedComponent == Type.OBJECT) {
            verifyAssignable(expectedComponent, value);
        } else {
            verifyAssignable(component, value);
        }
    }

    private void evalBinaryMath(Type expected, Frame frame) throws BadBytecode {
        Type value2 = simplePop(frame);
        Type value1 = simplePop(frame);

        verifyAssignable(expected, value2);
        verifyAssignable(expected, value1);
        simplePush(value1, frame);
    }

    private void evalGetField(int opcode, int index, Frame frame) throws BadBytecode {
        String desc = constPool.getFieldrefType(index);
        Type type = zeroExtend(typeFromDesc(desc));

        if (opcode == GETFIELD) {
            Type objectType = resolveClassInfo(constPool.getFieldrefClassName(index));
            verifyAssignable(objectType, simplePop(frame));
        }

        simplePush(type, frame);
    }

    private void evalInvokeIntfMethod(int opcode, int index, Frame frame) throws BadBytecode {
        String desc = constPool.getInterfaceMethodrefType(index);
        Type[] types = paramTypesFromDesc(desc);
        int i = types.length;

        while (i > 0)
            verifyAssignable(zeroExtend(types[--i]), simplePop(frame));

        String classInfo = constPool.getInterfaceMethodrefClassName(index);
        Type objectType = resolveClassInfo(classInfo);
        verifyAssignable(objectType, simplePop(frame));

        Type returnType = returnTypeFromDesc(desc);
        if (returnType != Type.VOID)
            simplePush(zeroExtend(returnType), frame);
    }

    private void evalInvokeMethod(int opcode, int index, Frame frame) throws BadBytecode {
        String desc = constPool.getMethodrefType(index);
        Type[] types = paramTypesFromDesc(desc);
        int i = types.length;

        while (i > 0)
            verifyAssignable(zeroExtend(types[--i]), simplePop(frame));

        if (opcode != INVOKESTATIC) {
            Type objectType = resolveClassInfo(constPool.getMethodrefClassName(index));
            verifyAssignable(objectType, simplePop(frame));
        }

        Type returnType = returnTypeFromDesc(desc);
        if (returnType != Type.VOID)
            simplePush(zeroExtend(returnType), frame);
    }

    private void evalInvokeDynamic(int opcode, int index, Frame frame) throws BadBytecode {
        String desc = constPool.getInvokeDynamicType(index);
        Type[] types = paramTypesFromDesc(desc);
        int i = types.length;

        while (i > 0)
            verifyAssignable(zeroExtend(types[--i]), simplePop(frame));

        // simplePop(frame);    // assume CosntPool#REF_invokeStatic

        Type returnType = returnTypeFromDesc(desc);
        if (returnType != Type.VOID)
            simplePush(zeroExtend(returnType), frame);
    }

    private void evalLDC(int index, Frame frame) throws BadBytecode {
        int tag = constPool.getTag(index);
        Type type;
        switch (tag) {
        case ConstPool.CONST_String:
            type = STRING_TYPE;
            break;
        case ConstPool.CONST_Integer:
            type = Type.INTEGER;
            break;
        case ConstPool.CONST_Float:
            type = Type.FLOAT;
            break;
        case ConstPool.CONST_Long:
            type = Type.LONG;
            break;
        case ConstPool.CONST_Double:
            type = Type.DOUBLE;
            break;
        case ConstPool.CONST_Class:
            type = CLASS_TYPE;
            break;
        default:
            throw new BadBytecode("bad LDC [pos = " + lastPos + "]: " + tag);
        }

        simplePush(type, frame);
    }

    private void evalLoad(Type expected, int index, Frame frame, Subroutine subroutine) throws BadBytecode {
        Type type = frame.getLocal(index);

        verifyAssignable(expected, type);

        simplePush(type, frame);
        access(index, type, subroutine);
    }

    private void evalNewArray(int pos, CodeIterator iter, Frame frame) throws BadBytecode {
        verifyAssignable(Type.INTEGER, simplePop(frame));
        Type type = null;
        int typeInfo = iter.byteAt(pos + 1);
        switch (typeInfo) {
            case T_BOOLEAN:
                type = getType("boolean[]");
                break;
            case T_CHAR:
                type = getType("char[]");
                break;
            case T_BYTE:
                type = getType("byte[]");
                break;
            case T_SHORT:
                type = getType("short[]");
                break;
            case T_INT:
                type = getType("int[]");
                break;
            case T_LONG:
                type = getType("long[]");
                break;
            case T_FLOAT:
                type = getType("float[]");
                break;
            case T_DOUBLE:
                type = getType("double[]");
                break;
            default:
                throw new BadBytecode("Invalid array type [pos = " + pos + "]: " + typeInfo);

        }

        frame.push(type);
    }

    private void evalNewObjectArray(int pos, CodeIterator iter, Frame frame) throws BadBytecode {
        // Convert to x[] format
        Type type = resolveClassInfo(constPool.getClassInfo(iter.u16bitAt(pos + 1)));
        String name = type.getCtClass().getName();
        int opcode = iter.byteAt(pos);
        int dimensions;

        if (opcode == MULTIANEWARRAY) {
            dimensions = iter.byteAt(pos + 3);
        } else {
            name = name + "[]";
            dimensions = 1;
        }

        while (dimensions-- > 0) {
            verifyAssignable(Type.INTEGER, simplePop(frame));
        }

        simplePush(getType(name), frame);
    }

    private void evalPutField(int opcode, int index, Frame frame) throws BadBytecode {
        String desc = constPool.getFieldrefType(index);
        Type type = zeroExtend(typeFromDesc(desc));

        verifyAssignable(type, simplePop(frame));

        if (opcode == PUTFIELD) {
            Type objectType = resolveClassInfo(constPool.getFieldrefClassName(index));
            verifyAssignable(objectType, simplePop(frame));
        }
    }

    private void evalShift(Type expected, Frame frame) throws BadBytecode {
        Type value2 = simplePop(frame);
        Type value1 = simplePop(frame);

        verifyAssignable(Type.INTEGER, value2);
        verifyAssignable(expected, value1);
        simplePush(value1, frame);
    }

    private void evalStore(Type expected, int index, Frame frame, Subroutine subroutine) throws BadBytecode {
        Type type = simplePop(frame);

        // RETURN_ADDRESS is allowed by ASTORE
        if (! (expected == Type.OBJECT && type == Type.RETURN_ADDRESS))
            verifyAssignable(expected, type);
        simpleSetLocal(index, type, frame);
        access(index, type, subroutine);
    }

    private void evalWide(int pos, CodeIterator iter, Frame frame, Subroutine subroutine) throws BadBytecode {
        int opcode = iter.byteAt(pos + 1);
        int index = iter.u16bitAt(pos + 2);
        switch (opcode) {
            case ILOAD:
                evalLoad(Type.INTEGER, index, frame, subroutine);
                break;
            case LLOAD:
                evalLoad(Type.LONG, index, frame, subroutine);
                break;
            case FLOAD:
                evalLoad(Type.FLOAT, index, frame, subroutine);
                break;
            case DLOAD:
                evalLoad(Type.DOUBLE, index, frame, subroutine);
                break;
            case ALOAD:
                evalLoad(Type.OBJECT, index, frame, subroutine);
                break;
            case ISTORE:
                evalStore(Type.INTEGER, index, frame, subroutine);
                break;
            case LSTORE:
                evalStore(Type.LONG, index, frame, subroutine);
                break;
            case FSTORE:
                evalStore(Type.FLOAT, index, frame, subroutine);
                break;
            case DSTORE:
                evalStore(Type.DOUBLE, index, frame, subroutine);
                break;
            case ASTORE:
                evalStore(Type.OBJECT, index, frame, subroutine);
                break;
            case IINC:
                verifyAssignable(Type.INTEGER, frame.getLocal(index));
                break;
            case RET:
                verifyAssignable(Type.RETURN_ADDRESS, frame.getLocal(index));
                break;
            default:
                throw new BadBytecode("Invalid WIDE operand [pos = " + pos + "]: " + opcode);
        }

    }

    private Type getType(String name) throws BadBytecode {
        try {
            return Type.get(classPool.get(name));
        } catch (NotFoundException e) {
            throw new BadBytecode("Could not find class [pos = " + lastPos + "]: " + name);
        }
    }

    private Type[] paramTypesFromDesc(String desc) throws BadBytecode {
        CtClass classes[] = null;
        try {
            classes = Descriptor.getParameterTypes(desc, classPool);
        } catch (NotFoundException e) {
            throw new BadBytecode("Could not find class in descriptor [pos = " + lastPos + "]: " + e.getMessage());
        }

        if (classes == null)
            throw new BadBytecode("Could not obtain parameters for descriptor [pos = " + lastPos + "]: " + desc);

        Type[] types = new Type[classes.length];
        for (int i = 0; i < types.length; i++)
            types[i] = Type.get(classes[i]);

        return types;
    }

    private Type returnTypeFromDesc(String desc) throws BadBytecode {
        CtClass clazz = null;
        try {
            clazz = Descriptor.getReturnType(desc, classPool);
        } catch (NotFoundException e) {
            throw new BadBytecode("Could not find class in descriptor [pos = " + lastPos + "]: " + e.getMessage());
        }

        if (clazz == null)
            throw new BadBytecode("Could not obtain return type for descriptor [pos = " + lastPos + "]: " + desc);

        return Type.get(clazz);
    }

    private Type simplePeek(Frame frame) {
        Type type = frame.peek();
        return (type == Type.TOP) ? frame.getStack(frame.getTopIndex() - 1) : type;
    }

    private Type simplePop(Frame frame) {
        Type type = frame.pop();
        return (type == Type.TOP) ? frame.pop() : type;
    }

    private void simplePush(Type type, Frame frame) {
        frame.push(type);
        if (type.getSize() == 2)
            frame.push(Type.TOP);
    }

    private void access(int index, Type type, Subroutine subroutine) {
        if (subroutine == null)
            return;
        subroutine.access(index);
        if (type.getSize() == 2)
            subroutine.access(index + 1);
    }

    private void simpleSetLocal(int index, Type type, Frame frame) {
        frame.setLocal(index, type);
        if (type.getSize() == 2)
            frame.setLocal(index + 1, Type.TOP);
    }

    private Type resolveClassInfo(String info) throws BadBytecode {
        CtClass clazz = null;
        try {
            if (info.charAt(0) == '[') {
                clazz = Descriptor.toCtClass(info, classPool);
            } else {
                clazz = classPool.get(info);
            }

        } catch (NotFoundException e) {
            throw new BadBytecode("Could not find class in descriptor [pos = " + lastPos + "]: " + e.getMessage());
        }

        if (clazz == null)
            throw new BadBytecode("Could not obtain type for descriptor [pos = " + lastPos + "]: " + info);

        return Type.get(clazz);
    }

    private Type typeFromDesc(String desc) throws BadBytecode {
        CtClass clazz = null;
        try {
            clazz = Descriptor.toCtClass(desc, classPool);
        } catch (NotFoundException e) {
            throw new BadBytecode("Could not find class in descriptor [pos = " + lastPos + "]: " + e.getMessage());
        }

        if (clazz == null)
            throw new BadBytecode("Could not obtain type for descriptor [pos = " + lastPos + "]: " + desc);

        return Type.get(clazz);
    }

    private void verifyAssignable(Type expected, Type type) throws BadBytecode {
        if (! expected.isAssignableFrom(type))
            throw new BadBytecode("Expected type: " + expected + " Got: " + type + " [pos = " + lastPos + "]");
    }
}
