001    /*
002     * Cobertura - http://cobertura.sourceforge.net/
003     *
004     * Copyright (C) 2005 Mark Doliner
005     * Copyright (C) 2006 Jiri Mares
006     * Copyright (C) 2010 Piotr Tabor
007     *
008     * Cobertura is free software; you can redistribute it and/or modify
009     * it under the terms of the GNU General Public License as published
010     * by the Free Software Foundation; either version 2 of the License,
011     * or (at your option) any later version.
012     *
013     * Cobertura is distributed in the hope that it will be useful, but
014     * WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
016     * General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with Cobertura; if not, write to the Free Software
020     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
021     * USA
022     */
023    
024    package net.sourceforge.cobertura.instrument;
025    
026    import net.sourceforge.cobertura.util.RegexUtil;
027    
028    import org.objectweb.asm.Label;
029    import org.objectweb.asm.Opcodes;
030    
031    /*
032     * TODO: If class is abstract then do not count the "public abstract class bleh" line as a SLOC.
033     */
034    public class SecondPassMethodInstrumenter extends NewLocalVariableMethodAdapter implements Opcodes
035    {
036            private String TOUCH_COLLECTOR_CLASS="net/sourceforge/cobertura/coveragedata/TouchCollector";
037            
038            private int currentLine;
039       
040            private int currentJump;
041            
042            private boolean methodStarted;
043            
044            private int myVariableIndex;
045    
046            private Label startLabel;
047            
048            private Label endLabel;
049            
050            private JumpHolder lastJump;
051       
052            private FirstPassMethodInstrumenter firstPass;
053            
054            private static final int BOOLEAN_TRUE = ICONST_0;
055            private static final int BOOLEAN_FALSE = ICONST_1;
056    
057            public SecondPassMethodInstrumenter(FirstPassMethodInstrumenter firstPass)
058            {
059                    super(firstPass.getWriterMethodVisitor(), firstPass.getMyAccess(), firstPass.getMyDescriptor(), 2);
060                    this.firstPass = firstPass;
061                    this.currentLine = 0;
062            }
063    
064            public void visitJumpInsn(int opcode, Label label)
065            {
066                    //to touch the previous branch (when there is such)
067                    touchBranchFalse();
068                    
069                    // Ignore any jump instructions in the "class init" method.
070                    // When initializing static variables, the JVM first checks
071                    // that the variable is null before attempting to set it.
072                    // This check contains an IFNONNULL jump instruction which
073                    // would confuse people if it showed up in the reports.
074                    if ((opcode != GOTO) && (opcode != JSR) && (currentLine != 0)
075                                    && (!this.firstPass.getMyName().equals("<clinit>")))
076                    {
077                            lastJump = new JumpHolder(currentLine, currentJump++); 
078                            mv.visitIntInsn(SIPUSH, currentLine);
079                            mv.visitVarInsn(ISTORE, myVariableIndex);
080                            mv.visitIntInsn(SIPUSH, lastJump.getJumpNumber());
081                            mv.visitVarInsn(ISTORE, myVariableIndex + 1);
082                    }
083                    
084                    super.visitJumpInsn(opcode, label);
085            }
086    
087            public void visitLineNumber(int line, Label start)
088            {
089                    // Record initial information about this line of code
090                    currentLine = line;
091                    currentJump = 0;
092    
093                    instrumentOwnerClass();
094    
095                    // Mark the current line number as covered:
096                    // classData.touch(line)
097                    mv.visitIntInsn(SIPUSH, line);
098                    mv.visitMethodInsn(INVOKESTATIC,
099                                    TOUCH_COLLECTOR_CLASS, "touch",
100                                    "(Ljava/lang/String;I)V");
101    
102                    super.visitLineNumber(line, start);
103            }
104    
105            public void visitMethodInsn(int opcode, String owner, String name,
106                            String desc)
107            {
108                    //to touch the previous branch (when there is such)
109                    touchBranchFalse();
110                    
111                    super.visitMethodInsn(opcode, owner, name, desc);
112    
113                    // If any of the ignore patterns match this line
114                    // then remove it from our data
115                    if (RegexUtil.matches(firstPass.getIgnoreRegexs(), owner)) 
116                    {
117                            firstPass.removeLine(currentLine);
118                    }
119            }
120    
121            public void visitFieldInsn(int opcode, String owner, String name, String desc)
122            {
123                    //to touch the previous branch (when there is such)
124                    touchBranchFalse();
125                    
126                    super.visitFieldInsn(opcode, owner, name, desc);
127            }
128    
129            public void visitIincInsn(int var, int increment)
130            {
131                    //to touch the previous branch (when there is such)
132                    touchBranchFalse();
133                    
134                    super.visitIincInsn(var, increment);
135            }
136    
137            public void visitInsn(int opcode)
138            {
139                    //to touch the previous branch (when there is such)
140                    touchBranchFalse();
141                    
142                    super.visitInsn(opcode);
143            }
144    
145            public void visitIntInsn(int opcode, int operand)
146            {
147                    //to touch the previous branch (when there is such)
148                    touchBranchFalse();
149                    
150                    super.visitIntInsn(opcode, operand);
151            }
152    
153            public void visitLabel(Label label)
154            {
155                    //When this is the first method's label ... create the 2 new local variables (lineNumber and branchNumber)
156                    if (methodStarted) 
157                    {
158                            methodStarted = false;
159                            myVariableIndex = getFirstStackVariable();
160                            mv.visitInsn(ICONST_0);
161                            mv.visitVarInsn(ISTORE, myVariableIndex);
162                            mv.visitIntInsn(SIPUSH, -1); 
163                            mv.visitVarInsn(ISTORE, myVariableIndex + 1);
164                            startLabel = label;
165                    }
166                    //to have the last label for visitLocalVariable
167                    endLabel = label;
168                    
169                    super.visitLabel(label);
170                    
171                    //instrument the branch coverage collection
172                    if (firstPass.getJumpTargetLabels().keySet().contains(label)) 
173                    { //this label is the true branch label
174                            if (lastJump != null) 
175                            { //this is also label after jump - we have to check the branch number whether this is the true or false branch
176                                    Label newLabelX = instrumentIsLastJump();
177                                    instrumentOwnerClass();
178                                    instrumentPutLineAndBranchNumbers();
179                                    mv.visitInsn(BOOLEAN_FALSE);
180                                    instrumentInvokeTouchJump();
181                                    Label newLabelY = new Label();
182                                    mv.visitJumpInsn(GOTO, newLabelY);
183                                    mv.visitLabel(newLabelX);
184                                    mv.visitVarInsn(ILOAD, myVariableIndex + 1);
185                                    mv.visitJumpInsn(IFLT, newLabelY);
186                                    instrumentOwnerClass();
187                                    instrumentPutLineAndBranchNumbers();
188                                    mv.visitInsn(BOOLEAN_TRUE);
189                                    instrumentInvokeTouchJump();
190                                    mv.visitLabel(newLabelY);
191                            }
192                            else
193                            { //just hit te true branch
194                                    //just check whether the jump has been invoked or the label has been touched other way 
195                                    mv.visitVarInsn(ILOAD, myVariableIndex + 1);
196                                    Label newLabelX = new Label();
197                                    mv.visitJumpInsn(IFLT, newLabelX);
198                                    instrumentJumpHit(true);
199                                    mv.visitLabel(newLabelX);
200                            }
201                    } 
202                    else if (lastJump != null) 
203                    { //this is "only" after jump label, hit the false branch only if the lastJump is same as stored stack lineNumber and jumpNumber
204                            Label newLabelX = instrumentIsLastJump();
205                            instrumentJumpHit(false); 
206                            mv.visitLabel(newLabelX);
207                    }
208                    lastJump = null;
209                    
210                    SwitchHolder sh = (SwitchHolder) firstPass.getSwitchTargetLabels().get(label);
211                    if (sh != null)
212                    {
213                            instrumentSwitchHit(sh.getLineNumber(), sh.getSwitchNumber(), sh.getBranch());
214                    }
215                    
216                    //we have to manually invoke the visitLineNumber because of not correct MedthodNode's handling
217                    Integer line = (Integer) firstPass.getLineLabels().get(label);
218                    if (line != null) {
219                            visitLineNumber(line.intValue(), label);
220                    }
221            }
222    
223            public void visitLdcInsn(Object cst)
224            {
225                    //to touch the previous branch (when there is such)
226                    touchBranchFalse();
227                    
228                    super.visitLdcInsn(cst);
229            }
230    
231            public void visitMultiANewArrayInsn(String desc, int dims)
232            {
233                    //to touch the previous branch (when there is such)
234                    touchBranchFalse();
235                    
236                    super.visitMultiANewArrayInsn(desc, dims);
237            }
238    
239            public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels)
240            {
241                    //to touch the previous branch (when there is such)
242                    touchBranchFalse();
243                    
244                    super.visitLookupSwitchInsn(dflt, keys, labels);
245            }
246    
247            public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels)
248            {
249                    //to touch the previous branch (when there is such)
250                    touchBranchFalse();
251                    
252                    super.visitTableSwitchInsn(min, max, dflt, labels);
253            }
254    
255            public void visitTryCatchBlock(Label start, Label end, Label handler, String type)
256            {
257                    //to touch the previous branch (when there is such)
258                    touchBranchFalse();
259                    
260                    super.visitTryCatchBlock(start, end, handler, type);
261            }
262    
263            public void visitTypeInsn(int opcode, String desc)
264            {
265                    //to touch the previous branch (when there is such)
266                    touchBranchFalse();
267                    
268                    super.visitTypeInsn(opcode, desc);
269            }
270    
271            public void visitVarInsn(int opcode, int var)
272            {
273                    //to touch the previous branch (when there is such)
274                    touchBranchFalse();
275                    
276                    //this is to change the variable instructions to conform to 2 new variables
277                    super.visitVarInsn(opcode, var);
278            }
279    
280            public void visitCode()
281            {
282                    methodStarted = true;
283                    super.visitCode();
284            }
285            
286            private void touchBranchFalse() {
287                    if (lastJump != null) {
288                            lastJump = null;
289                            instrumentJumpHit(false);
290                    }
291            }
292    
293            private void instrumentOwnerClass()
294            {
295                    // OwnerClass is the name of the class being instrumented
296                    mv.visitLdcInsn(firstPass.getOwnerClass());
297            }
298            
299            private void instrumentSwitchHit(int lineNumber, int switchNumber, int branch)
300            {
301                    instrumentOwnerClass();
302                    
303                    //Invoke the touchSwitch(lineNumber, switchNumber, branch)
304                    mv.visitIntInsn(SIPUSH, lineNumber);
305                    mv.visitIntInsn(SIPUSH, switchNumber);
306                    mv.visitIntInsn(SIPUSH, branch);
307                    instrumentInvokeTouchSwitch();
308            }
309            
310            private void instrumentJumpHit(boolean branch)
311            {
312                    instrumentOwnerClass();
313                    
314                    //Invoke the touchJump(lineNumber, branchNumber, branch)
315                    instrumentPutLineAndBranchNumbers();
316                    mv.visitInsn(branch ? BOOLEAN_TRUE : BOOLEAN_FALSE);
317                    instrumentInvokeTouchJump();
318            }
319    
320            private void instrumentInvokeTouchJump()
321            {
322                    mv.visitMethodInsn(INVOKESTATIC, TOUCH_COLLECTOR_CLASS, "touchJump", "(Ljava/lang/String;IIZ)V");
323                    mv.visitIntInsn(SIPUSH, -1); //is important to reset current branch, because we have to know that the branch info on stack has already been used and can't be used
324                    mv.visitVarInsn(ISTORE, myVariableIndex + 1);
325            }
326    
327            private void instrumentInvokeTouchSwitch()
328            {
329                    mv.visitMethodInsn(INVOKESTATIC, TOUCH_COLLECTOR_CLASS, "touchSwitch", "(Ljava/lang/String;III)V");
330            }
331    
332            private void instrumentPutLineAndBranchNumbers()
333            {
334                    mv.visitVarInsn(ILOAD, myVariableIndex);
335                    mv.visitVarInsn(ILOAD, myVariableIndex + 1);
336            }
337    
338            private Label instrumentIsLastJump() {
339                    mv.visitVarInsn(ILOAD, myVariableIndex);
340                    mv.visitIntInsn(SIPUSH, lastJump.getLineNumber());
341                    Label newLabelX = new Label();
342                    mv.visitJumpInsn(IF_ICMPNE, newLabelX);
343                    mv.visitVarInsn(ILOAD, myVariableIndex + 1);
344                    mv.visitIntInsn(SIPUSH, lastJump.getJumpNumber());
345                    mv.visitJumpInsn(IF_ICMPNE, newLabelX);
346                    return newLabelX;
347            }
348    
349            public void visitMaxs(int maxStack, int maxLocals)
350            {
351                    mv.visitLocalVariable("__cobertura__line__number__", "I", null, startLabel, endLabel, myVariableIndex);
352                    mv.visitLocalVariable("__cobertura__branch__number__", "I", null, startLabel, endLabel, myVariableIndex + 1);
353                    super.visitMaxs(maxStack, maxLocals);
354            }
355    
356    }