/*
 * 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.stackmap;

import javassist.ClassPool;
import javassist.bytecode.BadBytecode;
import javassist.bytecode.ByteArray;
import javassist.bytecode.ConstPool;
import javassist.bytecode.Descriptor;
import javassist.bytecode.Opcode;

/*
 * A class for performing abstract interpretation.
 * See also MapMaker class. 
 */

public abstract class Tracer implements TypeTag {
    protected ClassPool classPool;
    protected ConstPool cpool;
    protected String returnType;    // used as the type of ARETURN

    protected int stackTop;
    protected TypeData[] stackTypes;
    protected TypeData[] localsTypes;

    public Tracer(ClassPool classes, ConstPool cp, int maxStack, int maxLocals,
                  String retType) {
        classPool = classes;
        cpool = cp;
        returnType = retType;
        stackTop = 0;
        stackTypes = TypeData.make(maxStack);
        localsTypes = TypeData.make(maxLocals);
    }

    public Tracer(Tracer t) {
        classPool = t.classPool;
        cpool = t.cpool;
        returnType = t.returnType;
        stackTop = t.stackTop;
        stackTypes = TypeData.make(t.stackTypes.length);
        localsTypes = TypeData.make(t.localsTypes.length);
    }

    /**
     * Does abstract interpretation on the given bytecode instruction.
     * It records whether or not a local variable (i.e. register) is accessed.
     * If the instruction requires that a local variable or
     * a stack element has a more specific type, this method updates the
     * type of it.
     *
     * @param pos         the position of the instruction.
     * @return      the size of the instruction at POS.
     */
    protected int doOpcode(int pos, byte[] code) throws BadBytecode {
        try {
            int op = code[pos] & 0xff;
            if (op < 54)
                return doOpcode0_53(pos, code, op);
            if (op < 96)
                return doOpcode54_95(pos, code, op);
            if (op < 148)
                return doOpcode96_147(pos, code, op);
            return doOpcode148_201(pos, code, op);
        }
        catch (ArrayIndexOutOfBoundsException e) {
            throw new BadBytecode("inconsistent stack height " + e.getMessage(), e);
        }
    }

    protected void visitBranch(int pos, byte[] code, int offset) throws BadBytecode {}
    protected void visitGoto(int pos, byte[] code, int offset) throws BadBytecode {}
    protected void visitReturn(int pos, byte[] code) throws BadBytecode {}
    protected void visitThrow(int pos, byte[] code) throws BadBytecode {}

    /**
     * @param pos           the position of TABLESWITCH
     * @param code          bytecode
     * @param n             the number of case labels
     * @param offsetPos     the position of the branch-target table.
     * @param defaultOffset     the offset to the default branch target.
     */
    protected void visitTableSwitch(int pos, byte[] code, int n,
                int offsetPos, int defaultOffset) throws BadBytecode {}

    /**
     * @param pos           the position of LOOKUPSWITCH
     * @param code          bytecode
     * @param n             the number of case labels
     * @param pairsPos      the position of the table of pairs of a value and a branch target.
     * @param defaultOffset     the offset to the default branch target.
     */
    protected void visitLookupSwitch(int pos, byte[] code, int n,
                int pairsPos, int defaultOffset) throws BadBytecode {}

    /**
     * Invoked when the visited instruction is jsr.
     * Java6 or later does not allow using RET.
     */
    protected void visitJSR(int pos, byte[] code) throws BadBytecode {
        /* Since JSR pushes a return address onto the operand stack,
         * the stack map at the entry point of a subroutine is
         * stackTypes resulting after executing the following code:
         *
         *     stackTypes[stackTop++] = TOP;
         */
    }

    /**
     * Invoked when the visited instruction is ret or wide ret.
     * Java6 or later does not allow using RET.
     */
    protected void visitRET(int pos, byte[] code) throws BadBytecode {}

