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}