/*
* 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.
This especially occurs in case of 'finally' blocks. For example:
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}
effects in generation such a JVM code:
// 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.(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.(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.(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.(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
Note that 'LINENUMBER 186' instruction occurs many times and code after that instruction is nearly identical (see CodeFootstamp
criteria of 'identity').
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.
The goal of this class is to provide duplicatedLinesCollector
that is map of: (line number -> (duplicated lineId -> origin lineId)).
/**
* 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))
/**
* 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
/**
* Name (internal asm) of currently processed class
*/
private String className;
Every LINENUMBER instruction will have generated it's lineId.
The generated ids must be the same as those generated by ( AbstractFindTouchPointsClassInstrumenter.lineIdGenerator
) /**
* 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))
/**
* Returns map of (line number -> (duplicated lineId -> origin lineId))
*/
public Map<Integer, Map<Integer, Integer>> getDuplicatesLinesCollector() {
return duplicatedLinesCollector;
}
}