    private int doOpcode0_53(int pos, byte[] code, int op) throws BadBytecode {
        int reg;
        TypeData[] stackTypes = this.stackTypes;
        switch (op) {
        case Opcode.NOP :
            break;
        case Opcode.ACONST_NULL :
            stackTypes[stackTop++] = new TypeData.NullType();
            break;
        case Opcode.ICONST_M1 :
        case Opcode.ICONST_0 :
        case Opcode.ICONST_1 :
        case Opcode.ICONST_2 :
        case Opcode.ICONST_3 :
        case Opcode.ICONST_4 :
        case Opcode.ICONST_5 :
            stackTypes[stackTop++] = INTEGER;
            break;
        case Opcode.LCONST_0 :
        case Opcode.LCONST_1 :
            stackTypes[stackTop++] = LONG;
            stackTypes[stackTop++] = TOP;
            break;
        case Opcode.FCONST_0 :
        case Opcode.FCONST_1 :
        case Opcode.FCONST_2 :
            stackTypes[stackTop++] = FLOAT;
            break;
        case Opcode.DCONST_0 :
        case Opcode.DCONST_1 :
            stackTypes[stackTop++] = DOUBLE;
            stackTypes[stackTop++] = TOP;
            break;
        case Opcode.BIPUSH :
        case Opcode.SIPUSH :
            stackTypes[stackTop++] = INTEGER;
            return op == Opcode.SIPUSH ? 3 : 2;
        case Opcode.LDC :
            doLDC(code[pos + 1] & 0xff);
            return 2;
        case Opcode.LDC_W :
        case Opcode.LDC2_W :
            doLDC(ByteArray.readU16bit(code, pos + 1));
            return 3;
        case Opcode.ILOAD :
            return doXLOAD(INTEGER, code, pos);
        case Opcode.LLOAD :
            return doXLOAD(LONG, code, pos);
        case Opcode.FLOAD :
            return doXLOAD(FLOAT, code, pos);
        case Opcode.DLOAD :
            return doXLOAD(DOUBLE, code, pos);
        case Opcode.ALOAD :
            return doALOAD(code[pos + 1] & 0xff);
        case Opcode.ILOAD_0 :
        case Opcode.ILOAD_1 :
        case Opcode.ILOAD_2 :
        case Opcode.ILOAD_3 :
            stackTypes[stackTop++] = INTEGER;
            break;
        case Opcode.LLOAD_0 :
        case Opcode.LLOAD_1 :
        case Opcode.LLOAD_2 :
        case Opcode.LLOAD_3 :
            stackTypes[stackTop++] = LONG;
            stackTypes[stackTop++] = TOP;
            break;
        case Opcode.FLOAD_0 :
        case Opcode.FLOAD_1 :
        case Opcode.FLOAD_2 :
        case Opcode.FLOAD_3 :
            stackTypes[stackTop++] = FLOAT;
            break;
        case Opcode.DLOAD_0 :
        case Opcode.DLOAD_1 :
        case Opcode.DLOAD_2 :
        case Opcode.DLOAD_3 :
            stackTypes[stackTop++] = DOUBLE;
            stackTypes[stackTop++] = TOP;
            break;
        case Opcode.ALOAD_0 :
        case Opcode.ALOAD_1 :
        case Opcode.ALOAD_2 :
        case Opcode.ALOAD_3 :
            reg = op - Opcode.ALOAD_0;
            stackTypes[stackTop++] = localsTypes[reg];
            break;
        case Opcode.IALOAD :
            stackTypes[--stackTop - 1] = INTEGER;
            break;
        case Opcode.LALOAD :
            stackTypes[stackTop - 2] = LONG;
            stackTypes[stackTop - 1] = TOP;
            break;
        case Opcode.FALOAD :
            stackTypes[--stackTop - 1] = FLOAT;
            break;
        case Opcode.DALOAD :
            stackTypes[stackTop - 2] = DOUBLE;
            stackTypes[stackTop - 1] = TOP;
            break;
        case Opcode.AALOAD : {
            int s = --stackTop - 1;
            TypeData data = stackTypes[s];
            stackTypes[s] = TypeData.ArrayElement.make(data);
            break; }
        case Opcode.BALOAD :
        case Opcode.CALOAD :
        case Opcode.SALOAD :
            stackTypes[--stackTop - 1] = INTEGER;
            break;
        default :
            throw new RuntimeException("fatal");
        }

        return 1;
    }

