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 }