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
023package net.sourceforge.cobertura.instrument;
024
025import net.sourceforge.cobertura.util.RegexUtil;
026
027import org.objectweb.asm.Label;
028import 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 */
033public 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}