    private void doLDC(int index) {
        TypeData[] stackTypes = this.stackTypes;
        int tag = cpool.getTag(index);
        if (tag == ConstPool.CONST_String)
            stackTypes[stackTop++] = new TypeData.ClassName("java.lang.String");
        else if (tag == ConstPool.CONST_Integer)
            stackTypes[stackTop++] = INTEGER;
        else if (tag == ConstPool.CONST_Float)
            stackTypes[stackTop++] = FLOAT;
        else if (tag == ConstPool.CONST_Long) {
            stackTypes[stackTop++] = LONG;
            stackTypes[stackTop++] = TOP;
        }
        else if (tag == ConstPool.CONST_Double) {
            stackTypes[stackTop++] = DOUBLE;
            stackTypes[stackTop++] = TOP;
        }
        else if (tag == ConstPool.CONST_Class)
            stackTypes[stackTop++] = new TypeData.ClassName("java.lang.Class");
        else
            throw new RuntimeException("bad LDC: " + tag);
    }

    private int doXLOAD(TypeData type, byte[] code, int pos) {
        int localVar = code[pos + 1] & 0xff;
        return doXLOAD(localVar, type);
    }

    private int doXLOAD(int localVar, TypeData type) {
        stackTypes[stackTop++] = type;
        if (type.is2WordType())
            stackTypes[stackTop++] = TOP;

        return 2;
    }

    private int doALOAD(int localVar) {
        stackTypes[stackTop++] = localsTypes[localVar];
        return 2;
    }

