org.jacoco.core.internal.instr.FrameTracker.java Source code

Java tutorial

Introduction

Here is the source code for org.jacoco.core.internal.instr.FrameTracker.java

Source

/*******************************************************************************
 * Copyright (c) 2009, 2013 Mountainminds GmbH & Co. KG and Contributors
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Marc R. Hoffmann - initial API and implementation
 *    
 *******************************************************************************/
package org.jacoco.core.internal.instr;

import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

/**
 * This method adapter tracks the state of the local variable and stack types.
 * With insertFrame() additional frames can then be added. The adapter is only
 * intended to be used with class file versions >= {@link Opcodes#V1_6}.
 */
class FrameTracker extends MethodVisitor implements IFrameInserter {

    private final String owner;

    private Object[] local;
    private int localSize;
    private Object[] stack;
    private int stackSize;

    public FrameTracker(final String owner, final int access, final String name, final String desc,
            final MethodVisitor mv) {
        super(Opcodes.ASM4, mv);
        this.owner = owner;
        local = new Object[8];
        localSize = 0;
        stack = new Object[8];
        stackSize = 0;

        if ((access & Opcodes.ACC_STATIC) == 0) {
            if ("<init>".equals(name)) {
                set(localSize, Opcodes.UNINITIALIZED_THIS);
            } else {
                set(localSize, owner);
            }
        }
        for (final Type t : Type.getArgumentTypes(desc)) {
            set(localSize, t);
        }

    }

    public void insertFrame() {
        // Reduced types do not need more space than expanded types:
        final Object[] local = new Object[this.localSize];
        final Object[] stack = new Object[this.stackSize];
        final int localSize = reduce(this.local, this.localSize, local);
        final int stackSize = reduce(this.stack, this.stackSize, stack);
        mv.visitFrame(Opcodes.F_NEW, localSize, local, stackSize, stack);
    }

