ClassInstrumenter.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 static java.lang.String.format;
import org.jacoco.core.internal.flow.ClassProbesVisitor;
import org.jacoco.core.internal.flow.MethodProbesVisitor;
import org.jacoco.core.runtime.IExecutionDataAccessorGenerator;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
/**
* Adapter that instruments a class for coverage tracing.
*/
public class ClassInstrumenter extends ClassProbesVisitor {
private static final Object[] STACK_ARRZ = new Object[] { InstrSupport.DATAFIELD_DESC };
private static final Object[] NO_LOCALS = new Object[0];
private final long id;
private final IExecutionDataAccessorGenerator accessorGenerator;
private IProbeArrayStrategy probeArrayStrategy;
private String className;
private boolean withFrames;
private int probeCount;
/**
* Emits a instrumented version of this class to the given class visitor.
*
* @param id
* unique identifier given to this class
* @param accessorGenerator
* this generator will be used for instrumentation
* @param cv
* next delegate in the visitor chain will receive the
* instrumented class
*/
public ClassInstrumenter(final long id,
final IExecutionDataAccessorGenerator accessorGenerator,
final ClassVisitor cv) {
super(cv);
this.id = id;
this.accessorGenerator = accessorGenerator;
}
@Override
public void visit(final int version, final int access, final String name,
final String signature, final String superName,
final String[] interfaces) {
this.className = name;
withFrames = (version & 0xff) >= Opcodes.V1_6;
if ((access & Opcodes.ACC_INTERFACE) == 0) {
this.probeArrayStrategy = new ClassTypeStrategy();
} else {
this.probeArrayStrategy = new InterfaceTypeStrategy();
}
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public FieldVisitor visitField(final int access, final String name,
final String desc, final String signature, final Object value) {
assertNotInstrumented(name, InstrSupport.DATAFIELD_NAME);
return super.visitField(access, name, desc, signature, value);
}
@Override
public MethodProbesVisitor visitMethod(final int access, final String name,
final String desc, final String signature, final String[] exceptions) {
assertNotInstrumented(name, InstrSupport.INITMETHOD_NAME);
final MethodVisitor mv = cv.visitMethod(access, name, desc, signature,
exceptions);
if (mv == null) {
return null;
}
final MethodVisitor frameEliminator = new DuplicateFrameEliminator(mv);
final ProbeInserter probeVariableInserter = new ProbeInserter(access,
desc, frameEliminator, probeArrayStrategy);
if (withFrames) {
final FrameTracker frameTracker = new FrameTracker(className,
access, name, desc, probeVariableInserter);
return new MethodInstrumenter(frameTracker, probeVariableInserter,
frameTracker);
} else {
return new MethodInstrumenter(probeVariableInserter,
probeVariableInserter, IFrameInserter.NOP);
}
}
@Override
public void visitTotalProbeCount(final int count) {
probeCount = count;
}
@Override
public void visitEnd() {
probeArrayStrategy.addMembers(cv);
super.visitEnd();
}
/**
* Ensures that the given member does not correspond to a internal member
* created by the instrumentation process. This would mean that the class
* has been instrumented twice.
*
* @param member
* name of the member to check
* @param instrMember
* name of a instrumentation member
* @throws IllegalStateException
* thrown if the member has the same name than the
* instrumentation member
*/
private void assertNotInstrumented(final String member,
final String instrMember) throws IllegalStateException {
if (member.equals(instrMember)) {
throw new IllegalStateException(format(
"Class %s is already instrumented.", className));
}
}
// === probe array strategies ===
private class ClassTypeStrategy implements IProbeArrayStrategy {
public int storeInstance(final MethodVisitor mv, final int variable) {
mv.visitMethodInsn(Opcodes.INVOKESTATIC, className,
InstrSupport.INITMETHOD_NAME, InstrSupport.INITMETHOD_DESC);
mv.visitVarInsn(Opcodes.ASTORE, variable);
return 1;
}
public void addMembers(final ClassVisitor delegate) {
createDataField();
createInitMethod(probeCount);
}
private void createDataField() {
cv.visitField(InstrSupport.DATAFIELD_ACC,
InstrSupport.DATAFIELD_NAME, InstrSupport.DATAFIELD_DESC,
null, null);
}
private void createInitMethod(final int probeCount) {
final MethodVisitor mv = cv.visitMethod(
InstrSupport.INITMETHOD_ACC, InstrSupport.INITMETHOD_NAME,
InstrSupport.INITMETHOD_DESC, null, null);
mv.visitCode();
// Load the value of the static data field:
mv.visitFieldInsn(Opcodes.GETSTATIC, className,
InstrSupport.DATAFIELD_NAME, InstrSupport.DATAFIELD_DESC);
mv.visitInsn(Opcodes.DUP);
// Stack[1]: [Z
// Stack[0]: [Z
// Skip initialization when we already have a data array:
final Label alreadyInitialized = new Label();
mv.visitJumpInsn(Opcodes.IFNONNULL, alreadyInitialized);
// Stack[0]: [Z
mv.visitInsn(Opcodes.POP);
final int size = genInitializeDataField(mv, probeCount);
// Stack[0]: [Z
// Return the class' probe array:
if (withFrames) {
mv.visitFrame(Opcodes.F_NEW, 0, NO_LOCALS, 1, STACK_ARRZ);
}
mv.visitLabel(alreadyInitialized);
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(Math.max(size, 2), 0); // Maximum local stack size is 2
mv.visitEnd();
}
/**
* Generates the byte code to initialize the static coverage data field
* within this class.
*
* The code will push the [Z data array on the operand stack.
*
* @param mv
* generator to emit code to
*/
private int genInitializeDataField(final MethodVisitor mv,
final int probeCount) {
final int size = accessorGenerator.generateDataAccessor(id,
className, probeCount, mv);
// Stack[0]: [Z
mv.visitInsn(Opcodes.DUP);
// Stack[1]: [Z
// Stack[0]: [Z
mv.visitFieldInsn(Opcodes.PUTSTATIC, className,
InstrSupport.DATAFIELD_NAME, InstrSupport.DATAFIELD_DESC);
// Stack[0]: [Z
return Math.max(size, 2); // Maximum local stack size is 2
}
}
private class InterfaceTypeStrategy implements IProbeArrayStrategy {
public int storeInstance(final MethodVisitor mv, final int variable) {
final int maxStack = accessorGenerator.generateDataAccessor(id,
className, probeCount, mv);
mv.visitVarInsn(Opcodes.ASTORE, variable);
return maxStack;
}
public void addMembers(final ClassVisitor delegate) {
// nothing to do
}
}
}