net.sourceforge.cobertura.instrument.SecondPassMethodInstrumenter.java Source code

Java tutorial

Introduction

Here is the source code for net.sourceforge.cobertura.instrument.SecondPassMethodInstrumenter.java

Source

/*
 * 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 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 implements Opcodes {
    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);
    }

}