    private int doOpcode54_95(int pos, byte[] code, int op) throws BadBytecode {
        switch (op) {
        case Opcode.ISTORE :
            return doXSTORE(pos, code, INTEGER);
        case Opcode.LSTORE :
            return doXSTORE(pos, code, LONG);
        case Opcode.FSTORE :
            return doXSTORE(pos, code, FLOAT);
        case Opcode.DSTORE :
            return doXSTORE(pos, code, DOUBLE);
        case Opcode.ASTORE :
            return doASTORE(code[pos + 1] & 0xff);
        case Opcode.ISTORE_0 :
        case Opcode.ISTORE_1 :
        case Opcode.ISTORE_2 :
        case Opcode.ISTORE_3 :
          { int var = op - Opcode.ISTORE_0;
            localsTypes[var] = INTEGER;
            stackTop--; }
            break;
        case Opcode.LSTORE_0 :
        case Opcode.LSTORE_1 :
        case Opcode.LSTORE_2 :
        case Opcode.LSTORE_3 :
          { int var = op - Opcode.LSTORE_0;
            localsTypes[var] = LONG;
            localsTypes[var + 1] = TOP;
            stackTop -= 2; }
            break;
        case Opcode.FSTORE_0 :
        case Opcode.FSTORE_1 :
        case Opcode.FSTORE_2 :
        case Opcode.FSTORE_3 :
          { int var = op - Opcode.FSTORE_0;
            localsTypes[var] = FLOAT;
            stackTop--; }
            break;
        case Opcode.DSTORE_0 :
        case Opcode.DSTORE_1 :
        case Opcode.DSTORE_2 :
        case Opcode.DSTORE_3 :
          { int var = op - Opcode.DSTORE_0;
            localsTypes[var] = DOUBLE;
            localsTypes[var + 1] = TOP;
            stackTop -= 2; }
            break;
        case Opcode.ASTORE_0 :
        case Opcode.ASTORE_1 :
        case Opcode.ASTORE_2 :
        case Opcode.ASTORE_3 :
          { int var = op - Opcode.ASTORE_0;
            doASTORE(var);
            break; }
        case Opcode.IASTORE :
        case Opcode.LASTORE :
        case Opcode.FASTORE :
        case Opcode.DASTORE :
            stackTop -= (op == Opcode.LASTORE || op == Opcode.DASTORE) ? 4 : 3;
            break;
        case Opcode.AASTORE :
            TypeData.ArrayElement.aastore(stackTypes[stackTop - 3],
                                          stackTypes[stackTop - 1],
                                          classPool);
            stackTop -= 3;
            break;
        case Opcode.BASTORE :
        case Opcode.CASTORE :
        case Opcode.SASTORE :
            stackTop -= 3;
            break;
        case Opcode.POP :
            stackTop--;
            break;
        case Opcode.POP2 :
            stackTop -= 2;
            break;
        case Opcode.DUP : {
            int sp = stackTop;
            stackTypes[sp] = stackTypes[sp - 1];
            stackTop = sp + 1;
            break; }
        case Opcode.DUP_X1 :
        case Opcode.DUP_X2 : {
            int len = op - Opcode.DUP_X1 + 2;
            doDUP_XX(1, len);
            int sp = stackTop;
            stackTypes[sp - len] = stackTypes[sp];
            stackTop = sp + 1;
            break; }
        case Opcode.DUP2 :
            doDUP_XX(2, 2);
            stackTop += 2;
            break;
        case Opcode.DUP2_X1 :
        case Opcode.DUP2_X2 : {
            int len = op - Opcode.DUP2_X1 + 3;
            doDUP_XX(2, len);
            int sp = stackTop;
            stackTypes[sp - len] = stackTypes[sp];
            stackTypes[sp - len + 1] = stackTypes[sp + 1];
            stackTop = sp + 2; 
            break; }
        case Opcode.SWAP : {
            int sp = stackTop - 1;
            TypeData t = stackTypes[sp];
            stackTypes[sp] = stackTypes[sp - 1];
            stackTypes[sp - 1] = t;
            break; }
        default :
            throw new RuntimeException("fatal");
        }

        return 1;
    }

    private int doXSTORE(int pos, byte[] code, TypeData type) {
        int index = code[pos + 1] & 0xff;
        return doXSTORE(index, type);
    }

    private int doXSTORE(int index, TypeData type) {
        stackTop--;
        localsTypes[index] = type;
        if (type.is2WordType()) {
            stackTop--;
            localsTypes[index + 1] = TOP;
        }

        return 2;
    }

    private int doASTORE(int index) {
        stackTop--;
        // implicit upcast might be done.
        localsTypes[index] = stackTypes[stackTop];
        return 2;
    }

    private void doDUP_XX(int delta, int len) {
        TypeData types[] = stackTypes;
        int sp = stackTop - 1;
        int end = sp - len;
        while (sp > end) {
            types[sp + delta] = types[sp];
            sp--;
        }
    }

    private int doOpcode96_147(int pos, byte[] code, int op) {
        if (op <= Opcode.LXOR) {    // IADD...LXOR
            stackTop += Opcode.STACK_GROW[op];
            return 1;
        }

        switch (op) {
        case Opcode.IINC :
            // this does not call writeLocal().
            return 3;
        case Opcode.I2L :
            stackTypes[stackTop - 1] = LONG;
            stackTypes[stackTop] = TOP;
            stackTop++;
            break;
        case Opcode.I2F :
            stackTypes[stackTop - 1] = FLOAT;
            break;
        case Opcode.I2D :
            stackTypes[stackTop - 1] = DOUBLE;
            stackTypes[stackTop] = TOP;
            stackTop++;
            break;
        case Opcode.L2I :
            stackTypes[--stackTop - 1] = INTEGER;
            break;
        case Opcode.L2F :
            stackTypes[--stackTop - 1] = FLOAT;
            break;
        case Opcode.L2D :
            stackTypes[stackTop - 2] = DOUBLE;
            break;
        case Opcode.F2I :
            stackTypes[stackTop - 1] = INTEGER;
            break;
        case Opcode.F2L :
            stackTypes[stackTop - 1] = LONG;
            stackTypes[stackTop] = TOP;
            stackTop++;
            break;
        case Opcode.F2D :
            stackTypes[stackTop - 1] = DOUBLE;
            stackTypes[stackTop] = TOP;
            stackTop++;
            break;
        case Opcode.D2I :
            stackTypes[--stackTop - 1] = INTEGER;
            break;
        case Opcode.D2L :
            stackTypes[stackTop - 2] = LONG;
            break;
        case Opcode.D2F :
            stackTypes[--stackTop - 1] = FLOAT;
            break;
        case Opcode.I2B :
        case Opcode.I2C :
        case Opcode.I2S :
            break;
        default :
            throw new RuntimeException("fatal");
        }

        return 1;
    }

