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 }