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 }