    private int doOpcode148_201(int pos, byte[] code, int op) throws BadBytecode {
        switch (op) {
        case Opcode.LCMP :
            stackTypes[stackTop - 4] = INTEGER;
            stackTop -= 3;
            break;
        case Opcode.FCMPL :
        case Opcode.FCMPG :
            stackTypes[--stackTop - 1] = INTEGER;
            break;
        case Opcode.DCMPL :
        case Opcode.DCMPG :
            stackTypes[stackTop - 4] = INTEGER;
            stackTop -= 3;
            break;
        case Opcode.IFEQ :
        case Opcode.IFNE :
        case Opcode.IFLT :
        case Opcode.IFGE :
        case Opcode.IFGT :
        case Opcode.IFLE :
            stackTop--;     // branch
            visitBranch(pos, code, ByteArray.readS16bit(code, pos + 1));
            return 3;
        case Opcode.IF_ICMPEQ :
        case Opcode.IF_ICMPNE :
        case Opcode.IF_ICMPLT :
        case Opcode.IF_ICMPGE :
        case Opcode.IF_ICMPGT :
        case Opcode.IF_ICMPLE :
        case Opcode.IF_ACMPEQ :
        case Opcode.IF_ACMPNE :
            stackTop -= 2;  // branch
            visitBranch(pos, code, ByteArray.readS16bit(code, pos + 1));
            return 3;
        case Opcode.GOTO :
            visitGoto(pos, code, ByteArray.readS16bit(code, pos + 1));
            return 3;       // branch
        case Opcode.JSR :
            visitJSR(pos, code);
            return 3;       // branch
        case Opcode.RET :
            visitRET(pos, code);
            return 2;
        case Opcode.TABLESWITCH : {
            stackTop--;     // branch
            int pos2 = (pos & ~3) + 8;
            int low = ByteArray.read32bit(code, pos2);
            int high = ByteArray.read32bit(code, pos2 + 4);
            int n = high - low + 1;
            visitTableSwitch(pos, code, n, pos2 + 8, ByteArray.read32bit(code, pos2 - 4));
            return n * 4 + 16 - (pos & 3); }
        case Opcode.LOOKUPSWITCH : {
            stackTop--;     // branch
            int pos2 = (pos & ~3) + 8;
            int n = ByteArray.read32bit(code, pos2);
            visitLookupSwitch(pos, code, n, pos2 + 4, ByteArray.read32bit(code, pos2 - 4));
            return n * 8 + 12 - (pos & 3); }
        case Opcode.IRETURN :
            stackTop--;
            visitReturn(pos, code);
            break;
        case Opcode.LRETURN :
            stackTop -= 2;
            visitReturn(pos, code);
            break;
        case Opcode.FRETURN :
            stackTop--;
            visitReturn(pos, code);
            break;
        case Opcode.DRETURN :
            stackTop -= 2;
            visitReturn(pos, code);
            break;
        case Opcode.ARETURN :
            stackTypes[--stackTop].setType(returnType, classPool);
            visitReturn(pos, code);
            break;
        case Opcode.RETURN :
            visitReturn(pos, code);
            break;
        case Opcode.GETSTATIC :
            return doGetField(pos, code, false);
        case Opcode.PUTSTATIC :
            return doPutField(pos, code, false);
        case Opcode.GETFIELD :
            return doGetField(pos, code, true);
        case Opcode.PUTFIELD :
            return doPutField(pos, code, true);
        case Opcode.INVOKEVIRTUAL :
        case Opcode.INVOKESPECIAL :
            return doInvokeMethod(pos, code, true);
        case Opcode.INVOKESTATIC :
            return doInvokeMethod(pos, code, false);
        case Opcode.INVOKEINTERFACE :
            return doInvokeIntfMethod(pos, code);
        case Opcode.INVOKEDYNAMIC :
            return doInvokeDynamic(pos, code);
        case Opcode.NEW : {
            int i = ByteArray.readU16bit(code, pos + 1);
            stackTypes[stackTop++]
                      = new TypeData.UninitData(pos, cpool.getClassInfo(i));
            return 3; }
        case Opcode.NEWARRAY :
            return doNEWARRAY(pos, code);
        case Opcode.ANEWARRAY : {
            int i = ByteArray.readU16bit(code, pos + 1);
            String type = cpool.getClassInfo(i).replace('.', '/');
            if (type.charAt(0) == '[')
                type = "[" + type;
            else
                type = "[L" + type + ";";

            stackTypes[stackTop - 1]
                    = new TypeData.ClassName(type);
            return 3; }
        case Opcode.ARRAYLENGTH :
            stackTypes[stackTop - 1].setType("[Ljava.lang.Object;", classPool);
            stackTypes[stackTop - 1] = INTEGER;
            break;
        case Opcode.ATHROW :
            stackTypes[--stackTop].setType("java.lang.Throwable", classPool);
            visitThrow(pos, code);
            break;
        case Opcode.CHECKCAST : {
            // TypeData.setType(stackTypes[stackTop - 1], "java.lang.Object", classPool);
            int i = ByteArray.readU16bit(code, pos + 1);
            String type = cpool.getClassInfo(i);
            if (type.charAt(0) == '[')
                type = type.replace('.', '/');  // getClassInfo() may return "[java.lang.Object;".

            stackTypes[stackTop - 1] = new TypeData.ClassName(type);
            return 3; }
        case Opcode.INSTANCEOF :
            // TypeData.setType(stackTypes[stackTop - 1], "java.lang.Object", classPool);
            stackTypes[stackTop - 1] = INTEGER;
            return 3;
        case Opcode.MONITORENTER :
        case Opcode.MONITOREXIT :
            stackTop--;
            // TypeData.setType(stackTypes[stackTop], "java.lang.Object", classPool);
            break;
        case Opcode.WIDE :
            return doWIDE(pos, code);
        case Opcode.MULTIANEWARRAY :
            return doMultiANewArray(pos, code);
        case Opcode.IFNULL :
        case Opcode.IFNONNULL :
            stackTop--;         // branch
            visitBranch(pos, code, ByteArray.readS16bit(code, pos + 1));
            return 3;
        case Opcode.GOTO_W :
            visitGoto(pos, code, ByteArray.read32bit(code, pos + 1));
            return 5;           // branch
        case Opcode.JSR_W :
            visitJSR(pos, code);
            return 5;
        }
        return 1;
    }

