/*
* Cobertura - http://cobertura.sourceforge.net/
*
* Copyright (C) 2011 Piotr Tabor
*
* Note: This file is dual licensed under the GPL and the Apache
* Source License (so that it can be used from both the main
* Cobertura classes and the ant tasks).
*
* Cobertura is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation; either version 2 of the License,
* or (at your option) any later version.
*
* Cobertura is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Cobertura; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
package net.sourceforge.cobertura.instrument.pass3;
import net.sourceforge.cobertura.coveragedata.LightClassmapListener;
import net.sourceforge.cobertura.coveragedata.TouchCollector;
import net.sourceforge.cobertura.instrument.tp.*;
import org.objectweb.asm.*;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
/**
* Common method used by most of {@link AbstractCodeProvider} implementations.
* The methods realized here are independent on counters storing structure.
*
* @author piotr.tabor@gmail.com
*/
public abstract class AbstractCodeProvider implements CodeProvider {
/**
* CounterId used to store unnecessary events to avoid fake jump counting in
* instrumented(generated) code
*/
public static final int FAKE_COUNTER_ID = 0;
public AbstractCodeProvider() {
super();
}
public void generateCodeThatSetsJumpCounterIdVariable(
MethodVisitor nextMethodVisitor, int new_value,
int lastJumpIdVariableIndex) {
nextMethodVisitor.visitLdcInsn(new_value);
nextMethodVisitor.visitVarInsn(Opcodes.ISTORE, lastJumpIdVariableIndex);
}
public void generateCodeThatZeroJumpCounterIdVariable(
MethodVisitor nextMethodVisitor, int lastJumpIdVariableIndex) {
generateCodeThatSetsJumpCounterIdVariable(nextMethodVisitor,
FAKE_COUNTER_ID, lastJumpIdVariableIndex);
}
public void generateCodeThatIncrementsCoberturaCounterIfVariableEqualsAndCleanVariable(
MethodVisitor nextMethodVisitor,
Integer neededJumpCounterIdVariableValue,
Integer counterIdToIncrement, int lastJumpIdVariableIndex,
String className) {
nextMethodVisitor.visitLdcInsn((int) neededJumpCounterIdVariableValue);
nextMethodVisitor.visitVarInsn(Opcodes.ILOAD, lastJumpIdVariableIndex);
Label afterJump = new Label();
nextMethodVisitor.visitJumpInsn(Opcodes.IF_ICMPNE, afterJump);
generateCodeThatIncrementsCoberturaCounter(nextMethodVisitor,
counterIdToIncrement, className);
generateCodeThatZeroJumpCounterIdVariable(nextMethodVisitor,
lastJumpIdVariableIndex);
nextMethodVisitor.visitLabel(afterJump);
}
/**
* {@inheritDoc}
* <p/>
* The code injected by this implementation just registers the class using {@link TouchCollector#registerClass(Class)}. This way, during the
* execution, touch collector knows that is responsible to ask the class after execution about a current status of the counters.
*/
protected void generateRegisterClass(MethodVisitor mv, String className) {
mv.visitLdcInsn(className);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, Type
.getInternalName(TouchCollector.class), "registerClass",
"(Ljava/lang/String;)V");
}
final String CLASSMAP_LISTENER_INTERNALNAME = Type
.getInternalName(LightClassmapListener.class);
/**
* {@inheritDoc}<br/><br/>
* <p/>
* Generates method (named {@link #COBERTURA_CLASSMAP_METHOD_NAME}) with such a signature:
* __cobertura_classmap( {@link LightClassmapListener} listener).</br>
* <p/>
* The method informs the listener about all lines, jumps and switches found, and about all counters tracking
* the constructions.
*/
public void generateCoberturaClassMapMethod(ClassVisitor cv,
ClassMap classMap) {
LinkedList<TouchPointDescriptor> touchPointDescriptors = new LinkedList<TouchPointDescriptor>(
classMap.getTouchPointsInLineOrder());
int parts = 0;
for (int j = 0; touchPointDescriptors.size() > 0; j++) {
List<TouchPointDescriptor> bufor = new LinkedList<TouchPointDescriptor>();
for (int i = 0; i < 1000 && touchPointDescriptors.size() > 0; i++) {
bufor.add(touchPointDescriptors.removeFirst());
}
classMapContent(cv, j, bufor);
parts++;
}
MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC
| Opcodes.ACC_STATIC, COBERTURA_CLASSMAP_METHOD_NAME, "("
+ Type.getType(LightClassmapListener.class).toString() + ")V",
null, null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(classMap.getClassName());
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE,
CLASSMAP_LISTENER_INTERNALNAME, "setClazz",
"(Ljava/lang/String;)V");
if (classMap.getSource() != null) {
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(classMap.getSource());
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE,
CLASSMAP_LISTENER_INTERNALNAME, "setSource",
"(Ljava/lang/String;)V");
}
for (int i = 0; i < parts; i++) {
mv.visitInsn(Opcodes.DUP);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, classMap.getClassName(),
COBERTURA_CLASSMAP_METHOD_NAME + "_" + i, "("
+ Type.getType(LightClassmapListener.class)
.toString() + ")V");
}
mv.visitInsn(Opcodes.POP);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(0, 0);//will be recalculated by writer
mv.visitEnd();
}
enum Abcd {
A, B, C;
}
private void classMapContent(ClassVisitor cv, int nr,
List<TouchPointDescriptor> touchPointDescriptors) {
MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC
| Opcodes.ACC_STATIC,
COBERTURA_CLASSMAP_METHOD_NAME + "_" + nr, "("
+ Type.getType(LightClassmapListener.class).toString()
+ ")V", null, null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
for (TouchPointDescriptor tpd : touchPointDescriptors) {
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(tpd.getLineNumber());
if (tpd instanceof LineTouchPointDescriptor) {
mv
.visitLdcInsn(((LineTouchPointDescriptor) tpd)
.getCounterId());
mv.visitLdcInsn(((LineTouchPointDescriptor) tpd)
.getMethodName());
mv.visitLdcInsn(((LineTouchPointDescriptor) tpd)
.getMethodSignature());
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE,
CLASSMAP_LISTENER_INTERNALNAME, "putLineTouchPoint",
"(IILjava/lang/String;Ljava/lang/String;)V");
} else if (tpd instanceof JumpTouchPointDescriptor) {
mv.visitLdcInsn(((JumpTouchPointDescriptor) tpd)
.getCounterIdForTrue());
mv.visitLdcInsn(((JumpTouchPointDescriptor) tpd)
.getCounterIdForFalse());
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE,
CLASSMAP_LISTENER_INTERNALNAME, "putJumpTouchPoint",
"(III)V");
} else if (tpd instanceof SwitchTouchPointDescriptor) {
SwitchTouchPointDescriptor stpd = (SwitchTouchPointDescriptor) tpd;
final String enum_sign = ((SwitchTouchPointDescriptor) tpd)
.getEnumType();
if (enum_sign == null) {
mv.visitLdcInsn(Integer.MAX_VALUE);
} else {
mv.visitMethodInsn(Opcodes.INVOKESTATIC, enum_sign,
"values", "()[L" + enum_sign + ";");
mv.visitInsn(Opcodes.ARRAYLENGTH);
}
Collection<Integer> ci = stpd.getCountersForLabels();
mv.visitLdcInsn(ci.size());//Size of a new table
mv.visitIntInsn(Opcodes.NEWARRAY, Opcodes.T_INT);
int i = 0;
for (Integer counterId : ci) {
mv.visitInsn(Opcodes.DUP); //First for addition of items, second ad putSwitchTouchPoint parameter (or next loop iteration)
mv.visitLdcInsn(i);
mv.visitLdcInsn(counterId);
mv.visitInsn(Opcodes.IASTORE);
i++;
}
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE,
CLASSMAP_LISTENER_INTERNALNAME, "putSwitchTouchPoint",
"(II[I)V");
}
}
mv.visitInsn(Opcodes.POP);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(0, 0);//will be recalculated by writer
mv.visitEnd();
}
/**
* Generates code that is injected into static constructor of an instrumented class.
* <p/>
* It is good place to initiate static fields inserted into a class ({@link #generateCountersField(ClassVisitor)}),
* or execute other code that should be executed when the class it used for the first time. Registering the class in
* {@link TouchCollector} would be a bright idea.
* <p/>
* It is expected that all counter will be set to zero after that operation.
*
* @param mv - {@link MethodVisitor} that is listener of code-generation events
* @param className - internal name (asm) of class being instrumented
* @param counters_cnt - information about how many counters are expected to be used by instrumentation code.
* In most cases the method is responsible for allocating objects that will be used to store counters.
*/
protected abstract void generateCINITmethod(MethodVisitor mv,
String className, int counters_cnt);
public void generateCoberturaInitMethod(ClassVisitor cv, String className,
int countersCnt) {
MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC
| Opcodes.ACC_STATIC, COBERTURA_INIT_METHOD_NAME, "()V", null,
null);
mv.visitCode();
generateCINITmethod(mv, className, countersCnt);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(0, 0); //will be recalculated by writer
mv.visitEnd();
}
public void generateCallCoberturaInitMethod(MethodVisitor mv,
String className) {
mv.visitCode(); // Since we are using the ASM Check Adapter, we need to visit the code before visiting any instructions.
mv.visitMethodInsn(Opcodes.INVOKESTATIC, className,
COBERTURA_INIT_METHOD_NAME, "()V");
}
}