    @Override
    public void visitFrame(final int type, final int nLocal, final Object[] local, final int nStack,
            final Object[] stack) {

        if (type != Opcodes.F_NEW) {
            throw new IllegalArgumentException("ClassReader.accept() should be called with EXPAND_FRAMES flag");
        }

        // expanded types need at most twice the size
        this.local = ensureSize(this.local, nLocal * 2);
        this.stack = ensureSize(this.stack, nStack * 2);
        this.localSize = expand(local, nLocal, this.local);
        this.stackSize = expand(stack, nStack, this.stack);

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

    @Override
    public void visitInsn(final int opcode) {
        final Object t1, t2, t3, t4;
        switch (opcode) {
        case Opcodes.NOP:
        case Opcodes.RETURN:
            break;
        case Opcodes.ARETURN:
        case Opcodes.ATHROW:
        case Opcodes.FRETURN:
        case Opcodes.IRETURN:
        case Opcodes.MONITORENTER:
        case Opcodes.MONITOREXIT:
        case Opcodes.POP:
            pop(1);
            break;
        case Opcodes.DRETURN:
        case Opcodes.LRETURN:
        case Opcodes.POP2:
            pop(2);
            break;
        case Opcodes.AASTORE:
        case Opcodes.BASTORE:
        case Opcodes.CASTORE:
        case Opcodes.FASTORE:
        case Opcodes.IASTORE:
        case Opcodes.SASTORE:
            pop(3);
            break;
        case Opcodes.LASTORE:
        case Opcodes.DASTORE:
            pop(4);
            break;
        case Opcodes.ICONST_M1:
        case Opcodes.ICONST_0:
        case Opcodes.ICONST_1:
        case Opcodes.ICONST_2:
        case Opcodes.ICONST_3:
        case Opcodes.ICONST_4:
        case Opcodes.ICONST_5:
            push(Opcodes.INTEGER);
            break;
        case Opcodes.ARRAYLENGTH:
        case Opcodes.F2I:
        case Opcodes.I2B:
        case Opcodes.I2C:
        case Opcodes.I2S:
        case Opcodes.INEG:
            pop(1);
            push(Opcodes.INTEGER);
            break;
        case Opcodes.BALOAD:
        case Opcodes.CALOAD:
        case Opcodes.D2I:
        case Opcodes.FCMPG:
        case Opcodes.FCMPL:
        case Opcodes.IADD:
        case Opcodes.IALOAD:
        case Opcodes.IAND:
        case Opcodes.IDIV:
        case Opcodes.IMUL:
        case Opcodes.IOR:
        case Opcodes.IREM:
        case Opcodes.ISHL:
        case Opcodes.ISHR:
        case Opcodes.ISUB:
        case Opcodes.IUSHR:
        case Opcodes.IXOR:
        case Opcodes.L2I:
        case Opcodes.SALOAD:
            pop(2);
            push(Opcodes.INTEGER);
            break;
        case Opcodes.DCMPG:
        case Opcodes.DCMPL:
        case Opcodes.LCMP:
            pop(4);
            push(Opcodes.INTEGER);
            break;
        case Opcodes.FCONST_0:
        case Opcodes.FCONST_1:
        case Opcodes.FCONST_2:
            push(Opcodes.FLOAT);
            break;
        case Opcodes.FNEG:
        case Opcodes.I2F:
            pop(1);
            push(Opcodes.FLOAT);
            break;
        case Opcodes.D2F:
        case Opcodes.FADD:
        case Opcodes.FALOAD:
        case Opcodes.FDIV:
        case Opcodes.FMUL:
        case Opcodes.FREM:
        case Opcodes.FSUB:
        case Opcodes.L2F:
            pop(2);
            push(Opcodes.FLOAT);
            break;
        case Opcodes.LCONST_0:
        case Opcodes.LCONST_1:
            push(Opcodes.LONG);
            push(Opcodes.TOP);
            break;
        case Opcodes.F2L:
        case Opcodes.I2L:
            pop(1);
            push(Opcodes.LONG);
            push(Opcodes.TOP);
            break;
        case Opcodes.D2L:
        case Opcodes.LALOAD:
        case Opcodes.LNEG:
            pop(2);
            push(Opcodes.LONG);
            push(Opcodes.TOP);
            break;
        case Opcodes.LSHL:
        case Opcodes.LSHR:
        case Opcodes.LUSHR:
            pop(3);
            push(Opcodes.LONG);
            push(Opcodes.TOP);
            break;
        case Opcodes.LADD:
        case Opcodes.LAND:
        case Opcodes.LDIV:
        case Opcodes.LMUL:
        case Opcodes.LOR:
        case Opcodes.LREM:
        case Opcodes.LSUB:
        case Opcodes.LXOR:
            pop(4);
            push(Opcodes.LONG);
            push(Opcodes.TOP);
            break;
        case Opcodes.DCONST_0:
        case Opcodes.DCONST_1:
            push(Opcodes.DOUBLE);
            push(Opcodes.TOP);
            break;
        case Opcodes.F2D:
        case Opcodes.I2D:
            pop(1);
            push(Opcodes.DOUBLE);
            push(Opcodes.TOP);
            break;
        case Opcodes.DALOAD:
        case Opcodes.DNEG:
        case Opcodes.L2D:
            pop(2);
            push(Opcodes.DOUBLE);
            push(Opcodes.TOP);
            break;
        case Opcodes.DADD:
        case Opcodes.DDIV:
        case Opcodes.DMUL:
        case Opcodes.DREM:
        case Opcodes.DSUB:
            pop(4);
            push(Opcodes.DOUBLE);
            push(Opcodes.TOP);
            break;
        case Opcodes.ACONST_NULL:
            push(Opcodes.NULL);
            break;
        case Opcodes.AALOAD:
            pop(1);
            t1 = pop();
            push(Type.getType(((String) t1).substring(1)));
            break;
        case Opcodes.DUP:
            t1 = pop();
            push(t1);
            push(t1);
            break;
        case Opcodes.DUP_X1:
            t1 = pop();
            t2 = pop();
            push(t1);
            push(t2);
            push(t1);
            break;
        case Opcodes.DUP_X2:
            t1 = pop();
            t2 = pop();
            t3 = pop();
            push(t1);
            push(t3);
            push(t2);
            push(t1);
            break;
        case Opcodes.DUP2:
            t1 = pop();
            t2 = pop();
            push(t2);
            push(t1);
            push(t2);
            push(t1);
            break;
        case Opcodes.DUP2_X1:
            t1 = pop();
            t2 = pop();
            t3 = pop();
            push(t2);
            push(t1);
            push(t3);
            push(t2);
            push(t1);
            break;
        case Opcodes.DUP2_X2:
            t1 = pop();
            t2 = pop();
            t3 = pop();
            t4 = pop();
            push(t2);
            push(t1);
            push(t4);
            push(t3);
            push(t2);
            push(t1);
            break;
        case Opcodes.SWAP:
            t1 = pop();
            t2 = pop();
            push(t1);
            push(t2);
            break;
        default:
            throw new IllegalArgumentException();
        }
        mv.visitInsn(opcode);
    }

    @Override
    public void visitIntInsn(final int opcode, final int operand) {
        switch (opcode) {
        case Opcodes.BIPUSH:
        case Opcodes.SIPUSH:
            push(Opcodes.INTEGER);
            break;
        case Opcodes.NEWARRAY:
            pop(1);
            switch (operand) {
            case Opcodes.T_BOOLEAN:
                push("[Z");
                break;
            case Opcodes.T_CHAR:
                push("[C");
                break;
            case Opcodes.T_FLOAT:
                push("[F");
                break;
            case Opcodes.T_DOUBLE:
                push("[D");
                break;
            case Opcodes.T_BYTE:
                push("[B");
                break;
            case Opcodes.T_SHORT:
                push("[S");
                break;
            case Opcodes.T_INT:
                push("[I");
                break;
            case Opcodes.T_LONG:
                push("[J");
                break;
            default:
                throw new IllegalArgumentException();
            }
            break;
        default:
            throw new IllegalArgumentException();
        }
        mv.visitIntInsn(opcode, operand);
    }

    @Override
    public void visitVarInsn(final int opcode, final int var) {
        final Object t;
        switch (opcode) {
        case Opcodes.ALOAD:
            push(get(var));
            break;
        case Opcodes.ILOAD:
            push(Opcodes.INTEGER);
            break;
        case Opcodes.FLOAD:
            push(Opcodes.FLOAT);
            break;
        case Opcodes.LLOAD:
            push(Opcodes.LONG);
            push(Opcodes.TOP);
            break;
        case Opcodes.DLOAD:
            push(Opcodes.DOUBLE);
            push(Opcodes.TOP);
            break;
        case Opcodes.ASTORE:
        case Opcodes.ISTORE:
        case Opcodes.FSTORE:
            t = pop();
            set(var, t);
            break;
        case Opcodes.LSTORE:
        case Opcodes.DSTORE:
            pop(1);
            t = pop();
            set(var, t);
            set(var + 1, Opcodes.TOP);
            break;
        default:
            throw new IllegalArgumentException();
        }
        mv.visitVarInsn(opcode, var);
    }

    @Override
    public void visitTypeInsn(final int opcode, final String type) {
        switch (opcode) {
        case Opcodes.NEW:
            final Label label = new Label();
            mv.visitLabel(label);
            push(label);
            break;
        case Opcodes.ANEWARRAY:
            pop(1);
            push('[' + Type.getObjectType(type).getDescriptor());
            break;
        case Opcodes.CHECKCAST:
            pop(1);
            push(type);
            break;
        case Opcodes.INSTANCEOF:
            pop(1);
            push(Opcodes.INTEGER);
            break;
        default:
            throw new IllegalArgumentException();
        }
        mv.visitTypeInsn(opcode, type);
    }

    @Override
    public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) {
        final Type t = Type.getType(desc);
        switch (opcode) {
        case Opcodes.PUTSTATIC:
            pop(t);
            break;
        case Opcodes.PUTFIELD:
            pop(t);
            pop(1);
            break;
        case Opcodes.GETSTATIC:
            push(t);
            break;
        case Opcodes.GETFIELD:
            pop(1);
            push(t);
            break;
        default:
            throw new IllegalArgumentException();
        }
        mv.visitFieldInsn(opcode, owner, name, desc);
    }