    private int doWIDE(int pos, byte[] code) throws BadBytecode {
        int op = code[pos + 1] & 0xff;
        switch (op) {
        case Opcode.ILOAD :
            doWIDE_XLOAD(pos, code, INTEGER);
            break;
        case Opcode.LLOAD :
            doWIDE_XLOAD(pos, code, LONG);
            break;
        case Opcode.FLOAD :
            doWIDE_XLOAD(pos, code, FLOAT);
            break;
        case Opcode.DLOAD :
            doWIDE_XLOAD(pos, code, DOUBLE);
            break;
        case Opcode.ALOAD : {
            int index = ByteArray.readU16bit(code, pos + 2);
            doALOAD(index);
            break; }
        case Opcode.ISTORE :
            doWIDE_STORE(pos, code, INTEGER);
            break;
        case Opcode.LSTORE :
            doWIDE_STORE(pos, code, LONG);
            break;
        case Opcode.FSTORE :
            doWIDE_STORE(pos, code, FLOAT);
            break;
        case Opcode.DSTORE :
            doWIDE_STORE(pos, code, DOUBLE);
            break;
        case Opcode.ASTORE : {
            int index = ByteArray.readU16bit(code, pos + 2);
            doASTORE(index);
            break; }
        case Opcode.IINC :
            // this does not call writeLocal().
            return 6;
        case Opcode.RET :
            visitRET(pos, code);
            break;
        default :
            throw new RuntimeException("bad WIDE instruction: " + op);
        }

        return 4;
    }

