001/* 002 * Cobertura - http://cobertura.sourceforge.net/ 003 * 004 * Copyright (C) 2011 Piotr Tabor 005 * 006 * Note: This file is dual licensed under the GPL and the Apache 007 * Source License (so that it can be used from both the main 008 * Cobertura classes and the ant tasks). 009 * 010 * Cobertura is free software; you can redistribute it and/or modify 011 * it under the terms of the GNU General Public License as published 012 * by the Free Software Foundation; either version 2 of the License, 013 * or (at your option) any later version. 014 * 015 * Cobertura is distributed in the hope that it will be useful, but 016 * WITHOUT ANY WARRANTY; without even the implied warranty of 017 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 018 * General Public License for more details. 019 * 020 * You should have received a copy of the GNU General Public License 021 * along with Cobertura; if not, write to the Free Software 022 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 023 * USA 024 */ 025 026package net.sourceforge.cobertura.instrument.pass3; 027 028import java.util.Collection; 029import java.util.Map; 030import java.util.Set; 031import java.util.regex.Pattern; 032 033import net.sourceforge.cobertura.instrument.AbstractFindTouchPointsClassInstrumenter; 034import net.sourceforge.cobertura.instrument.FindTouchPointsMethodAdapter; 035import net.sourceforge.cobertura.instrument.tp.ClassMap; 036 037import org.objectweb.asm.ClassVisitor; 038import org.objectweb.asm.MethodVisitor; 039import org.objectweb.asm.Opcodes; 040import org.objectweb.asm.Type; 041import org.objectweb.asm.commons.LocalVariablesSorter; 042 043/** 044 * <p>This class is responsible for real instrumentation of the user's class.</p> 045 * 046 * <p>It uses information acquired 047 * by {@link BuildClassMapClassVisitor} ( {@link #classMap} ) and 048 * {@link DetectDuplicatedCodeClassVisitor} and injects 049 * code snippet provided by {@link CodeProvider} ( {@link #codeProvider} ).</p> 050 * 051 * @author piotr.tabor@gmail.com 052 */ 053public class InjectCodeClassInstrumenter extends AbstractFindTouchPointsClassInstrumenter{ 054 /** 055 * This class is responsible for injecting code inside 'interesting places' of methods inside instrumented class 056 */ 057 private final InjectCodeTouchPointListener touchPointListener; 058 059 /** 060 * {@link ClassMap} generated in previous instrumentation pass by {@link BuildClassMapClassVisitor} 061 */ 062 private final ClassMap classMap; 063 064 /** 065 * {@link CodeProvider} used to generate pieces of asm code that is injected into instrumented class. 066 * 067 * We are strictly recommending here using {@link FastArrayCodeProvider} instead of {@link AtomicArrayCodeProvider} because 068 * of performance. 069 */ 070 private final CodeProvider codeProvider; 071 072 /** 073 * When we processing the class we want to now if we processed 'static initialization block' (clinit method). 074 * 075 * <p>If there is no such a method in the instrumented class - we will need to generate it at the end</p> 076 */ 077 private boolean wasStaticInitMethodVisited=false; 078 079 private final Set<String> ignoredMethods; 080 081 /** 082 * @param cv - a listener for code-instrumentation events 083 * @param ignoreRegexp - list of patters of method calls that should be ignored from line-coverage-measurement 084 * @param classMap - map of all interesting places in the class. You should acquire it by {@link BuildClassMapClassVisitor} and remember to 085 * prepare it using {@link ClassMap#assignCounterIds()} before using it with {@link InjectCodeClassInstrumenter} 086 * @param duplicatedLinesMap - map of found duplicates in the class. You should use {@link DetectDuplicatedCodeClassVisitor} to find the duplicated lines. 087 */ 088 public InjectCodeClassInstrumenter(ClassVisitor cv, Collection<Pattern> ignoreRegexes, boolean threadsafeRigorous, 089 ClassMap classMap,Map<Integer, Map<Integer, Integer>> duplicatedLinesMap, 090 Set<String> ignoredMethods) { 091 super(cv,ignoreRegexes,duplicatedLinesMap); 092 this.classMap=classMap; 093 this.ignoredMethods = ignoredMethods; 094 codeProvider = threadsafeRigorous ? new AtomicArrayCodeProvider() : new FastArrayCodeProvider(); 095 touchPointListener=new InjectCodeTouchPointListener(classMap, codeProvider); 096 } 097 098 /** 099 * <p>Marks the class 'already instrumented' and injects code connected to the fields that are keeping counters.</p> 100 */ 101 @Override 102 public void visit(int version, int access, String name, String signature, 103 String supertype, String[] interfaces) { 104 105 super.visit(version, access, name, signature, supertype, interfaces); 106 codeProvider.generateCountersField(cv); 107 } 108 109 /** 110 * <p>Instrumenting a code in a single method. Special conditions for processing 'static initialization block'.</p> 111 * 112 * <p>This method also uses {@link ShiftVariableMethodAdapter} that is used firstly to calculate the index of internal 113 * variable injected to store information about last 'processed' jump or switch in runtime ( {@link ShiftVariableMethodAdapter#calculateFirstStackVariable(int, String)} ), 114 * and then is used to inject code responsible for keeping the variable and shifting (+1) all previously seen variables. 115 */ 116 @Override 117 public MethodVisitor visitMethod(int access, String name, String desc, 118 String signature, String[] exceptions) { 119 MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); 120 if (ignoredMethods.contains(name + desc)) { 121 return mv; 122 } 123 if ((access & Opcodes.ACC_STATIC) != 0) { 124 mv = new GenerateCallCoberturaInitMethodVisitor(mv, classMap.getClassName()); 125 if ("<clinit>".equals(name)) { 126 wasStaticInitMethodVisited=true; 127 } 128 } 129 FindTouchPointsMethodAdapter instrumenter = new FindTouchPointsMethodAdapter(mv,classMap.getClassName(),name,desc,eventIdGenerator,duplicatedLinesMap,lineIdGenerator); 130 instrumenter.setTouchPointListener(touchPointListener); 131 instrumenter.setIgnoreRegexp(getIgnoreRegexp()); 132 LocalVariablesSorter sorter = new LocalVariablesSorter(access, desc, instrumenter); 133 int variable = sorter.newLocal(Type.INT_TYPE); 134 touchPointListener.setLastJumpIdVariableIndex(variable); 135 return sorter; 136 //return new ShiftVariableMethodAdapter(instrumenter, access, desc, 1); 137 } 138 139 /** 140 * Method instrumenter that injects {@link CodeProvider#generateCINITmethod(MethodVisitor, String, int)} code, and 141 * then forwards the whole previous content of the method. 142 * 143 * @author piotr.tabor@gmail.com 144 */ 145 private class GenerateCallCoberturaInitMethodVisitor extends MethodVisitor { 146 private String className; 147 public GenerateCallCoberturaInitMethodVisitor(MethodVisitor arg0,String className) { 148 super(Opcodes.ASM4, arg0); 149 this.className = className; 150 } 151 152 @Override 153 public void visitCode() { 154 codeProvider.generateCallCoberturaInitMethod(mv, className); 155 super.visitCode(); 156 } 157 } 158 159 /** 160 * <p>If there was no 'static initialization block' in the class, the method is responsible for generating the method.<br/> 161 * It is also responsible for generating method that keeps mapping of counterIds into source places connected to them</p> 162 163 */ 164 @Override 165 public void visitEnd() { 166 if (!wasStaticInitMethodVisited){ 167 //We need to generate new method 168 MethodVisitor mv = super.visitMethod(Opcodes.ACC_STATIC, "<clinit>", "()V", null, null); 169 mv.visitCode(); 170 codeProvider.generateCallCoberturaInitMethod(mv, classMap.getClassName()); 171 mv.visitInsn(Opcodes.RETURN); 172 mv.visitMaxs(/*stack*/3,/*local*/ 0); 173 mv.visitEnd(); 174 wasStaticInitMethodVisited=true; 175 } 176 177 codeProvider.generateCoberturaInitMethod(cv, classMap.getClassName(), 178 classMap.getMaxCounterId() + 1); 179 codeProvider.generateCoberturaClassMapMethod(cv, classMap); 180 codeProvider.generateCoberturaGetAndResetCountersMethod(cv, classMap.getClassName()); 181 182 super.visitEnd(); 183 } 184 185 186}