/*
* 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.pass1;
import net.sourceforge.cobertura.instrument.AbstractFindTouchPointsClassInstrumenter;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.util.CheckClassAdapter;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
/**
* The same line can cause generation of many byte-code blocks connected to the same line.
* <p/>
* This especially occurs in case of 'finally' blocks. For example:
* <p/>
* <pre>
* 173: public void methodWithFinishBlock(FinishReturnTypeEnum f){
* 174: try {
* 175: switch (f) {
* 176: case BY_RETURN:
* 177: System.out.println("will return");
* 178: return;
* 179: case BY_THROW:
* 180: System.out.println("will throw");
* 181: throw new IllegalStateException("Expected exception");
* 182: default:
* 183: System.out.println("default");
* 184: }
* 185: } finally {
* 186: if (f != null) { //This piece of code is generated in ASM 3 times. We should merge it into one block
* 187: System.out.println("Finish with: f="+f);
* 188: }
* 189: }
* 190}
* </pre>
* <p/>
* effects in generation such a JVM code:
* <p/>
* <pre>
* // access flags 1
* public methodWithFinishBlock(Ltest/performance/Test1$FinishReturnTypeEnum;)V
* TRYCATCHBLOCK L0 L1 L2
* TRYCATCHBLOCK L3 L2 L2
* L0
* LINENUMBER 175 L0
* INVOKESTATIC test/performance/Test1.$SWITCH_TABLE$test$performance$Test1$FinishReturnTypeEnum()[I
* ALOAD 1
* INVOKEVIRTUAL test/performance/Test1$FinishReturnTypeEnum.ordinal()I
* IALOAD
* TABLESWITCH
* 1: L4
* 2: L3
* default: L5
* L4
* LINENUMBER 177 L4
* GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
* LDC "will return"
* INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V
* L1
* LINENUMBER 186 L1
* ALOAD 1
* IFNULL L6
* L7
* LINENUMBER 187 L7
* GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
* NEW java/lang/StringBuilder
* DUP
* LDC "Finish with: f="
* INVOKESPECIAL java/lang/StringBuilder.<init>(Ljava/lang/String;)V
* ALOAD 1
* INVOKEVIRTUAL java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lang/StringBuilder;
* INVOKEVIRTUAL java/lang/StringBuilder.toString()Ljava/lang/String;
* INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V
* L6
* LINENUMBER 178 L6
* RETURN
* L3
* LINENUMBER 180 L3
* GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
* LDC "will throw"
* INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V
* L8
* LINENUMBER 181 L8
* NEW java/lang/IllegalStateException
* DUP
* LDC "Expected exception"
* INVOKESPECIAL java/lang/IllegalStateException.<init>(Ljava/lang/String;)V
* ATHROW
* L5
* LINENUMBER 183 L5
* GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
* LDC "default"
* INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V
* GOTO L9
* L2
* LINENUMBER 185 L2
* ASTORE 2
* L10
* LINENUMBER 186 L10
* ALOAD 1
* IFNULL L11
* L12
* LINENUMBER 187 L12
* GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
* NEW java/lang/StringBuilder
* DUP
* LDC "Finish with: f="
* INVOKESPECIAL java/lang/StringBuilder.<init>(Ljava/lang/String;)V
* ALOAD 1
* INVOKEVIRTUAL java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lang/StringBuilder;
* INVOKEVIRTUAL java/lang/StringBuilder.toString()Ljava/lang/String;
* INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V
* L11
* LINENUMBER 189 L11
* ALOAD 2
* ATHROW
* L9
* LINENUMBER 186 L9
* ALOAD 1
* IFNULL L13
* L14
* LINENUMBER 187 L14
* GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
* NEW java/lang/StringBuilder
* DUP
* LDC "Finish with: f="
* INVOKESPECIAL java/lang/StringBuilder.<init>(Ljava/lang/String;)V
* ALOAD 1
* INVOKEVIRTUAL java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lang/StringBuilder;
* INVOKEVIRTUAL java/lang/StringBuilder.toString()Ljava/lang/String;
* INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V
* L13
* LINENUMBER 190 L13
* RETURN
* L15
* LOCALVARIABLE this Ltest/performance/Test1; L0 L15 0
* LOCALVARIABLE f Ltest/performance/Test1$FinishReturnTypeEnum; L0 L15 1
* MAXSTACK = 4
* MAXLOCALS = 3
* </pre>
* <p/>
* <p>Note that 'LINENUMBER 186' instruction occurs many times and code after that instruction is nearly identical
* (see {@link CodeFootstamp} criteria of 'identity').</p>
* <p/>
* <p>On the other hand duplicated 'LINENUMBER 186' instruction could happened for for example for 'for' loop. In this
* case the code after this instruction is different.</p>
* <p/>
* <p>The goal of this class is to provide {@link #duplicatedLinesCollector} that is map of:
* (line number -> (duplicated lineId -> origin lineId)).</p>
*/
public class DetectDuplicatedCodeClassVisitor extends ClassVisitor {
/**
* map of (line number -> (duplicated lineId -> origin lineId))
*/
private Map<Integer, Map<Integer, Integer>> duplicatedLinesCollector = new HashMap<Integer, Map<Integer, Integer>>();
/**
* Name (internal asm) of currently processed class
*/
private String className;
/**
* Every LINENUMBER instruction will have generated it's lineId.
* <p/>
* The generated ids must be the same as those generated by
* ( {@link AbstractFindTouchPointsClassInstrumenter#lineIdGenerator} )
*/
private final AtomicInteger lineIdGenerator = new AtomicInteger(0);
public DetectDuplicatedCodeClassVisitor(ClassVisitor cv) {
super(Opcodes.ASM4, new CheckClassAdapter(cv, false));
}
@Override
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
this.className = name;
}
@Override
public MethodVisitor visitMethod(int access, String methodName,
String description, String signature, String[] exceptions) {
MethodVisitor nestedVisitor = super.visitMethod(access, methodName,
description, signature, exceptions);
return new DetectDuplicatedCodeMethodVisitor(nestedVisitor,
duplicatedLinesCollector, className, methodName, description,
lineIdGenerator);
}
/**
* Returns map of (line number -> (duplicated lineId -> origin lineId))
*/
public Map<Integer, Map<Integer, Integer>> getDuplicatesLinesCollector() {
return duplicatedLinesCollector;
}
}