    private void doWIDE_XLOAD(int pos, byte[] code, TypeData type) {
        int index = ByteArray.readU16bit(code, pos + 2);
        doXLOAD(index, type);
    }

    private void doWIDE_STORE(int pos, byte[] code, TypeData type) {
        int index = ByteArray.readU16bit(code, pos + 2);
        doXSTORE(index, type);
    }

    private int doPutField(int pos, byte[] code, boolean notStatic) throws BadBytecode {
        int index = ByteArray.readU16bit(code, pos + 1);
        String desc = cpool.getFieldrefType(index);
        stackTop -= Descriptor.dataSize(desc);
        char c = desc.charAt(0);
        if (c == 'L')
            stackTypes[stackTop].setType(getFieldClassName(desc, 0), classPool);
        else if (c == '[')
            stackTypes[stackTop].setType(desc, classPool);

        setFieldTarget(notStatic, index);
        return 3;
    }

    private int doGetField(int pos, byte[] code, boolean notStatic) throws BadBytecode {
        int index = ByteArray.readU16bit(code, pos + 1);
        setFieldTarget(notStatic, index);
        String desc = cpool.getFieldrefType(index);
        pushMemberType(desc);
        return 3;
    }

    private void setFieldTarget(boolean notStatic, int index) throws BadBytecode {
        if (notStatic) {
            String className = cpool.getFieldrefClassName(index);
            stackTypes[--stackTop].setType(className, classPool);
        }
    }

    private int doNEWARRAY(int pos, byte[] code) {
        int s = stackTop - 1;
        String type;
        switch (code[pos + 1] & 0xff) {
        case Opcode.T_BOOLEAN :
            type = "[Z";
            break;
        case Opcode.T_CHAR :
            type = "[C";
            break;
        case Opcode.T_FLOAT :
            type = "[F";
            break;
        case Opcode.T_DOUBLE :
            type = "[D";
            break;
        case Opcode.T_BYTE :
            type = "[B";
            break;
        case Opcode.T_SHORT :
            type = "[S";
            break;
        case Opcode.T_INT :
            type = "[I";
            break;
        case Opcode.T_LONG :
            type = "[J";
            break;
        default :
            throw new RuntimeException("bad newarray");
        }

        stackTypes[s] = new TypeData.ClassName(type);
        return 2;
    }

    private int doMultiANewArray(int pos, byte[] code) {
        int i = ByteArray.readU16bit(code, pos + 1);
        int dim = code[pos + 3] & 0xff;
        stackTop -= dim - 1;

        String type = cpool.getClassInfo(i).replace('.', '/');
        stackTypes[stackTop - 1] = new TypeData.ClassName(type);
        return 4;
    }

