001/*
002 * Cobertura - http://cobertura.sourceforge.net/
003 *
004 * Copyright (C) 2011 Piotr Tabor
005 *
006 * Note: This file is dual licensed under the GPL and the Apache
007 * Source License (so that it can be used from both the main
008 * Cobertura classes and the ant tasks).
009 *
010 * Cobertura is free software; you can redistribute it and/or modify
011 * it under the terms of the GNU General Public License as published
012 * by the Free Software Foundation; either version 2 of the License,
013 * or (at your option) any later version.
014 *
015 * Cobertura is distributed in the hope that it will be useful, but
016 * WITHOUT ANY WARRANTY; without even the implied warranty of
017 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
018 * General Public License for more details.
019 *
020 * You should have received a copy of the GNU General Public License
021 * along with Cobertura; if not, write to the Free Software
022 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
023 * USA
024 */
025
026package net.sourceforge.cobertura.instrument.pass1;
027
028import java.util.HashMap;
029import java.util.Map;
030import java.util.concurrent.atomic.AtomicInteger;
031
032import net.sourceforge.cobertura.instrument.AbstractFindTouchPointsClassInstrumenter;
033
034import org.objectweb.asm.ClassVisitor;
035import org.objectweb.asm.MethodVisitor;
036import org.objectweb.asm.Opcodes;
037
038/**
039 * The same line can cause generation of many byte-code blocks connected to the same line.
040 * 
041 * This especially occurs in case of 'finally' blocks. For example:
042 * 
043 * <pre>
044 * 173: public void methodWithFinishBlock(FinishReturnTypeEnum f){
045 * 174:   try {
046 * 175:     switch (f) {
047 * 176:       case BY_RETURN:
048 * 177:         System.out.println("will return");
049 * 178:         return;
050 * 179:       case BY_THROW:
051 * 180:         System.out.println("will throw");
052 * 181:         throw new IllegalStateException("Expected exception");
053 * 182:       default:
054 * 183:         System.out.println("default");
055 * 184:     }
056 * 185:   } finally {
057 * 186:     if (f != null) { //This piece of code is generated in ASM 3 times. We should merge it into one block
058 * 187:       System.out.println("Finish with: f="+f);
059 * 188:   }
060 * 189: }
061 * 190}
062 * </pre>
063 * 
064 * effects in generation such a JVM code:
065 * 
066 * <pre>
067 * // access flags 1
068 *   public methodWithFinishBlock(Ltest/performance/Test1$FinishReturnTypeEnum;)V
069 *     TRYCATCHBLOCK L0 L1 L2 
070 *     TRYCATCHBLOCK L3 L2 L2 
071 *    L0
072 *     LINENUMBER 175 L0
073 *     INVOKESTATIC test/performance/Test1.$SWITCH_TABLE$test$performance$Test1$FinishReturnTypeEnum()[I
074 *     ALOAD 1
075 *     INVOKEVIRTUAL test/performance/Test1$FinishReturnTypeEnum.ordinal()I
076 *     IALOAD
077 *     TABLESWITCH
078 *       1: L4
079 *       2: L3
080 *       default: L5
081 *    L4
082 *     LINENUMBER 177 L4
083 *     GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
084 *     LDC "will return"
085 *     INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V
086 *    L1
087 *     LINENUMBER 186 L1
088 *     ALOAD 1
089 *     IFNULL L6
090 *    L7
091 *     LINENUMBER 187 L7
092 *     GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
093 *     NEW java/lang/StringBuilder
094 *     DUP
095 *     LDC "Finish with: f="
096 *     INVOKESPECIAL java/lang/StringBuilder.<init>(Ljava/lang/String;)V
097 *     ALOAD 1
098 *     INVOKEVIRTUAL java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lang/StringBuilder;
099 *     INVOKEVIRTUAL java/lang/StringBuilder.toString()Ljava/lang/String;
100 *     INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V
101 *    L6
102 *     LINENUMBER 178 L6
103 *     RETURN
104 *    L3
105 *     LINENUMBER 180 L3
106 *     GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
107 *     LDC "will throw"
108 *     INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V
109 *    L8
110 *     LINENUMBER 181 L8
111 *     NEW java/lang/IllegalStateException
112 *     DUP
113 *     LDC "Expected exception"
114 *     INVOKESPECIAL java/lang/IllegalStateException.<init>(Ljava/lang/String;)V
115 *     ATHROW
116 *    L5
117 *     LINENUMBER 183 L5
118 *     GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
119 *     LDC "default"
120 *     INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V
121 *     GOTO L9
122 *    L2
123 *     LINENUMBER 185 L2
124 *     ASTORE 2
125 *    L10
126 *     LINENUMBER 186 L10
127 *     ALOAD 1
128 *     IFNULL L11
129 *    L12
130 *     LINENUMBER 187 L12
131 *     GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
132 *     NEW java/lang/StringBuilder
133 *     DUP
134 *     LDC "Finish with: f="
135 *     INVOKESPECIAL java/lang/StringBuilder.<init>(Ljava/lang/String;)V
136 *     ALOAD 1
137 *     INVOKEVIRTUAL java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lang/StringBuilder;
138 *     INVOKEVIRTUAL java/lang/StringBuilder.toString()Ljava/lang/String;
139 *     INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V
140 *    L11
141 *     LINENUMBER 189 L11
142 *     ALOAD 2
143 *     ATHROW
144 *    L9
145 *     LINENUMBER 186 L9
146 *     ALOAD 1
147 *     IFNULL L13
148 *    L14
149 *     LINENUMBER 187 L14
150 *     GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
151 *     NEW java/lang/StringBuilder
152 *     DUP
153 *     LDC "Finish with: f="
154 *     INVOKESPECIAL java/lang/StringBuilder.<init>(Ljava/lang/String;)V
155 *     ALOAD 1
156 *     INVOKEVIRTUAL java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lang/StringBuilder;
157 *     INVOKEVIRTUAL java/lang/StringBuilder.toString()Ljava/lang/String;
158 *     INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V
159 *    L13
160 *     LINENUMBER 190 L13
161 *     RETURN
162 *    L15
163 *     LOCALVARIABLE this Ltest/performance/Test1; L0 L15 0
164 *     LOCALVARIABLE f Ltest/performance/Test1$FinishReturnTypeEnum; L0 L15 1
165 *     MAXSTACK = 4
166 *     MAXLOCALS = 3
167 * </pre>
168 * 
169 * <p>Note that 'LINENUMBER 186' instruction occurs many times and code after that instruction is nearly identical 
170 * (see {@link CodeFootstamp} criteria of 'identity').</p>  
171 * 
172 * <p>On the other hand duplicated 'LINENUMBER 186' instruction could happened for for example for 'for' loop. In this      
173 * case the code after this instruction is different.</p>
174 * 
175 * <p>The goal of this class is to provide {@link #duplicatedLinesCollector} that is map of:
176 * (line number -> (duplicated lineId -> origin lineId)).</p>    
177 */
178
179public class DetectDuplicatedCodeClassVisitor extends ClassVisitor{
180        /**
181         *  map of (line number -> (duplicated lineId -> origin lineId))
182         */ 
183        private Map<Integer,Map<Integer,Integer>> duplicatedLinesCollector=new HashMap<Integer, Map<Integer,Integer>>();
184        
185        /**
186         * Name (internal asm) of currently processed class  
187         **/
188        private String className;
189        
190        /**
191         * Every LINENUMBER instruction will have generated it's lineId. 
192         * 
193         * The generated ids must be the same as those generated by ( {@link AbstractFindTouchPointsClassInstrumenter#lineIdGenerator} ) 
194         */
195        private final AtomicInteger lineIdGenerator = new AtomicInteger(0);
196                
197        public  DetectDuplicatedCodeClassVisitor(ClassVisitor cv) {
198                super(Opcodes.ASM4, cv);
199        }
200        
201        @Override
202        public void visit(int version, int access,
203                        String name, String signature, String superName, String[] interfaces)  {        
204                super.visit(version, access, name, signature, superName, interfaces);
205                this.className = name;
206        }
207        
208        @Override
209        public MethodVisitor visitMethod(int access, String methodName, String description, 
210                    String signature, String[] exceptions) {
211                MethodVisitor nestedVisitor=super.visitMethod(access, methodName, description, 
212                    signature, exceptions);
213                return new DetectDuplicatedCodeMethodVisitor(nestedVisitor, 
214                    duplicatedLinesCollector, className, methodName, description, lineIdGenerator);
215        }
216        
217        /**
218         * Returns map of (line number -> (duplicated lineId -> origin lineId))
219         */
220        public Map<Integer, Map<Integer, Integer>> getDuplicatesLinesCollector() {
221                return duplicatedLinesCollector;
222        }
223}