001 /** 002 * www.jcoverage.com 003 * Copyright (C)2003 jcoverage ltd. 004 * 005 * This file is part of jcoverage. 006 * 007 * jcoverage 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 * jcoverage 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 jcoverage; 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 com.jcoverage.coverage; 024 025 import com.jcoverage.util.ClassGenHelper; 026 import com.jcoverage.util.InstructionHelper; 027 import com.jcoverage.util.InstructionListHelper; 028 import com.jcoverage.util.MethodGenHelper; 029 030 import java.util.HashSet; 031 import java.util.Set; 032 033 import org.apache.bcel.classfile.Method; 034 035 import org.apache.bcel.generic.ClassGen; 036 import org.apache.bcel.generic.IfInstruction; 037 import org.apache.bcel.generic.InstructionHandle; 038 import org.apache.bcel.generic.InstructionList; 039 import org.apache.bcel.generic.InstructionTargeter; 040 import org.apache.bcel.generic.InvokeInstruction; 041 import org.apache.bcel.generic.LDC; 042 import org.apache.bcel.generic.LineNumberGen; 043 import org.apache.bcel.generic.MethodGen; 044 import org.apache.bcel.generic.RET; 045 import org.apache.bcel.generic.Type; 046 047 import org.apache.oro.text.regex.MalformedPatternException; 048 import org.apache.oro.text.regex.Pattern; 049 import org.apache.oro.text.regex.Perl5Compiler; 050 import org.apache.oro.text.regex.Perl5Matcher; 051 052 import org.apache.log4j.Logger; 053 054 055 /** 056 * Add bytecode instrumentation to the method. 057 */ 058 class InstrumentMethodGen { 059 static final Logger logger=Logger.getLogger(InstrumentMethodGen.class); 060 final Method original; 061 final MethodGenHelper methodGenHelper; 062 final ClassGenHelper classGenHelper; 063 064 /** 065 * The set of "conditionals" (@see Conditional). Whenever a 066 * conditional branch is encountered it is recorded here, including 067 * the next Java source line after the conditional branch, and the 068 * Java source line of the branch target. This information is later 069 * used to calculate the branch coverage rate for this method. 070 */ 071 final Set conditionals=new HashSet(); 072 073 /** 074 * The set of "valid" source lines. That is, those lines of Java 075 * source code that do not represent comments, or other syntax 076 * "fluff" (e.g., "} else {"), or those lines that have been ignored 077 * because they match the ignore regex. 078 */ 079 final Set sourceLineNumbers=new HashSet(); 080 081 final Perl5Matcher pm=new Perl5Matcher(); 082 Pattern ignoreRegex=null; 083 084 InstrumentMethodGen(Method original,ClassGen cg,String ignoreRegex) { 085 this.original=original; 086 this.methodGenHelper=new MethodGenHelper(new MethodGen(original,cg.getClassName(),cg.getConstantPool())); 087 this.classGenHelper=ClassGenHelper.newInstance(cg); 088 089 Perl5Compiler pc=new Perl5Compiler(); 090 091 if(ignoreRegex!=null) { 092 /** 093 * Compile the ignore regex for later usage 094 */ 095 try { 096 this.ignoreRegex=pc.compile(ignoreRegex); 097 } catch(MalformedPatternException ex) { 098 throw new CoverageRuntimeException(ex); 099 } 100 } 101 } 102 103 void updateTargeters(InstructionHandle oldTarget,InstructionHandle newTarget,InstructionTargeter targeter) { 104 targeter.updateTarget(oldTarget,newTarget); 105 } 106 107 void updateTargeters(InstructionHandle oldTarget,InstructionHandle newTarget,InstructionTargeter[] targeters) { 108 for(int i=0;i<targeters.length;i++) { 109 updateTargeters(oldTarget,newTarget,targeters[i]); 110 } 111 } 112 113 /** 114 * Inserting coverage instrumentation to a method, inserts 115 * additional code into the instrumented class. When this happens we 116 * need to adjust any targeters of the original instruction so that 117 * they instead target the inserted instrumentation. The 118 * instrumentation is inserted immediately prior to 119 * <code>oldTarget</code>. Adjusting the targeters to 120 * <code>newTarget</code> (the start of where the instrumentation 121 * has been added) ensures that the instrumentation is invoked as 122 * the original code would have been. 123 */ 124 void updateTargeters(InstructionHandle oldTarget,InstructionHandle newTarget) { 125 if(oldTarget.hasTargeters()) { 126 updateTargeters(oldTarget,newTarget,oldTarget.getTargeters()); 127 } 128 } 129 130 /** 131 * We currently only ignore those invoke instructions that are from 132 * a matching regular regular expression. 133 */ 134 boolean isIgnorable(ClassGenHelper helper,InstructionHandle handle) { 135 if(InstructionHelper.isInvokeInstruction(handle)) { 136 if(logger.isDebugEnabled()) { 137 logger.debug("class name: "+helper.getClassName(handle)); 138 } 139 return pm.matches(helper.getClassName(handle),ignoreRegex); 140 } 141 return false; 142 } 143 144 boolean hasIgnoreRegex() { 145 return ignoreRegex!=null; 146 } 147 148 149 /** 150 * We can ignore (for the purposes of instrumentation) any set of 151 * instructions which are on our ignore list. Taking the instruction 152 * handle of the line number, we iterate over the instructions until 153 * we meet the next instruction that has a line number. If we 154 * encounter an instruction on our ignore list, then we can ignore 155 * (for the purposes of instrumentation) this group of instructions. 156 */ 157 boolean isIgnorable(ClassGenHelper helper,LineNumberGen lng) { 158 if(!hasIgnoreRegex()) { 159 return false; 160 } 161 162 if(logger.isDebugEnabled()) { 163 StringBuffer sb=new StringBuffer(); 164 sb.append("instruction offset: "); 165 sb.append(lng.getInstruction().getPosition()); 166 sb.append(", source line: "); 167 sb.append(lng.getSourceLine()); 168 logger.debug(sb.toString()); 169 } 170 171 if(isIgnorable(helper,lng.getInstruction())) { 172 return true; 173 } 174 175 InstructionHandle handle=lng.getInstruction().getNext(); 176 177 while((handle!=null)&&(!methodGenHelper.hasLineNumber(handle))) { 178 if(isIgnorable(helper,handle)) { 179 return true; 180 } 181 182 handle=handle.getNext(); 183 } 184 185 return false; 186 } 187 188 /** 189 * We've found a conditional branch instruction. We need to record 190 * the line number immediately after the branch, and the line number 191 * of the target of the branch. We can later determine branch 192 * coverage rates for this method. 193 * 194 * @param lng the line number that we found the branch instruction 195 * @param ifInstruction the actual <code>if</code> instruction 196 */ 197 void addIfInstruction(LineNumberGen lng,IfInstruction ifInstruction) { 198 if(logger.isDebugEnabled()) { 199 StringBuffer sb=new StringBuffer(); 200 sb.append("if instruction at line: "); 201 sb.append(lng.getSourceLine()); 202 sb.append(", target: "); 203 sb.append(methodGenHelper.getLineNumber(ifInstruction.getTarget())); 204 sb.append("(for method: "); 205 sb.append(methodGenHelper.getMethodGen().getClassName()); 206 sb.append('.'); 207 sb.append(methodGenHelper.getMethodGen().getName()); 208 sb.append(')'); 209 logger.debug(sb.toString()); 210 } 211 212 /** 213 * only add the conditional branch if the target has a line number 214 */ 215 if(methodGenHelper.getLineNumber(ifInstruction.getTarget())!=0) { 216 conditionals.add(ConditionalFactory.newConditional(lng,methodGenHelper.getLineNumber(ifInstruction.getTarget()))); 217 } 218 } 219 220 void handleIfInstruction(LineNumberGen lng) { 221 if(InstructionHelper.isIfInstruction(lng)) { 222 addIfInstruction(lng,(IfInstruction)lng.getInstruction().getInstruction()); 223 return; 224 } 225 226 InstructionHandle handle=lng.getInstruction().getNext(); 227 228 while((handle!=null)&&(!methodGenHelper.hasLineNumber(handle))) { 229 if(InstructionHelper.isIfInstruction(handle)) { 230 addIfInstruction(lng,(IfInstruction)handle.getInstruction()); 231 return; 232 } 233 handle=handle.getNext(); 234 } 235 } 236 237 /** 238 * Add coverage instrumentation to the instructions representing a 239 * line of Java source code. 240 */ 241 void addInstrumentation(LineNumberGen lng) { 242 if(logger.isDebugEnabled()) { 243 StringBuffer sb=new StringBuffer(); 244 sb.append("adding instrumentation to: "); 245 sb.append(classGenHelper.getClassGen().getClassName()); 246 sb.append('.'); 247 sb.append(methodGenHelper.getMethodGen().getName()); 248 sb.append(" at line: "); 249 sb.append(lng.getSourceLine()); 250 sb.append(", position: "); 251 sb.append(lng.getInstruction().getPosition()); 252 logger.debug(sb.toString()); 253 } 254 255 if(isIgnorable(classGenHelper,lng)) { 256 return; 257 } 258 259 /** 260 * If we find a conditional branch instruction in the set of 261 * instructions that represent this line of Java source code, 262 * include them in the set of conditionals, so that we can 263 * calculate the branch coverage rate. 264 */ 265 handleIfInstruction(lng); 266 267 /** 268 * Add this line of Java code to the list of "valid" source lines 269 * for this method 270 */ 271 addSourceLine(lng); 272 273 /** 274 * Emit and insert the coverage instrumentation code immediately 275 * prior to the first instruction representing the Java 276 * code. Update any targeters of the original instruction to 277 * instead target the coverage instrumentation code. 278 */ 279 updateTargeters(lng.getInstruction(),methodGenHelper.getMethodGen().getInstructionList().insert(lng.getInstruction(),emitGetInstrumentationAndTouchLine(lng))); 280 } 281 282 /** 283 * The core instrumentation. This sequence of instructions is 284 * emitted into the instrumented class on every line of original 285 * Java code. 286 * 287 * NOTE THAT THIS EMITTED CODE IS ALSO LICENSED UNDER THE GNU 288 * GENERAL PUBLIC LICENSE. NON GPL INSTRUMENTED APPLICATIONS MUST BE 289 * LICENSED UNDER SEPARATE AGREEMENT. FOR FURTHER DETAILS, PLEASE 290 * VISIT http://jcoverage.com/license.html. 291 */ 292 InstructionList emitGetInstrumentationAndTouchLine(LineNumberGen lng) { 293 InstructionList il=new InstructionList(); 294 295 /** 296 * Obtain an instance of InstrumentationFactory, via a static call 297 * to InstrumentationFactory. 298 */ 299 il.append(classGenHelper.createInvokeStatic(InstrumentationFactory.class,"getInstance",InstrumentationFactory.class)); 300 301 /** 302 * Create a new instance of Instrumentation (or reuse an existing 303 * instance, if one is already present in the factory), for the 304 * class that we have instrumented. 305 */ 306 il.append(new LDC(classGenHelper.getConstantPool().addString(classGenHelper.getClassGen().getClassName()))); 307 il.append(classGenHelper.createInvokeVirtual(InstrumentationFactory.class,"newInstrumentation",Instrumentation.class,String.class)); 308 309 /** 310 * Update the coverage counters for this line of source code, by 311 * "touching" its instrumentation. 312 */ 313 il.append(InstructionListHelper.push(classGenHelper.getConstantPool(),lng.getSourceLine())); 314 il.append(classGenHelper.createInvokeInterface(Instrumentation.class,"touch",void.class,int.class)); 315 316 return il; 317 } 318 319 /** 320 * We only record the set of "valid" source lines. That is, source 321 * lines that are not comments, or contain other syntax "fluff" 322 * (e.g., "} else {"), or any line of code that is being ignored by 323 * instrumentation ignore regex. <code>addSourceLine</code> is only 324 * called if the source line represented by <code>lng</code> is a 325 * "real" line of code. 326 */ 327 void addSourceLine(LineNumberGen lng) { 328 sourceLineNumbers.add(new Integer(lng.getSourceLine())); 329 } 330 331 void addInstrumentation(LineNumberGen[] lineNumberTable) { 332 for(int i=0;i<lineNumberTable.length;i++) { 333 if((i==(lineNumberTable.length-1))&&methodGenHelper.isVoidReturningMethod()&&InstructionHelper.isRetInstruction(lineNumberTable[i])) { 334 continue; 335 } 336 337 addInstrumentation(lineNumberTable[i]); 338 } 339 } 340 341 /** 342 * The entry point for this class. We add coverage instrumentation 343 * immediately prior to every instruction found in the line number 344 * table. 345 */ 346 public void addInstrumentation() { 347 if(logger.isDebugEnabled()) { 348 StringBuffer sb=new StringBuffer(); 349 sb.append("adding instrumentation to: "); 350 sb.append(classGenHelper.getClassGen().getClassName()); 351 sb.append('.'); 352 sb.append(methodGenHelper.getMethodGen().getName()); 353 logger.debug(sb.toString()); 354 } 355 356 /** 357 * Add instrumentation to this method. 358 */ 359 addInstrumentation(methodGenHelper.getMethodGen().getLineNumbers()); 360 361 /** 362 * Recalculate the maxium stack size necessary for this 363 * instrumented method. 364 */ 365 methodGenHelper.getMethodGen().setMaxStack(); 366 367 /** 368 * Replace the original method, with the instrumented method. 369 */ 370 classGenHelper.getClassGen().replaceMethod(original,methodGenHelper.getMethodGen().getMethod()); 371 } 372 373 /** 374 * @return the set of valid source line numbers, that is those that 375 * are not comments, nor syntax "fluff" (e.g., "} else {"), nor 376 * lines that are being ignored by the instrumentation ignore regex. 377 */ 378 Set getSourceLineNumbers() { 379 return sourceLineNumbers; 380 } 381 382 /** 383 * This method is used internally to calculate the branch coverage 384 * rate for this method. 385 */ 386 Set getConditionals() { 387 if(logger.isDebugEnabled()) { 388 StringBuffer sb=new StringBuffer(); 389 sb.append(classGenHelper.getClassGen().getClassName()); 390 sb.append('.'); 391 sb.append(methodGenHelper.getMethodGen().getName()); 392 sb.append(" conditionals: "); 393 sb.append(conditionals.toString()); 394 logger.debug(sb.toString()); 395 } 396 397 return conditionals; 398 } 399 }