    private int doInvokeMethod(int pos, byte[] code, boolean notStatic) throws BadBytecode {
        int i = ByteArray.readU16bit(code, pos + 1);
        String desc = cpool.getMethodrefType(i);
        checkParamTypes(desc, 1);
        if (notStatic) {
            String className = cpool.getMethodrefClassName(i);
            TypeData target = stackTypes[--stackTop];
            if (target instanceof TypeData.UninitTypeVar && target.isUninit())
                constructorCalled(target, ((TypeData.UninitTypeVar)target).offset());
            else if (target instanceof TypeData.UninitData)
                constructorCalled(target, ((TypeData.UninitData)target).offset());

            target.setType(className, classPool);
        }

        pushMemberType(desc);
        return 3;
    }

    /* This is a constructor call on an uninitialized object.
     * Sets flags of other references to that object.
     *
     * @param offset        the offset where the object has been created.
     */
    private void constructorCalled(TypeData target, int offset) {
        target.constructorCalled(offset);
        for (int i = 0; i < stackTop; i++)
            stackTypes[i].constructorCalled(offset);

        for (int i = 0; i < localsTypes.length; i++)
            localsTypes[i].constructorCalled(offset);
    }

    private int doInvokeIntfMethod(int pos, byte[] code) throws BadBytecode {
        int i = ByteArray.readU16bit(code, pos + 1);
        String desc = cpool.getInterfaceMethodrefType(i);
        checkParamTypes(desc, 1);
        String className = cpool.getInterfaceMethodrefClassName(i);
        stackTypes[--stackTop].setType(className, classPool);
        pushMemberType(desc);
        return 5;
    }

    private int doInvokeDynamic(int pos, byte[] code) throws BadBytecode {
        int i = ByteArray.readU16bit(code, pos + 1);
        String desc = cpool.getInvokeDynamicType(i);
        checkParamTypes(desc, 1);

     // assume CosntPool#REF_invokeStatic
     /* TypeData target = stackTypes[--stackTop];
        if (target instanceof TypeData.UninitTypeVar && target.isUninit())
            constructorCalled((TypeData.UninitTypeVar)target);
      */

        pushMemberType(desc);
        return 5;
    }

    private void pushMemberType(String descriptor) {
        int top = 0;
        if (descriptor.charAt(0) == '(') {
            top = descriptor.indexOf(')') + 1;
            if (top < 1)
                throw new IndexOutOfBoundsException("bad descriptor: "
                                                    + descriptor);
        }

        TypeData[] types = stackTypes;
        int index = stackTop;
        switch (descriptor.charAt(top)) {
        case '[' :
            types[index] = new TypeData.ClassName(descriptor.substring(top));
            break;
        case 'L' :
            types[index] = new TypeData.ClassName(getFieldClassName(descriptor, top));
            break;
        case 'J' :
            types[index] = LONG;
            types[index + 1] = TOP;
            stackTop += 2;
            return;
        case 'F' :
            types[index] = FLOAT;
            break;
        case 'D' :
            types[index] = DOUBLE;
            types[index + 1] = TOP;
            stackTop += 2;
            return;
        case 'V' :
            return;
        default : // C, B, S, I, Z
            types[index] = INTEGER;
            break;
        }

        stackTop++;
    }

    private static String getFieldClassName(String desc, int index) {
        return desc.substring(index + 1, desc.length() - 1).replace('/', '.');
    }

    private void checkParamTypes(String desc, int i) throws BadBytecode {
        char c = desc.charAt(i);
        if (c == ')')
            return;

        int k = i;
        boolean array = false;
        while (c == '[') {
            array = true;
            c = desc.charAt(++k);
        }

        if (c == 'L') {
            k = desc.indexOf(';', k) + 1;
            if (k <= 0)
                throw new IndexOutOfBoundsException("bad descriptor");
        }
        else
            k++;

        checkParamTypes(desc, k);
        if (!array && (c == 'J' || c == 'D'))
            stackTop -= 2;
        else
            stackTop--;

        if (array)
            stackTypes[stackTop].setType(desc.substring(i, k), classPool);
        else if (c == 'L')
            stackTypes[stackTop].setType(desc.substring(i + 1, k - 1).replace('/', '.'),
                                         classPool);
    }
}
