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    }