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 */
023package com.jcoverage.coverage;
024
025import com.jcoverage.util.ClassGenHelper;
026import com.jcoverage.util.InstructionHelper;
027import com.jcoverage.util.InstructionListHelper;
028import com.jcoverage.util.MethodGenHelper;
029
030import java.util.HashSet;
031import java.util.Set;
032
033import org.apache.bcel.classfile.Method;
034
035import org.apache.bcel.generic.ClassGen;
036import org.apache.bcel.generic.IfInstruction;
037import org.apache.bcel.generic.InstructionHandle;
038import org.apache.bcel.generic.InstructionList;
039import org.apache.bcel.generic.InstructionTargeter;
040import org.apache.bcel.generic.InvokeInstruction;
041import org.apache.bcel.generic.LDC;
042import org.apache.bcel.generic.LineNumberGen;
043import org.apache.bcel.generic.MethodGen;
044import org.apache.bcel.generic.RET;
045import org.apache.bcel.generic.Type;
046
047import org.apache.oro.text.regex.MalformedPatternException;
048import org.apache.oro.text.regex.Pattern;
049import org.apache.oro.text.regex.Perl5Compiler;
050import org.apache.oro.text.regex.Perl5Matcher;
051
052import org.apache.log4j.Logger;
053
054
055/**
056 * Add bytecode instrumentation to the method.
057 */
058class 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}