bytecode.MethodImporter.java Source code

Java tutorial

Introduction

Here is the source code for bytecode.MethodImporter.java

Source

/*
 * Parallelising JVM Compiler
 *
 * Copyright 2010 Peter Calvert, University of Cambridge
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License
 */

package bytecode;

import debug.LinePropagator;

import analysis.dataflow.LiveVariable;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import graph.Annotation;
import graph.BasicBlock;
import graph.Block;
import graph.ClassNode;
import graph.Method;
import graph.Modifier;
import graph.Type;

import graph.instructions.*;
import graph.state.*;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.Stack;

/**
 * Visits JVM bytecode, as represented by the ASM library, converting this into
 * a joint control-flow and data-flow graph that is used for analysis and
 * transformation.
 */
public class MethodImporter implements MethodVisitor {
    /**
     * Method being imported.
     */
    private Method method;

    /**
     * Working stack.
     */
    private Stack<Producer> stack = new Stack<Producer>();

    /**
     * Current block to which instructions should be added.
     */
    private BasicBlock current;

    /**
     * Mapping between JVM labels and graph blocks.
     */
    private Map<Label, BasicBlock> labels = new HashMap<Label, BasicBlock>();

    /**
     * Used for caching flattened order of instructions to ease exporting later.
     */
    private List<Instruction> ordered = new LinkedList<Instruction>();

    /**
     * Used to allocate the same variable object to each memory access in a block,
     * this allows variable names to be allocated.
     */
    private Map<Block, Map<Integer, Variable>> variables = new HashMap<Block, Map<Integer, Variable>>();

    /**
     * Constructs a method importer, starting on the given basic block.
     *
     * @param method    Method to import.
     */
    public MethodImporter(Method method) {
        this.method = method;
    }

    /**
     * Called at start of code. Used to mark method as having an implementation.
     */
    @Override
    public void visitCode() {
        current = new BasicBlock();
        variables.put(current, new HashMap<Integer, Variable>());
        method.setImplementation(current);
    }

    /**
     * Called for each annotation defined on the method. Used to store these
     * annotations which are later used to determine which classes to transform.
     *
     * @param  desc     Descriptor for the annotation type.
     * @param  visible  Visibility of annotation (unused).
     * @return          AnnotationVisitor that imports the annotation properties.
     */
    @Override
    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
        Annotation a = new Annotation(Type.getType(desc));

        method.addAnnotation(a);

