/*
* Cobertura - http://cobertura.sourceforge.net/
*
* Copyright (C) 2005 Mark Doliner
* Copyright (C) 2006 Jiri Mares
* Copyright (C) 2008 Scott Frederick
* Copyright (C) 2010 Tad Smith
*
* 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 java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import net.sourceforge.cobertura.coveragedata.ClassData;
import net.sourceforge.cobertura.util.RegexUtil;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import static org.objectweb.asm.Opcodes.*;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.MethodNode;
public class FirstPassMethodInstrumenter extends MethodVisitor {
private final String ownerClass;
private final String ownerSuperClass;
private String myName;
private String myDescriptor;
private int myAccess;
private Collection ignoreRegexs;
private Collection ignoreBranchesRegexs;
private Collection ignoreMethodAnnotations;
private boolean ignoreTrivial = false;
private boolean ignored = false;
private boolean mightBeTrivial = false;
private boolean isGetter = false;
private boolean isSetter = false;
private boolean isInit = false;
private ClassData classData;
private int currentLine;
private int currentJump;
private int currentSwitch;
private Map jumpTargetLabels;
private Map switchTargetLabels;
private Map lineLabels;
private MethodVisitor writerMethodVisitor;
private MethodNode methodNode;
public FirstPassMethodInstrumenter(ClassData classData, final MethodVisitor mv,
final String owner, final String superOwner, final int access, final String name, final String desc,
final String signature, final String[] exceptions, final Collection ignoreRegexs,
final Collection ignoreBranchesRegexs, final Collection ignoreMethodAnnotations,
final boolean ignoreTrivial) {
super(ASM4, new MethodNode(access, name, desc, signature, exceptions));
this.writerMethodVisitor = mv;
this.ownerClass = owner;
this.ownerSuperClass = superOwner;
this.methodNode = (MethodNode) this.mv;
this.classData = classData;
this.myAccess = access;
this.myName = name;
this.myDescriptor = desc;
this.ignoreRegexs = ignoreRegexs;
this.ignoreBranchesRegexs = ignoreBranchesRegexs;
this.ignoreMethodAnnotations = ignoreMethodAnnotations;
this.ignoreTrivial = ignoreTrivial;
this.jumpTargetLabels = new HashMap();
this.switchTargetLabels = new HashMap();
this.lineLabels = new HashMap();
this.currentLine = 0;
if (ignoreTrivial) {
checkForTrivialSignature();
}
}
private void checkForTrivialSignature() {
Type[] args = Type.getArgumentTypes(myDescriptor);
Type ret = Type.getReturnType(myDescriptor);
if (myName.equals("<init>")) {
isInit = true;
mightBeTrivial = true;
return;
}
// a "setter" method must:
// - have a name starting with "set"
// - take one arguments
// - return nothing (void)
if (myName.startsWith("set") && args.length == 1 && ret.equals(Type.VOID_TYPE)) {
isSetter = true;
mightBeTrivial = true;
return;
}
// a "getter" method must:
// - have a name starting with "get", "is", or "has"
// - take no arguments
// - return a value (non-void)
if ((myName.startsWith("get") || myName.startsWith("is") || myName.startsWith("has")) &&
args.length == 0 && !ret.equals(Type.VOID_TYPE)) {
isGetter = true;
mightBeTrivial = true;
return;
}
}
public void visitEnd() {
super.visitEnd();
// if we get to the end and nothing has ruled out this method being trivial,
// then it must be trivial, so we'll ignore it, if configured to do so
if (ignoreTrivial && mightBeTrivial) {
ignored = true;
}
if (ignored) {
Iterator iter = lineLabels.values().iterator();
while (iter.hasNext()) {
classData.removeLine(((Integer) iter.next()).intValue());
}
lineLabels.clear();
}
methodNode.accept(lineLabels.isEmpty() ? writerMethodVisitor : new SecondPassMethodInstrumenter(this)); //when there is no line number info -> no instrumentation
}
public void visitJumpInsn(int opcode, Label label) {
// 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.myName.equals("<clinit>"))) {
classData.addLineJump(currentLine, currentJump);
jumpTargetLabels.put(label, new JumpHolder(currentLine, currentJump++));
}
markNonTrivial();
super.visitJumpInsn(opcode, label);
}
public void visitLineNumber(int line, Label start) {
// Record initial information about this line of code
currentLine = line;
classData.addLine(currentLine, myName, myDescriptor);
currentJump = 0;
currentSwitch = 0;
lineLabels.put(start, new Integer(line));
//removed because the MethodNode doesn't reproduce visitLineNumber where they are but at the end of the file :-((
//therefore we don't need them
//We can directly instrument the visit line number here, but it is better to leave all instrumentation in the second pass
//therefore we just collects what label is the line ...
//super.visitLineNumber(line, start);
}
public void visitFieldInsn(int opcode, String string, String string1, String string2) {
super.visitFieldInsn(opcode, string, string1, string2);
if (!ignored && mightBeTrivial) {
// trivial opcodes for accessing class fields are:
// - GETFIELD or PUTFIELD
if ((isGetter && opcode != GETFIELD) ||
(isSetter && opcode != PUTFIELD) ||
(isInit && opcode != PUTFIELD)) {
markNonTrivial();
}
}
}
public void visitVarInsn(int opcode, int i1) {
super.visitVarInsn(opcode, i1);
if (!ignored && mightBeTrivial) {
if (
opcode == ILOAD ||
opcode == LLOAD ||
opcode == FLOAD ||
opcode == DLOAD ||
opcode == ALOAD
) {
// trivial opcodes for accessing local variables.
} else {
markNonTrivial();
}
}
}
public void visitMethodInsn(int opcode, String owner, String name,
String desc) {
super.visitMethodInsn(opcode, owner, name, desc);
// If any of the ignore patterns match this line
// then remove it from our data
if (RegexUtil.matches(ignoreRegexs, owner)) {
classData.removeLine(currentLine);
}
if (!ignored && mightBeTrivial) {
if (isInit) {
// trivial initializers can invoke parent initializers,
// but cannot invoke any other methods
if (opcode == INVOKESPECIAL && name.equals("<init>") && owner.equals(ownerSuperClass)) {
// trivial call to super constructor
} else {
markNonTrivial();
}
} else {
markNonTrivial();
}
}
}
public void visitTypeInsn(int i, String string) {
super.visitTypeInsn(i, string);
markNonTrivial();
}
public void visitIntInsn(int i, int i1) {
super.visitIntInsn(i, i1);
markNonTrivial();
}
public void visitLdcInsn(Object object) {
super.visitLdcInsn(object);
markNonTrivial();
}
public void visitIincInsn(int i, int i1) {
super.visitIincInsn(i, i1);
markNonTrivial();
}
public void visitMultiANewArrayInsn(String string, int i) {
super.visitMultiANewArrayInsn(string, i);
markNonTrivial();
}
public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
super.visitLookupSwitchInsn(dflt, keys, labels);
if (currentLine != 0) {
switchTargetLabels.put(dflt, new SwitchHolder(currentLine, currentSwitch, -1));
for (int i = labels.length - 1; i >= 0; i--)
switchTargetLabels.put(labels[i], new SwitchHolder(currentLine, currentSwitch, i));
classData.addLineSwitch(currentLine, currentSwitch++, keys);
}
markNonTrivial();
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
// We need to convert desc to a fully-qualified classname. Example:
// java.lang.Override --> passed in as this: "Ljava/lang/Override;"
if (desc.charAt(0) == 'L' && desc.charAt(desc.length() - 1) == ';') {
desc = desc.substring(1, desc.length() - 1).replace('/', '.');
}
// Check to see if this annotation is one of the ones that we use to
// trigger us to ignore this method
if (ignoreMethodAnnotations.contains(desc)) {
ignored = true;
}
return super.visitAnnotation(desc, visible);
}
public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
super.visitTableSwitchInsn(min, max, dflt, labels);
if (currentLine != 0) {
switchTargetLabels.put(dflt, new SwitchHolder(currentLine, currentSwitch, -1));
for (int i = labels.length - 1; i >= 0; i--)
switchTargetLabels.put(labels[i], new SwitchHolder(currentLine, currentSwitch, i));
classData.addLineSwitch(currentLine, currentSwitch++, min, max);
}
markNonTrivial();
}
protected void removeLine(int lineNumber) {
classData.removeLine(lineNumber);
}
protected MethodVisitor getWriterMethodVisitor() {
return writerMethodVisitor;
}
protected Collection getIgnoreRegexs() {
return ignoreRegexs;
}
protected Map getJumpTargetLabels() {
return jumpTargetLabels;
}
protected Map getSwitchTargetLabels() {
return switchTargetLabels;
}
protected int getMyAccess() {
return myAccess;
}
protected String getMyDescriptor() {
return myDescriptor;
}
protected String getMyName() {
return myName;
}
protected String getOwnerClass() {
return ownerClass;
}
protected Map getLineLabels() {
return lineLabels;
}
private void markNonTrivial() {
mightBeTrivial = false;
}
}