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