        return new AnnotationImporter(a);
    }

    /**
     * Called for each annotation defined on one of the method's parameters. Used
     * to store these annotations which are later used to guide alias analysis.
     * 
     * @param parameter Index of the parameter to annotate.
     * @param desc      Descriptor for the annotation type.
     * @param visible   Visibility of annotation (unused).
     * @return          AnnotationVisitor that imports the annotation properties.
     */
    @Override
    public AnnotationVisitor visitParameterAnnotation(final int parameter, final String desc,
            final boolean visible) {
        Annotation a = new Annotation(Type.getType(desc));

        if (method.getModifiers().contains(Modifier.STATIC)) {
            method.addParameterAnnotation(parameter, a);
        } else {
            method.addParameterAnnotation(parameter + 1, a);
        }

        return new AnnotationImporter(a);
    }

    /**
     * Imports instructions with no immediate operands.
     *
     * @param opcode Opcode.
     */
    @Override
    public void visitInsn(final int opcode) {
        Producer a, b, c, d;
        Type top;

        switch (opcode) {
        // Constants
        case Opcodes.ACONST_NULL:
            createConstant(null);
            break;
        case Opcodes.ICONST_M1:
            createConstant(new Integer(-1));
            break;
        case Opcodes.ICONST_0:
            createConstant(new Integer(0));
            break;
        case Opcodes.ICONST_1:
            createConstant(new Integer(1));
            break;
        case Opcodes.ICONST_2:
            createConstant(new Integer(2));
            break;
        case Opcodes.ICONST_3:
            createConstant(new Integer(3));
            break;
        case Opcodes.ICONST_4:
            createConstant(new Integer(4));
            break;
        case Opcodes.ICONST_5:
            createConstant(new Integer(5));
            break;
        case Opcodes.LCONST_0:
            createConstant(new Long(0));
            break;
        case Opcodes.LCONST_1:
            createConstant(new Long(1));
            break;
        case Opcodes.FCONST_0:
            createConstant(new Float(0.0f));
            break;
        case Opcodes.FCONST_1:
            createConstant(new Float(1.0f));
            break;
        case Opcodes.FCONST_2:
            createConstant(new Float(2.0f));
            break;
        case Opcodes.DCONST_0:
            createConstant(new Double(0.0f));
            break;
        case Opcodes.DCONST_1:
            createConstant(new Double(1.0f));
            break;

        // Binary Operations
        case Opcodes.IADD:
            createArithmetic(Arithmetic.Operator.ADD, Type.INT);
            break;
        case Opcodes.LADD:
            createArithmetic(Arithmetic.Operator.ADD, Type.LONG);
            break;
        case Opcodes.FADD:
            createArithmetic(Arithmetic.Operator.ADD, Type.FLOAT);
            break;
        case Opcodes.DADD:
            createArithmetic(Arithmetic.Operator.ADD, Type.DOUBLE);
            break;
        case Opcodes.ISUB:
            createArithmetic(Arithmetic.Operator.SUB, Type.INT);
            break;
        case Opcodes.LSUB:
            createArithmetic(Arithmetic.Operator.SUB, Type.LONG);
            break;
        case Opcodes.FSUB:
            createArithmetic(Arithmetic.Operator.SUB, Type.FLOAT);
            break;
        case Opcodes.DSUB:
            createArithmetic(Arithmetic.Operator.SUB, Type.DOUBLE);
            break;
        case Opcodes.IMUL:
            createArithmetic(Arithmetic.Operator.MUL, Type.INT);
            break;
        case Opcodes.LMUL:
            createArithmetic(Arithmetic.Operator.MUL, Type.LONG);
            break;
        case Opcodes.FMUL:
            createArithmetic(Arithmetic.Operator.MUL, Type.FLOAT);
            break;
        case Opcodes.DMUL:
            createArithmetic(Arithmetic.Operator.MUL, Type.DOUBLE);
            break;
        case Opcodes.IDIV:
            createArithmetic(Arithmetic.Operator.DIV, Type.INT);
            break;
        case Opcodes.LDIV:
            createArithmetic(Arithmetic.Operator.DIV, Type.LONG);
            break;
        case Opcodes.FDIV:
            createArithmetic(Arithmetic.Operator.DIV, Type.FLOAT);
            break;
        case Opcodes.DDIV:
            createArithmetic(Arithmetic.Operator.DIV, Type.DOUBLE);
            break;
        case Opcodes.IREM:
            createArithmetic(Arithmetic.Operator.REM, Type.INT);
            break;
        case Opcodes.LREM:
            createArithmetic(Arithmetic.Operator.REM, Type.LONG);
            break;
        case Opcodes.FREM:
            createArithmetic(Arithmetic.Operator.REM, Type.FLOAT);
            break;
        case Opcodes.DREM:
            createArithmetic(Arithmetic.Operator.REM, Type.DOUBLE);
            break;
        case Opcodes.IAND:
            createArithmetic(Arithmetic.Operator.AND, Type.INT);
            break;
        case Opcodes.LAND:
            createArithmetic(Arithmetic.Operator.AND, Type.LONG);
            break;
        case Opcodes.IOR:
            createArithmetic(Arithmetic.Operator.OR, Type.INT);
            break;
        case Opcodes.LOR:
            createArithmetic(Arithmetic.Operator.OR, Type.LONG);
            break;
        case Opcodes.IXOR:
            createArithmetic(Arithmetic.Operator.XOR, Type.INT);
            break;
        case Opcodes.LXOR:
            createArithmetic(Arithmetic.Operator.XOR, Type.LONG);
            break;
        case Opcodes.ISHL:
            createArithmetic(Arithmetic.Operator.SHL, Type.INT);
            break;
        case Opcodes.LSHL:
            createArithmetic(Arithmetic.Operator.SHL, Type.LONG);
            break;
        case Opcodes.ISHR:
            createArithmetic(Arithmetic.Operator.SHR, Type.INT);
            break;
        case Opcodes.LSHR:
            createArithmetic(Arithmetic.Operator.SHR, Type.LONG);
            break;
        case Opcodes.IUSHR:
            createArithmetic(Arithmetic.Operator.USHR, Type.INT);
            break;
        case Opcodes.LUSHR:
            createArithmetic(Arithmetic.Operator.USHR, Type.LONG);
            break;

        case Opcodes.LCMP:
            createCompare(false, Type.LONG);
            break;
        case Opcodes.FCMPL:
            createCompare(false, Type.FLOAT);
            break;
        case Opcodes.FCMPG:
            createCompare(true, Type.FLOAT);
            break;
        case Opcodes.DCMPL:
            createCompare(false, Type.DOUBLE);
            break;
        case Opcodes.DCMPG:
            createCompare(true, Type.DOUBLE);
            break;
        case Opcodes.INEG:
            createNegate();
            break;
        case Opcodes.LNEG:
            createNegate();
            break;
        case Opcodes.FNEG:
            createNegate();
            break;
        case Opcodes.DNEG:
            createNegate();
            break;
        case Opcodes.I2L:
            createConvert(Type.LONG);
            break;
        case Opcodes.I2F:
            createConvert(Type.FLOAT);
            break;
        case Opcodes.I2D:
            createConvert(Type.DOUBLE);
            break;
        case Opcodes.I2B:
            createConvert(Type.BYTE);
            break;
        case Opcodes.I2C:
            createConvert(Type.CHAR);
            break;
        case Opcodes.I2S:
            createConvert(Type.SHORT);
            break;
        case Opcodes.L2I:
            createConvert(Type.INT);
            break;
        case Opcodes.L2F:
            createConvert(Type.FLOAT);
            break;
        case Opcodes.L2D:
            createConvert(Type.DOUBLE);
            break;
        case Opcodes.F2I:
            createConvert(Type.INT);
            break;
        case Opcodes.F2L:
            createConvert(Type.LONG);
            break;
        case Opcodes.F2D:
            createConvert(Type.DOUBLE);
            break;
        case Opcodes.D2I:
            createConvert(Type.INT);
            break;
        case Opcodes.D2F:
            createConvert(Type.FLOAT);
            break;
        case Opcodes.D2L:
            createConvert(Type.LONG);
            break;
        case Opcodes.IALOAD:
            createArrayRead(Type.INT);
            break;
        case Opcodes.LALOAD:
            createArrayRead(Type.LONG);
            break;
        case Opcodes.FALOAD:
            createArrayRead(Type.FLOAT);
            break;
        case Opcodes.DALOAD:
            createArrayRead(Type.DOUBLE);
            break;
        case Opcodes.AALOAD:
            createArrayRead(Type.getFreshRef());
            break;
        case Opcodes.BALOAD:
            createArrayRead(Type.BYTE);
            break;
        case Opcodes.CALOAD:
            createArrayRead(Type.CHAR);
            break;
        case Opcodes.SALOAD:
            createArrayRead(Type.SHORT);
            break;
        case Opcodes.IASTORE:
            createArrayWrite(Type.INT);
            break;
        case Opcodes.LASTORE:
            createArrayWrite(Type.LONG);
            break;
        case Opcodes.FASTORE:
            createArrayWrite(Type.FLOAT);
            break;
        case Opcodes.DASTORE:
            createArrayWrite(Type.DOUBLE);
            break;
        case Opcodes.AASTORE:
            createArrayWrite(Type.getFreshRef());
            break;
        case Opcodes.BASTORE:
            createArrayWrite(Type.BYTE);
            break;
        case Opcodes.CASTORE:
            createArrayWrite(Type.CHAR);
            break;
        case Opcodes.SASTORE:
            createArrayWrite(Type.SHORT);
            break;
        case Opcodes.IRETURN:
            createReturn(Type.INT);
            break;
        case Opcodes.LRETURN:
            createReturn(Type.LONG);
            break;
        case Opcodes.FRETURN:
            createReturn(Type.FLOAT);
            break;
        case Opcodes.DRETURN:
            createReturn(Type.DOUBLE);
            break;
        case Opcodes.ARETURN:
            createReturn(Type.REF);
            break;
        case Opcodes.RETURN:
            createReturn(null);
            break;
        case Opcodes.ATHROW:
            createThrow();
            break;

        // Array Length
        case Opcodes.ARRAYLENGTH:
            ordered.add(stack.push(new ArrayLength(stack.pop())));
            break;

        // Swap
        case Opcodes.SWAP:
            a = stack.pop();
            b = stack.pop();

            stack.push(a);
            stack.push(b);

            ordered.add(new StackOperation(StackOperation.Sort.SWAP));

            break;

        // Duplicates
        case Opcodes.DUP:
            stack.push(stack.peek());
            ordered.add(new StackOperation(StackOperation.Sort.DUP));
            break;
        case Opcodes.DUP2:
            top = stack.peek().getType();

            // Type 2 Values
            if (top.getSize() == 2) {
                stack.push(stack.peek());
                // Type 1 Values
            } else {
                b = stack.pop();
                a = stack.pop();

                stack.push(a);
                stack.push(b);
                stack.push(a);
                stack.push(b);
            }

            ordered.add(new StackOperation(StackOperation.Sort.DUP2));

            break;
        case Opcodes.DUP_X1:
            b = stack.pop();
            a = stack.pop();

            stack.push(b);
            stack.push(a);
            stack.push(b);

            ordered.add(new StackOperation(StackOperation.Sort.DUP_X1));

            break;
        case Opcodes.DUP_X2:
            top = stack.peek().getType();

            // Type 2 Values
            if (top.getSize() == 2) {
                b = stack.pop();
                a = stack.pop();

                stack.push(b);
                stack.push(a);
                stack.push(b);
                // Type 1 Values
            } else {
                c = stack.pop();
                b = stack.pop();
                a = stack.pop();

                stack.push(c);
                stack.push(a);
                stack.push(b);
                stack.push(c);
            }

            ordered.add(new StackOperation(StackOperation.Sort.DUP_X2));

            break;

        // Pops
        case Opcodes.POP:
            stack.pop();
            ordered.add(new StackOperation(StackOperation.Sort.POP));
            break;
        case Opcodes.POP2:
            top = stack.peek().getType();

            // Type 2 Values
            if (top.getSize() == 2) {
                stack.pop();
                // Type 1 Values
            } else {
                stack.pop();
                stack.pop();
            }

            ordered.add(new StackOperation(StackOperation.Sort.POP2));

            break;

        // TODO: DUP2_X1, DUP2_X2, MONITORENTER, MONITOREXIT
        case Opcodes.MONITORENTER:
            throw new RuntimeException("visitInsn: MONITORENTER");
        case Opcodes.MONITOREXIT:
            throw new RuntimeException("visitInsn: MONITOREXIT");
        case Opcodes.DUP2_X1:
            throw new RuntimeException("visitInsn: DUP2_X1");
        case Opcodes.DUP2_X2:
            throw new RuntimeException("visitInsn: DUP2_X2");
        default:
            throw new RuntimeException("visitInsn: " + opcode);
        }
    }

    /**
     * Imports constant loads from the constant pool.
     *
     * @param constant Constant to be loaded.
     */
    @Override
    public void visitLdcInsn(final Object constant) {
        createConstant(constant);
    }

    /**
     * Imports instructions with a single integer operand (byte push, short push
     * and allocation of primitive arrays).
     *
     * @param opcode  Opcode.
     * @param operand Integer operand.
     */
    @Override
    public void visitIntInsn(final int opcode, final int operand) {
        switch (opcode) {
        // Constants
        case Opcodes.BIPUSH:
            createConstant(new Byte((byte) operand));
            break;
        case Opcodes.SIPUSH:
            createConstant(new Short((short) operand));
            break;

        // New Array (Primitive)
        case Opcodes.NEWARRAY:
            Type type = null;

            switch (operand) {
            case Opcodes.T_BOOLEAN:
                type = Type.BOOL;
                break;
            case Opcodes.T_CHAR:
                type = Type.CHAR;
                break;
            case Opcodes.T_FLOAT:
                type = Type.FLOAT;
                break;
            case Opcodes.T_DOUBLE:
                type = Type.DOUBLE;
                break;
            case Opcodes.T_BYTE:
                type = Type.BYTE;
                break;
            case Opcodes.T_SHORT:
                type = Type.SHORT;
                break;
            case Opcodes.T_INT:
                type = Type.INT;
                break;
            case Opcodes.T_LONG:
                type = Type.LONG;
                break;
            }

            ordered.add(stack.push(new NewArray(type, stack.pop())));
            break;
        }
    }

    /**
     * Imports instructions with a single type operand (allocation of reference
     * arrays, allocation of objects, casting and type checks).
     *
     * @param opcode     Opcode.
     * @param descriptor String descriptor of the type operand.
     */
    @Override
    public void visitTypeInsn(final int opcode, final String descriptor) {
        Type type = Type.getObjectType(descriptor);

        switch (opcode) {
        // News
        case Opcodes.ANEWARRAY:
            ordered.add(stack.push(new NewArray(type, stack.pop())));
            break;
        case Opcodes.NEW:
            ordered.add(stack.push(new NewObject(type)));
            break;

        // Type Checks
        case Opcodes.CHECKCAST:
            ordered.add(stack.push(new CheckCast(stack.pop(), type)));
            break;
        case Opcodes.INSTANCEOF:
            ordered.add(stack.push(new InstanceOf(stack.pop(), type)));
            break;
        }
    }

    /**
     * Imports multiple dimension array allocations.
     *
     * @param descriptor Descriptor of the type.
     * @param dimensions Number of dimensions.
     */
    @Override
    public void visitMultiANewArrayInsn(final String descriptor, final int dimensions) {
        Producer[] counts = new Producer[dimensions];

        for (int i = dimensions - 1; i >= 0; i--) {
            counts[i] = stack.pop();
        }

        ordered.add(stack.push(new NewMultiArray(Type.getObjectType(descriptor), counts)));
    }

    /**
     * Imports variable load and store operations, as well as RET?!?
     *
     * @param opcode Opcode.
     * @param var    Variable index.
     */
    @Override
    public void visitVarInsn(final int opcode, final int var) {
        switch (opcode) {
        case Opcodes.ILOAD:
            createVarRead(var, Type.INT);
            break;
        case Opcodes.LLOAD:
            createVarRead(var, Type.LONG);
            break;
        case Opcodes.FLOAD:
            createVarRead(var, Type.FLOAT);
            break;
        case Opcodes.DLOAD:
            createVarRead(var, Type.DOUBLE);
            break;
        case Opcodes.ALOAD:
            createVarRead(var, Type.getFreshRef());
            break;
        case Opcodes.ISTORE:
            createVarWrite(var, Type.INT);
            break;
        case Opcodes.LSTORE:
            createVarWrite(var, Type.LONG);
            break;
        case Opcodes.FSTORE:
            createVarWrite(var, Type.FLOAT);
            break;
        case Opcodes.DSTORE:
            createVarWrite(var, Type.DOUBLE);
            break;
        case Opcodes.ASTORE:
            createVarWrite(var, Type.getFreshRef());
            break;

        // TODO: RET (paired with JSR)
        case Opcodes.RET:
            throw new RuntimeException("visitVarInsn: RET");
        }
    }

    /**
     * Imports variable increment instructions (and decrements by specifying
     * negative increment values).
     *
     * @param var       Variable index.
     * @param increment Amount of increment.
     */
    @Override
    public void visitIincInsn(final int var, final int increment) {
        Increment i = new Increment(getVariable(current, var, Type.INT), increment);

        current.getStateful().add(i);
        ordered.add(i);
    }

    /**
     * Imports field instructions (put and get), both for static and instance
     * fields.
     *
     * @param opcode Opcode.
     * @param owner  Class containing the field.
     * @param name   Name of field.
     * @param desc   Type descriptor of field.
     */
    @Override
    public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) {
        Field f = ClassNode.getClass(owner).getField(name, desc);

        if (((opcode == Opcodes.GETSTATIC) || (opcode == Opcodes.PUTSTATIC)) != f.getModifiers()
                .contains(Modifier.STATIC)) {
            throw new RuntimeException("Field staticness conflicts with instruction");
        }

        switch (opcode) {
        // Loads
        case Opcodes.GETSTATIC:
        case Opcodes.GETFIELD:
            createFieldRead(f);
            break;
        // Stores
        case Opcodes.PUTSTATIC:
        case Opcodes.PUTFIELD:
            createFieldWrite(f);
            break;
        }
    }

    /**
     * Imports method calls, storing the type of invokation (virtual, static,
     * special and interface).
     *
     * @param opcode     Opcode.
     * @param owner      Class containing the method.
     * @param name       Name of the method.
     * @param descriptor Descriptor of the method.
     */
    @Override
    public void visitMethodInsn(final int opcode, final String owner, final String name, final String descriptor) {
        // Find relevant class and method.
        ClassNode clazz = ClassNode.getClass(owner);
        Method m = clazz.getMethod(name, descriptor);

        int thisOffset = (opcode == Opcodes.INVOKESTATIC) ? 0 : 1;

        // Pop arguments off stack (remembering to pass 'this' for non-statics).
        Producer[] arguments = new Producer[m.getParameterCount() + thisOffset];
        List<Type> argTypes = m.getParameters();

        for (int i = m.getParameterCount() - 1; i >= 0; i--) {
            Producer arg = stack.pop();

            arg.getType().unify(argTypes.get(i), false);
            arguments[i + thisOffset] = arg;
        }

        // 'this' Argument
        if (thisOffset == 1) {
            Producer arg = stack.pop();
            arg.getType().unify(Type.getObjectType(owner));
            arguments[0] = arg;
        }

        // Call type
        Call.Sort sort = null;

        switch (opcode) {
        case Opcodes.INVOKEINTERFACE:
            sort = Call.Sort.INTERFACE;
            break;
        case Opcodes.INVOKESPECIAL:
            sort = Call.Sort.SPECIAL;
            break;
        case Opcodes.INVOKESTATIC:
            sort = Call.Sort.STATIC;
            break;
        case Opcodes.INVOKEVIRTUAL:
            sort = Call.Sort.VIRTUAL;
            break;
        }

        // Create instruction and update stack etc.
        Call c = new Call(arguments, m, sort);

        if (Type.getReturnType(descriptor).getSort() != null)
            stack.push(c);

        current.getStateful().add(c);
        ordered.add(c);
    }

    /**
     * Imports branch instructions, both conditional and unconditional.
     *
     * @param opcode Opcode.
     * @param label  Destination of branch.
     */
    @Override
    public void visitJumpInsn(final int opcode, final Label label) {
        BasicBlock section = getBasicBlock(label);

        switch (opcode) {
        // Unconditional Branch
        case Opcodes.GOTO:
            finishBlock(section);
            setCurrent(null);
            break;

        // Zero Test
        case Opcodes.IFEQ:
            createZeroCondition(Condition.Operator.EQ, section);
            break;
        case Opcodes.IFNE:
            createZeroCondition(Condition.Operator.NE, section);
            break;
        case Opcodes.IFLT:
            createZeroCondition(Condition.Operator.LT, section);
            break;
        case Opcodes.IFGE:
            createZeroCondition(Condition.Operator.GE, section);
            break;
        case Opcodes.IFGT:
            createZeroCondition(Condition.Operator.GT, section);
            break;
        case Opcodes.IFLE:
            createZeroCondition(Condition.Operator.LE, section);
            break;

        // Integer Comparison
        case Opcodes.IF_ICMPEQ:
            createCondition(Condition.Operator.EQ, section);
            break;
        case Opcodes.IF_ICMPNE:
            createCondition(Condition.Operator.NE, section);
            break;
        case Opcodes.IF_ICMPLT:
            createCondition(Condition.Operator.LT, section);
            break;
        case Opcodes.IF_ICMPGE:
            createCondition(Condition.Operator.GE, section);
            break;
        case Opcodes.IF_ICMPGT:
            createCondition(Condition.Operator.GT, section);
            break;
        case Opcodes.IF_ICMPLE:
            createCondition(Condition.Operator.LE, section);
            break;

        // Reference Comparisons
        case Opcodes.IF_ACMPEQ:
            createCondition(Condition.Operator.EQ, section);
            break;
        case Opcodes.IF_ACMPNE:
            createCondition(Condition.Operator.NE, section);
            break;
        case Opcodes.IFNULL:
            createNullCondition(Condition.Operator.EQ, section);
            break;
        case Opcodes.IFNONNULL:
            createNullCondition(Condition.Operator.NE, section);
            break;

        // TODO: JSR (paired with RET)
        case Opcodes.JSR:
            System.err.println("visitJumpInsn: JSR");
        }
    }

    /**
     * Imports 'table' switch instructions (i.e. those where the integer cases
     * are contiguous from a minimum to a maximum).
     *
     * @param min    Minimum case key.
     * @param max    Maximum case key.
     * @param dflt   Default case destination label.
     * @param labels Destination labels for the cases.
     */
    @Override
    public void visitTableSwitchInsn(final int min, final int max, final Label dflt, final Label[] labels) {
        Map<Integer, Block> map = new HashMap<Integer, Block>();

        // Construct Mapping
        for (int i = min; i <= max; i++) {
            map.put(new Integer(i), getBasicBlock(labels[i - min]));
        }

        createSwitch(map, getBasicBlock(dflt));
    }

    /**
     * Imports 'lookup' switch instructions (i.e. those where the integer cases
     * are explicitly named).
     *
     * @param dflt   Default case destination label.
     * @param keys   Keys for cases.
     * @param labels Destination labels for the cases.
     */
    @Override
    public void visitLookupSwitchInsn(final Label dflt, final int keys[], final Label labels[]) {
        Map<Integer, Block> map = new HashMap<Integer, Block>();

        // Construct Mapping
        for (int i = 0; i < keys.length; i++) {
            map.put(new Integer(keys[i]), getBasicBlock(labels[i]));
        }

        createSwitch(map, getBasicBlock(dflt));
    }

    /**
     * Imports 'try ... catch blocks'. These are represented in the object graph
     * as branch instructions, and therefore appear at the end of a basic block.
     *
     * @param start   Start of try region.
     * @param end     End of try region.
     * @param handler Exception handler (catch block).
     * @param type    Name of the exception caught.
     */
    @Override
    public void visitTryCatchBlock(final Label start, final Label end, final Label handler, final String type) {
        TryCatch t = new TryCatch(getBasicBlock(start), getBasicBlock(end), getBasicBlock(handler),
                Type.getObjectType(type));

        current.setBranch(t);
        ordered.add(t);

        finishBlock(null);
    }

    /**
     * Visited for each label defined in the bytecode. This is used to switch
     * to the new basic block, so instructions are correctly allocated.
     *
     * @param label  New label that is starting
     */
    @Override
    public void visitLabel(final Label label) {
        BasicBlock next = getBasicBlock(label);

        if (current != null) {
            finishBlock(next);
        } else {
            setCurrent(next);
        }
    }

    /**
     * Visits a new frame, giving details about the `delta' (including some
     * limited type information). This is not used by our importer.
     *
     * @param type   Type of delta (i.e. how it is described).
     * @param nLocal Change for local variable stack.
     * @param local  Types for the change.
     * @param nStack Change for operand stack.
     * @param stack  Types for the change.
     */
    @Override
    public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
        // Nothing
    }

    /**
     * Visited when the 'method' is part of an annotation class, to allow the
     * default value of the annotation method to be considered. This is not needed
     * here since annotation classes are never imported.
     *
     * @return       Annotation visitor to consider the default value of this
     *               method. Here <code>null</code>.
     */
    @Override
    public AnnotationVisitor visitAnnotationDefault() {
        return null;
    }

    /**
     * Visited for attributes. Unclear what these are, and aren't needed.
     *
     * @param attr   Attribute.
     */
    @Override
    public void visitAttribute(final Attribute attr) {
        // Nothing
    }

    /**
     * Gives analysis of operand and local variable stacks, not used for import.
     *
     * @param maxStack  Maximum size of stack.
     * @param maxLocals Maximum number of local variables.
     */
    @Override
    public void visitMaxs(final int maxStack, final int maxLocals) {
        // Nothing
    }

    /**
     * Called at end of method visit. Used to perform live variable analysis which
     * propogates variable types between blocks.
     */
    @Override
    public void visitEnd() {
        // Only do stuff for methods with code (not abstract or native).
        if (method.getImplementation() != null) {
            method.getImplementation().accept(new LinePropagator());
            new LiveVariable(method.getImplementation());
        }
    }

    /**
     * Uses line number debug information to mark BasicBlocks. This is solely for
     * debugging purposes.
     *
     * @param line       Line number in source file.
     * @param label      Corresponding label.
     */
    @Override
    public void visitLineNumber(final int line, final Label label) {
        getBasicBlock(label).setLineNumber(line);
    }

    /**
     * Uses debug information regarding names of local variables to update the
     * relevant object with this information. This is solely for debugging
     * purposes.
     *
     * @param name       Name of the variable.
     * @param descriptor Type descriptor of the variable.
     * @param signature
     * @param start
     * @param end
     * @param index      Index of the variable.
     */
    @Override
    public void visitLocalVariable(final String name, final String descriptor, final String signature,
            final Label start, final Label end, final int index) {
        Queue<Block> blocks = new LinkedList<Block>();
        Set<Block> visited = new HashSet<Block>();
        Block last = getBasicBlock(end);

        // Start
        blocks.add(getBasicBlock(start));

        // Update variable name for all blocks.
        while (!blocks.isEmpty()) {
            Block b = blocks.remove();

            visited.add(b);

            getVariable(b, index, null).setName(name);

            // Update successors until end label.
            for (Block s : b.getSuccessors()) {
                if ((s != last) && (s != null) && !visited.contains(s)) {
                    blocks.add(s);
                }
            }
        }
    }

    /**
     * Returns a unique variable per index per block. This allows variable names
     * to be retrospectively added.
     *
     * @param block      Block
     * @param index      Index of the variable.
     * @param type       Type of the variable in the given context (this will be
     *                   adjusted to equal the variable type, provided the
     *                   variable type is specialisation of it). For no context,
     *                   this can be supplied as <code>null</code>.
     * @return           Variable.
     */
    public Variable getVariable(Block block, int index, Type type) {
        // Block must exist before getting variables for it.
        if (!variables.containsKey(block)) {
            throw new RuntimeException("getVariable called before getBasicBlock for " + block + ".");
        }

        // Get variable.
        Variable var = variables.get(block).get(new Integer(index));

        if (var == null) {
            var = new Variable(index, type);
            variables.get(block).put(new Integer(index), var);
        }

        // Update context type if supplied.
        if (type != null) {
            type.unify(var.getType());
        }

        return var;
    }

    /**
     * Returns a basic block corresponding to a raw label in the bytecode.
     *
     * @param  label Label to look up.
     * @return       Corresponding basic block.
     */
    public BasicBlock getBasicBlock(final Label label) {
        BasicBlock section = labels.get(label);

        if (section == null) {
            section = new BasicBlock();
            labels.put(label, section);
            variables.put(section, new HashMap<Integer, Variable>());
        }

        return section;
    }

    /**
     * Finishes the current block.
     *
     * @param next   Next block to chain on (<code>null</code> for none).
     */
    private void finishBlock(BasicBlock next) {
        // Cache instruction ordering and reset.
        BlockExporter.cacheCode(current, ordered);
        ordered = new LinkedList<Instruction>();

        // Set final element of basic block.
        current.setNext(next);

        // Declare values that will be emitted and expected by successors.
        current.setValuesOut(stack);

        for (Block b : current.getSuccessors()) {
            if (b instanceof BasicBlock) {
                ((BasicBlock) b).setTypesIn(stack);
            } else {
                throw new RuntimeException("Non-basic block not expected at import-time.");
            }
        }

        // Move onto next basic block.
        setCurrent(next);
    }

    /**
     * Sets the current block to be that given.
     *
     * @param block  Block to set.
     */
    private void setCurrent(BasicBlock block) {
        if (block == null) {
            stack = null;
            current = null;
        } else {
            stack = new Stack<Producer>();
            current = block;

            // Add restore stack instructions.
            int index = 0;

            for (Type t : current.getTypesIn()) {
                stack.push(new RestoreStack(index++, t));
            }
        }
    }

    /**
     * Creation of arithmetic instruction.
     */
    private void createArithmetic(Arithmetic.Operator operator, Type type) {
        Producer operandB = stack.pop();
        Producer operandA = stack.pop();

        assert ((type == operandA.getType()) && (operandA.getType() == operandB.getType()));

        ordered.add(stack.push(new Arithmetic(operator, operandA, operandB)));
    }

    /**
     * Creation of floating point compare instruction.
     */
    private void createCompare(boolean variant, Type type) {
        Producer operandB = stack.pop();
        Producer operandA = stack.pop();

        ordered.add(stack.push(new Compare(variant, type, operandA, operandB)));
    }

    /**
     * Creation of constant instruction
     */
    private void createConstant(Object object) {
        ordered.add(stack.push(new Constant(object)));
    }

    /**
     * Creation of negation operator.
     */
    private void createNegate() {
        ordered.add(stack.push(new Negate(stack.pop())));
    }

    /**
     * Creation of primitive type conversion instruction.
     */
    private void createConvert(Type type) {
        ordered.add(stack.push(new Convert(stack.pop(), type)));
    }

    /**
     * Creation of array read instruction.
     */
    private void createArrayRead(Type type) {
        Producer index = stack.pop();
        Producer array = stack.pop();

        Read r = new Read(new ArrayElement(array, index, type));

        current.getStateful().add(r);
        stack.push(r);
        ordered.add(r);
    }

    /**
     * Creation of array write instruction.
     */
    private void createArrayWrite(Type type) {
        Producer value = stack.pop();
        Producer index = stack.pop();
        Producer array = stack.pop();

        Write w = new Write(new ArrayElement(array, index, type), value);

        current.getStateful().add(w);
        ordered.add(w);
    }

    /**
     * Creation of return instruction.
     */
    private void createReturn(Type type) {
        Branch b = (type == null) ? new Return() : new ValueReturn(stack.pop());

        current.setBranch(b);
        ordered.add(b);

        finishBlock(null);
    }

    /**
     * Creation of throw instruction.
     */
    private void createThrow() {
        Throw t = new Throw(stack.pop());

        current.setBranch(t);
        ordered.add(t);

        finishBlock(null);
    }

    /**
     * Creation of variable read instruction.
     */
    private void createVarRead(int var, Type type) {
        Read r = new Read(getVariable(current, var, type));

        current.getStateful().add(r);
        stack.push(r);
        ordered.add(r);
    }

    /**
     * Creation of variable write instruction.
     */
    private void createVarWrite(int var, Type type) {
        Write r = new Write(getVariable(current, var, type), stack.pop());

        current.getStateful().add(r);
        ordered.add(r);
    }

    /**
     * Creation of field read instruction.
     */
    private void createFieldRead(Field field) {
        Read r;

        if (field.getModifiers().contains(Modifier.STATIC)) {
            r = new Read(field);
        } else {
            r = new Read(new InstanceField(stack.pop(), field));
        }

        current.getStateful().add(r);
        stack.push(r);
        ordered.add(r);
    }

    /**
     * Creation of field write instruction.
     */
    private void createFieldWrite(Field field) {
        Write w;

        if (field.getModifiers().contains(Modifier.STATIC)) {
            w = new Write(field, stack.pop());
        } else {
            Producer value = stack.pop();
            Producer object = stack.pop();

            w = new Write(new InstanceField(object, field), value);
        }

        current.getStateful().add(w);
        ordered.add(w);
    }

    /**
     * Creation of comparison with zero.
     */
    private void createZeroCondition(Condition.Operator operator, BasicBlock destination) {
        Producer operandB = new Constant(new Integer(0));
        Producer operandA = stack.pop();
        Condition c = new Condition(operator, destination, operandA, operandB);

        current.setBranch(c);
        ordered.add(c);

        // New basic block, and copy across variables.
        BasicBlock next = new BasicBlock();
        variables.put(next, variables.get(current));
        finishBlock(next);
    }

    /**
     * Creation of comparison with NULL.
     */
    private void createNullCondition(Condition.Operator operator, BasicBlock destination) {
        Producer operandB = new Constant(null);
        Producer operandA = stack.pop();
        Condition c = new Condition(operator, destination, operandA, operandB);

        current.setBranch(c);
        ordered.add(c);

        // New basic block, and copy across variables.
        BasicBlock next = new BasicBlock();
        variables.put(next, variables.get(current));
        finishBlock(next);
    }

    /**
     * Creation of comparison.
     */
    private void createCondition(Condition.Operator operator, BasicBlock destination) {
        Producer operandB = stack.pop();
        Producer operandA = stack.pop();
        Condition c = new Condition(operator, destination, operandA, operandB);

        current.setBranch(c);
        ordered.add(c);

        // New basic block, and copy across variables.
        BasicBlock next = new BasicBlock();
        variables.put(next, variables.get(current));
        finishBlock(next);
    }

    /**
     * Creation of a switch branch.
     */
    private void createSwitch(Map<Integer, Block> destinations, BasicBlock dflt) {
        Switch s = new Switch(stack.pop(), destinations, dflt);

        current.setBranch(s);
        ordered.add(s);

        finishBlock(null);
    }
}