FrameTracker.java
/*******************************************************************************
* 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;
}
}
}
}