    @Override
    public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) {
        for (final Type t : Type.getArgumentTypes(desc)) {
            pop(t);
        }
        if (opcode != Opcodes.INVOKESTATIC) {
            final Object target = pop();
            if (target == Opcodes.UNINITIALIZED_THIS) {
                replace(Opcodes.UNINITIALIZED_THIS, this.owner);
            } else if (target instanceof Label) {
                replace(target, owner);
            }
        }
        push(Type.getReturnType(desc));

        mv.visitMethodInsn(opcode, owner, name, desc);
    }

    @Override
    public void visitInvokeDynamicInsn(final String name, final String desc, final Handle bsm,
            final Object... bsmArgs) {
        for (final Type t : Type.getArgumentTypes(desc)) {
            pop(t);
        }
        push(Type.getReturnType(desc));

        mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
    }

    @Override
    public void visitLdcInsn(final Object cst) {
        if (cst instanceof Integer) {
            push(Opcodes.INTEGER);
        } else if (cst instanceof Float) {
            push(Opcodes.FLOAT);
        } else if (cst instanceof Long) {
            push(Opcodes.LONG);
            push(Opcodes.TOP);
        } else if (cst instanceof Double) {
            push(Opcodes.DOUBLE);
            push(Opcodes.TOP);
        } else if (cst instanceof String) {
            push("java/lang/String");
        } else if (cst instanceof Type) {
            push("java/lang/Class");
        } else {
            throw new IllegalArgumentException();
        }
        mv.visitLdcInsn(cst);
    }

    @Override
    public void visitJumpInsn(final int opcode, final Label label) {
        switch (opcode) {
        case Opcodes.GOTO:
            break;
        case Opcodes.IFEQ:
        case Opcodes.IFNE:
        case Opcodes.IFLT:
        case Opcodes.IFGE:
        case Opcodes.IFGT:
        case Opcodes.IFLE:
        case Opcodes.IFNULL:
        case Opcodes.IFNONNULL:
            pop(1);
            break;
        case Opcodes.IF_ICMPEQ:
        case Opcodes.IF_ICMPNE:
        case Opcodes.IF_ICMPLT:
        case Opcodes.IF_ICMPGE:
        case Opcodes.IF_ICMPGT:
        case Opcodes.IF_ICMPLE:
        case Opcodes.IF_ACMPEQ:
        case Opcodes.IF_ACMPNE:
            pop(2);
            break;
        default:
            throw new IllegalArgumentException();
        }
        mv.visitJumpInsn(opcode, label);
    }

    @Override
    public void visitIincInsn(final int var, final int increment) {
        set(var, Opcodes.INTEGER);
        mv.visitIincInsn(var, increment);
    }

    @Override
    public void visitTableSwitchInsn(final int min, final int max, final Label dflt, final Label... labels) {
        pop(1);
        mv.visitTableSwitchInsn(min, max, dflt, labels);
    }

    @Override
    public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) {
        pop(1);
        mv.visitLookupSwitchInsn(dflt, keys, labels);
    }

    @Override
    public void visitMultiANewArrayInsn(final String desc, final int dims) {
        pop(dims);
        push(desc);
        mv.visitMultiANewArrayInsn(desc, dims);
    }

    private void push(final Object type) {
        stack = ensureSize(stack, stackSize + 1);
        stack[stackSize] = type;
        stackSize++;
    }

    private void push(final Type type) {
        switch (type.getSort()) {
        case Type.VOID:
            break;
        case Type.BOOLEAN:
        case Type.BYTE:
        case Type.CHAR:
        case Type.INT:
        case Type.SHORT:
            push(Opcodes.INTEGER);
            break;
        case Type.FLOAT:
            push(Opcodes.FLOAT);
            break;
        case Type.LONG:
            push(Opcodes.LONG);
            push(Opcodes.TOP);
            break;
        case Type.DOUBLE:
            push(Opcodes.DOUBLE);
            push(Opcodes.TOP);
            break;
        case Type.ARRAY:
        case Type.OBJECT:
            push(type.getInternalName());
            break;
        default:
            throw new AssertionError(type);
        }
    }

    private Object pop() {
        stackSize--;
        assertValidFrames(stackSize);
        return stack[stackSize];
    }

    private void pop(final int count) {
        stackSize -= count;
        assertValidFrames(stackSize);
    }

    private void assertValidFrames(final int stackSize) {
        if (stackSize < 0) {
            throw new IllegalStateException("Missing or invalid stackmap frames.");
        }
    }

    private void pop(final Type type) {
        pop(type.getSize());
    }

    private void set(final int pos, final Object type) {
        local = ensureSize(local, pos + 1);
        // fill gaps:
        for (int i = localSize; i < pos; i++) {
            local[i] = Opcodes.TOP;
        }
        localSize = Math.max(localSize, pos + 1);
        local[pos] = type;
    }

    private void set(final int pos, final Type type) {
        switch (type.getSort()) {
        case Type.BOOLEAN:
        case Type.BYTE:
        case Type.CHAR:
        case Type.INT:
        case Type.SHORT:
            set(pos, Opcodes.INTEGER);
            break;
        case Type.FLOAT:
            set(pos, Opcodes.FLOAT);
            break;
        case Type.LONG:
            set(pos, Opcodes.LONG);
            set(pos + 1, Opcodes.TOP);
            break;
        case Type.DOUBLE:
            set(pos, Opcodes.DOUBLE);
            set(pos + 1, Opcodes.TOP);
            break;
        case Type.ARRAY:
        case Type.OBJECT:
            set(pos, type.getInternalName());
            break;
        default:
            throw new AssertionError(type);
        }
    }

    private Object get(final int pos) {
        return local[pos];
    }

    private Object[] ensureSize(final Object[] array, final int size) {
        if (array.length >= size) {
            return array;
        }
        int newLength = array.length;
        while (newLength < size) {
            newLength *= 2;
        }
        final Object[] newArray = new Object[newLength];
        System.arraycopy(array, 0, newArray, 0, array.length);
        return newArray;
    }

    /**
     * Expand double word types into two slots.
     */
    private int expand(final Object[] source, final int size, final Object[] target) {
        int targetIdx = 0;
        for (int sourceIdx = 0; sourceIdx < size; sourceIdx++) {
            final Object type = source[sourceIdx];
            target[targetIdx++] = type;
            if (type == Opcodes.LONG || type == Opcodes.DOUBLE) {
                target[targetIdx++] = Opcodes.TOP;
            }
        }
        return targetIdx;
    }

    /**
     * Reduce double word types into a single slot.
     */
    private int reduce(final Object[] source, final int size, final Object[] target) {
        int targetIdx = 0;
        for (int sourceIdx = 0; sourceIdx < size; sourceIdx++) {
            final Object type = source[sourceIdx];
            target[targetIdx++] = type;
            if (type == Opcodes.LONG || type == Opcodes.DOUBLE) {
                sourceIdx++;
            }
        }
        return targetIdx;
    }

    /**
     * Replaces a type in the locals and on the stack. This is used for
     * uninitialized objects.
     * 
     * @param oldtype
     *            type to replace
     * @param newtype
     *            replacement type
     */
    private void replace(final Object oldtype, final Object newtype) {
        for (int i = 0; i < localSize; i++) {
            if (oldtype.equals(local[i])) {
                local[i] = newtype;
            }
        }
        for (int i = 0; i < stackSize; i++) {
            if (oldtype.equals(stack[i])) {
                stack[i] = newtype;
            }
        }
    }

}