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