/*
 * 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; } }