/*
* Cobertura - http://cobertura.sourceforge.net/
*
* Copyright (C) 2005 Mark Doliner
* Copyright (C) 2006 Jiri Mares
* Copyright (C) 2010 Piotr Tabor
*
* 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;
import net.sourceforge.cobertura.util.RegexUtil;
import org.objectweb.asm.Label;
import static org.objectweb.asm.Opcodes.*;
/*
* TODO: If class is abstract then do not count the "public abstract class bleh" line as a SLOC.
*/
public class SecondPassMethodInstrumenter extends NewLocalVariableMethodAdapter {
private String TOUCH_COLLECTOR_CLASS = "net/sourceforge/cobertura/coveragedata/TouchCollector";
private int currentLine;
private int currentJump;
private boolean methodStarted;
private int myVariableIndex;
private Label startLabel;
private Label endLabel;
private JumpHolder lastJump;
private FirstPassMethodInstrumenter firstPass;
private static final int BOOLEAN_TRUE = ICONST_0;
private static final int BOOLEAN_FALSE = ICONST_1;
public SecondPassMethodInstrumenter(FirstPassMethodInstrumenter firstPass) {
super(firstPass.getWriterMethodVisitor(), firstPass.getMyAccess(), firstPass.getMyDescriptor(), 2);
this.firstPass = firstPass;
this.currentLine = 0;
}
public void visitJumpInsn(int opcode, Label label) {
//to touch the previous branch (when there is such)
touchBranchFalse();
// Ignore any jump instructions in the "class init" method.
// When initializing static variables, the JVM first checks
// that the variable is null before attempting to set it.
// This check contains an IFNONNULL jump instruction which
// would confuse people if it showed up in the reports.
if ((opcode != GOTO) && (opcode != JSR) && (currentLine != 0)
&& (!this.firstPass.getMyName().equals("<clinit>"))) {
lastJump = new JumpHolder(currentLine, currentJump++);
mv.visitIntInsn(SIPUSH, currentLine);
mv.visitVarInsn(ISTORE, myVariableIndex);
mv.visitIntInsn(SIPUSH, lastJump.getJumpNumber());
mv.visitVarInsn(ISTORE, myVariableIndex + 1);
}
super.visitJumpInsn(opcode, label);
}
public void visitLineNumber(int line, Label start) {
// Record initial information about this line of code
currentLine = line;
currentJump = 0;
instrumentOwnerClass();
// Mark the current line number as covered:
// classData.touch(line)
mv.visitIntInsn(SIPUSH, line);
mv.visitMethodInsn(INVOKESTATIC,
TOUCH_COLLECTOR_CLASS, "touch",
"(Ljava/lang/String;I)V");
super.visitLineNumber(line, start);
}
public void visitMethodInsn(int opcode, String owner, String name,
String desc) {
//to touch the previous branch (when there is such)
touchBranchFalse();
super.visitMethodInsn(opcode, owner, name, desc);
// If any of the ignore patterns match this line
// then remove it from our data
if (RegexUtil.matches(firstPass.getIgnoreRegexs(), owner)) {
firstPass.removeLine(currentLine);
}
}
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
//to touch the previous branch (when there is such)
touchBranchFalse();
super.visitFieldInsn(opcode, owner, name, desc);
}
public void visitIincInsn(int var, int increment) {
//to touch the previous branch (when there is such)
touchBranchFalse();
super.visitIincInsn(var, increment);
}
public void visitInsn(int opcode) {
//to touch the previous branch (when there is such)
touchBranchFalse();
super.visitInsn(opcode);
}
public void visitIntInsn(int opcode, int operand) {
//to touch the previous branch (when there is such)
touchBranchFalse();
super.visitIntInsn(opcode, operand);
}
public void visitLabel(Label label) {
//When this is the first method's label ... create the 2 new local variables (lineNumber and branchNumber)
if (methodStarted) {
methodStarted = false;
myVariableIndex = getFirstStackVariable();
mv.visitInsn(ICONST_0);
mv.visitVarInsn(ISTORE, myVariableIndex);
mv.visitIntInsn(SIPUSH, -1);
mv.visitVarInsn(ISTORE, myVariableIndex + 1);
startLabel = label;
}
//to have the last label for visitLocalVariable
endLabel = label;
super.visitLabel(label);
//instrument the branch coverage collection
if (firstPass.getJumpTargetLabels().keySet().contains(label)) { //this label is the true branch label
if (lastJump != null) { //this is also label after jump - we have to check the branch number whether this is the true or false branch
Label newLabelX = instrumentIsLastJump();
instrumentOwnerClass();
instrumentPutLineAndBranchNumbers();
mv.visitInsn(BOOLEAN_FALSE);
instrumentInvokeTouchJump();
Label newLabelY = new Label();
mv.visitJumpInsn(GOTO, newLabelY);
mv.visitLabel(newLabelX);
mv.visitVarInsn(ILOAD, myVariableIndex + 1);
mv.visitJumpInsn(IFLT, newLabelY);
instrumentOwnerClass();
instrumentPutLineAndBranchNumbers();
mv.visitInsn(BOOLEAN_TRUE);
instrumentInvokeTouchJump();
mv.visitLabel(newLabelY);
} else { //just hit te true branch
//just check whether the jump has been invoked or the label has been touched other way
mv.visitVarInsn(ILOAD, myVariableIndex + 1);
Label newLabelX = new Label();
mv.visitJumpInsn(IFLT, newLabelX);
instrumentJumpHit(true);
mv.visitLabel(newLabelX);
}
} else if (lastJump != null) { //this is "only" after jump label, hit the false branch only if the lastJump is same as stored stack lineNumber and jumpNumber
Label newLabelX = instrumentIsLastJump();
instrumentJumpHit(false);
mv.visitLabel(newLabelX);
}
lastJump = null;
SwitchHolder sh = (SwitchHolder) firstPass.getSwitchTargetLabels().get(label);
if (sh != null) {
instrumentSwitchHit(sh.getLineNumber(), sh.getSwitchNumber(), sh.getBranch());
}
//we have to manually invoke the visitLineNumber because of not correct MedthodNode's handling
Integer line = (Integer) firstPass.getLineLabels().get(label);
if (line != null) {
visitLineNumber(line.intValue(), label);
}
}
public void visitLdcInsn(Object cst) {
//to touch the previous branch (when there is such)
touchBranchFalse();
super.visitLdcInsn(cst);
}
public void visitMultiANewArrayInsn(String desc, int dims) {
//to touch the previous branch (when there is such)
touchBranchFalse();
super.visitMultiANewArrayInsn(desc, dims);
}
public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
//to touch the previous branch (when there is such)
touchBranchFalse();
super.visitLookupSwitchInsn(dflt, keys, labels);
}
public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
//to touch the previous branch (when there is such)
touchBranchFalse();
super.visitTableSwitchInsn(min, max, dflt, labels);
}
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
//to touch the previous branch (when there is such)
touchBranchFalse();
super.visitTryCatchBlock(start, end, handler, type);
}
public void visitTypeInsn(int opcode, String desc) {
//to touch the previous branch (when there is such)
touchBranchFalse();
super.visitTypeInsn(opcode, desc);
}
public void visitVarInsn(int opcode, int var) {
//to touch the previous branch (when there is such)
touchBranchFalse();
//this is to change the variable instructions to conform to 2 new variables
super.visitVarInsn(opcode, var);
}
public void visitCode() {
methodStarted = true;
super.visitCode();
}
private void touchBranchFalse() {
if (lastJump != null) {
lastJump = null;
instrumentJumpHit(false);
}
}
private void instrumentOwnerClass() {
// OwnerClass is the name of the class being instrumented
mv.visitLdcInsn(firstPass.getOwnerClass());
}
private void instrumentSwitchHit(int lineNumber, int switchNumber, int branch) {
instrumentOwnerClass();
//Invoke the touchSwitch(lineNumber, switchNumber, branch)
mv.visitIntInsn(SIPUSH, lineNumber);
mv.visitIntInsn(SIPUSH, switchNumber);
mv.visitIntInsn(SIPUSH, branch);
instrumentInvokeTouchSwitch();
}
private void instrumentJumpHit(boolean branch) {
instrumentOwnerClass();
//Invoke the touchJump(lineNumber, branchNumber, branch)
instrumentPutLineAndBranchNumbers();
mv.visitInsn(branch ? BOOLEAN_TRUE : BOOLEAN_FALSE);
instrumentInvokeTouchJump();
}
private void instrumentInvokeTouchJump() {
mv.visitMethodInsn(INVOKESTATIC, TOUCH_COLLECTOR_CLASS, "touchJump", "(Ljava/lang/String;IIZ)V");
mv.visitIntInsn(SIPUSH, -1); //is important to reset current branch, because we have to know that the branch info on stack has already been used and can't be used
mv.visitVarInsn(ISTORE, myVariableIndex + 1);
}
private void instrumentInvokeTouchSwitch() {
mv.visitMethodInsn(INVOKESTATIC, TOUCH_COLLECTOR_CLASS, "touchSwitch", "(Ljava/lang/String;III)V");
}
private void instrumentPutLineAndBranchNumbers() {
mv.visitVarInsn(ILOAD, myVariableIndex);
mv.visitVarInsn(ILOAD, myVariableIndex + 1);
}
private Label instrumentIsLastJump() {
mv.visitVarInsn(ILOAD, myVariableIndex);
mv.visitIntInsn(SIPUSH, lastJump.getLineNumber());
Label newLabelX = new Label();
mv.visitJumpInsn(IF_ICMPNE, newLabelX);
mv.visitVarInsn(ILOAD, myVariableIndex + 1);
mv.visitIntInsn(SIPUSH, lastJump.getJumpNumber());
mv.visitJumpInsn(IF_ICMPNE, newLabelX);
return newLabelX;
}
public void visitMaxs(int maxStack, int maxLocals) {
mv.visitLocalVariable("__cobertura__line__number__", "I", null, startLabel, endLabel, myVariableIndex);
mv.visitLocalVariable("__cobertura__branch__number__", "I", null, startLabel, endLabel, myVariableIndex + 1);
super.visitMaxs(maxStack, maxLocals);
}
}