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 }