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.LinkedList;
030import java.util.List;
031
032import net.sourceforge.cobertura.coveragedata.LightClassmapListener;
033import net.sourceforge.cobertura.coveragedata.TouchCollector;
034import net.sourceforge.cobertura.instrument.tp.ClassMap;
035import net.sourceforge.cobertura.instrument.tp.JumpTouchPointDescriptor;
036import net.sourceforge.cobertura.instrument.tp.LineTouchPointDescriptor;
037import net.sourceforge.cobertura.instrument.tp.SwitchTouchPointDescriptor;
038import net.sourceforge.cobertura.instrument.tp.TouchPointDescriptor;
039
040import org.objectweb.asm.ClassVisitor;
041import org.objectweb.asm.Label;
042import org.objectweb.asm.MethodVisitor;
043import org.objectweb.asm.Opcodes;
044import org.objectweb.asm.Type;
045
046/**
047 * Common method used by most of {@link AbstractCodeProvider} implementations.  
048 * The methods realized here are independent on counters storing structure. 
049 *   
050 * 
051 * @author piotr.tabor@gmail.com
052 */
053public abstract class AbstractCodeProvider implements CodeProvider {
054
055        /**
056         * CounterId used to store unnecessary events to avoid fake jump counting in
057         * instrumented(generated) code
058         */
059        public static final int FAKE_COUNTER_ID = 0;
060        
061        public AbstractCodeProvider() {
062                super();
063        }
064        
065        public void generateCodeThatSetsJumpCounterIdVariable(
066                        MethodVisitor nextMethodVisitor, int new_value,
067                        int lastJumpIdVariableIndex) {
068                nextMethodVisitor.visitLdcInsn(new_value);
069                nextMethodVisitor.visitVarInsn(Opcodes.ISTORE, lastJumpIdVariableIndex);
070        }
071
072        public void generateCodeThatZeroJumpCounterIdVariable(
073                        MethodVisitor nextMethodVisitor, int lastJumpIdVariableIndex) {
074                generateCodeThatSetsJumpCounterIdVariable(nextMethodVisitor,
075                                FAKE_COUNTER_ID, lastJumpIdVariableIndex);
076        }
077        
078        public void generateCodeThatIncrementsCoberturaCounterIfVariableEqualsAndCleanVariable(
079                        MethodVisitor nextMethodVisitor,
080                        Integer neededJumpCounterIdVariableValue,
081                        Integer counterIdToIncrement, int lastJumpIdVariableIndex,
082                        String className) {
083
084                nextMethodVisitor.visitLdcInsn((int) neededJumpCounterIdVariableValue);
085                nextMethodVisitor.visitVarInsn(Opcodes.ILOAD, lastJumpIdVariableIndex);
086                Label afterJump = new Label();
087                nextMethodVisitor.visitJumpInsn(Opcodes.IF_ICMPNE, afterJump);
088                generateCodeThatIncrementsCoberturaCounter(nextMethodVisitor,
089                                counterIdToIncrement, className);
090                generateCodeThatZeroJumpCounterIdVariable(nextMethodVisitor,
091                                lastJumpIdVariableIndex);
092                nextMethodVisitor.visitLabel(afterJump);
093        }
094        
095        /**
096         * {@inheritDoc}
097         * 
098         * The code injected by this implementation just registers the class using {@link TouchCollector#registerClass(Class)}. This way, during the  
099         * execution, touch collector knows that is responsible to ask the class after execution about a current status of the counters.  
100         */
101        protected void generateRegisterClass(MethodVisitor mv, String className) {
102                mv.visitLdcInsn(Type.getObjectType(className));
103                mv.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(TouchCollector.class), "registerClass","(Ljava/lang/Class;)V");           
104        }       
105        
106        final String CLASSMAP_LISTENER_INTERNALNAME = Type.getInternalName(LightClassmapListener.class);
107        
108        /** 
109         * {@inheritDoc}<br/><br/>
110         * 
111         * Generates method (named  {@link #COBERTURA_CLASSMAP_METHOD_NAME}) with such a signature:
112         *  __cobertura_classmap( {@link LightClassmapListener} listener).</br>  
113         *  
114         *  The method informs the listener about all lines, jumps and switches found, and about all counters tracking 
115         *  the constructions.   
116         */
117        public void generateCoberturaClassMapMethod(ClassVisitor cv, ClassMap classMap) {
118                
119                LinkedList<TouchPointDescriptor> touchPointDescriptors = new LinkedList<TouchPointDescriptor>(classMap.getTouchPointsInLineOrder());
120                int parts = 0;
121                for(int j=0; touchPointDescriptors.size() > 0; j++) {
122                        List<TouchPointDescriptor> bufor = new LinkedList<TouchPointDescriptor>(); 
123                        for (int i = 0; i < 1000 && touchPointDescriptors.size() > 0; i++){
124                          bufor.add(touchPointDescriptors.removeFirst());
125                        }
126                        classMapContent(cv, j, bufor);
127                        parts++;
128                }
129                
130                MethodVisitor mv=cv.visitMethod(
131                                Opcodes.ACC_PUBLIC|Opcodes.ACC_STATIC, 
132                                COBERTURA_CLASSMAP_METHOD_NAME, 
133                                "("+Type.getType(LightClassmapListener.class).toString()+")V",
134                                null,null);
135                mv.visitCode();
136                mv.visitVarInsn(Opcodes.ALOAD,0);
137                
138                mv.visitInsn(Opcodes.DUP);
139                mv.visitLdcInsn(Type.getObjectType(classMap.getClassName()));
140                mv.visitMethodInsn(Opcodes.INVOKEINTERFACE,CLASSMAP_LISTENER_INTERNALNAME, "setClazz", "(Ljava/lang/Class;)V");
141                
142                if(classMap.getSource()!=null){
143                        mv.visitInsn(Opcodes.DUP);
144                        mv.visitLdcInsn(classMap.getSource());
145                        mv.visitMethodInsn(Opcodes.INVOKEINTERFACE,CLASSMAP_LISTENER_INTERNALNAME, "setSource", "(Ljava/lang/String;)V");                       
146                }
147                        
148                for (int i=0; i < parts; i++) {
149                        mv.visitInsn(Opcodes.DUP);
150                        mv.visitMethodInsn(Opcodes.INVOKESTATIC, classMap.getClassName(), 
151                                        COBERTURA_CLASSMAP_METHOD_NAME+"_"+i, "("+Type.getType(LightClassmapListener.class).toString()+")V");
152                }       
153
154                mv.visitInsn(Opcodes.POP);
155                mv.visitInsn(Opcodes.RETURN);
156                mv.visitMaxs(0, 0);//will be recalculated by writer
157                mv.visitEnd();          
158        }
159        
160        enum Abcd {
161                A, B, C;
162        }
163                
164        private void classMapContent(ClassVisitor cv, int nr, List<TouchPointDescriptor> touchPointDescriptors){
165                MethodVisitor mv=cv.visitMethod(
166                                Opcodes.ACC_PUBLIC|Opcodes.ACC_STATIC, 
167                                COBERTURA_CLASSMAP_METHOD_NAME + "_" + nr, 
168                                "("+Type.getType(LightClassmapListener.class).toString()+")V",
169                                null,null);
170                mv.visitCode();
171                mv.visitVarInsn(Opcodes.ALOAD,0);               
172                for (TouchPointDescriptor tpd : touchPointDescriptors){
173                        mv.visitInsn(Opcodes.DUP);
174                        mv.visitLdcInsn(tpd.getLineNumber());                   
175                        if (tpd instanceof LineTouchPointDescriptor){
176                                mv.visitLdcInsn(((LineTouchPointDescriptor) tpd).getCounterId());
177                                mv.visitLdcInsn(((LineTouchPointDescriptor) tpd).getMethodName());
178                                mv.visitLdcInsn(((LineTouchPointDescriptor) tpd).getMethodSignature());
179                                mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, CLASSMAP_LISTENER_INTERNALNAME,"putLineTouchPoint","(IILjava/lang/String;Ljava/lang/String;)V");
180                        }else if (tpd instanceof JumpTouchPointDescriptor){
181                                mv.visitLdcInsn(((JumpTouchPointDescriptor) tpd).getCounterIdForTrue());
182                                mv.visitLdcInsn(((JumpTouchPointDescriptor) tpd).getCounterIdForFalse());
183                                mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, CLASSMAP_LISTENER_INTERNALNAME,"putJumpTouchPoint","(III)V");
184                        }else if (tpd instanceof SwitchTouchPointDescriptor) {
185                                SwitchTouchPointDescriptor stpd=(SwitchTouchPointDescriptor)tpd;
186                                final String enum_sign = ((SwitchTouchPointDescriptor) tpd).getEnumType();
187                                if (enum_sign == null) {
188                                        mv.visitLdcInsn(Integer.MAX_VALUE);
189                                } else {
190                                        mv.visitMethodInsn(Opcodes.INVOKESTATIC, enum_sign, "values", "()[L" + enum_sign + ";");
191                                        mv.visitInsn(Opcodes.ARRAYLENGTH);
192                                }                                                               
193                                Collection<Integer> ci=stpd.getCountersForLabels();
194                                mv.visitLdcInsn(ci.size());//Size of a new table
195                                mv.visitIntInsn(Opcodes.NEWARRAY,Opcodes.T_INT);                                
196                                int i=0; 
197                                for(Integer counterId:ci){
198                                        mv.visitInsn(Opcodes.DUP); //First for addition of items, second ad putSwitchTouchPoint parameter (or next loop iteration)
199                                        mv.visitLdcInsn(i);
200                                        mv.visitLdcInsn(counterId);
201                                        mv.visitInsn(Opcodes.IASTORE);
202                                        i++;
203                                }
204                                mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, CLASSMAP_LISTENER_INTERNALNAME,"putSwitchTouchPoint","(II[I)V");                            
205                        }
206                }
207                mv.visitInsn(Opcodes.POP);
208                mv.visitInsn(Opcodes.RETURN);
209                mv.visitMaxs(0, 0);//will be recalculated by writer
210                mv.visitEnd();          
211        }
212        
213        /**
214         * Generates code that is injected into static constructor of an instrumented class.  
215         * 
216         * It is good place to initiate static fields inserted into a class ({@link #generateCountersField(ClassVisitor)}), 
217         * or execute other code that should be executed when the class it used for the first time. Registering the class in 
218         * {@link TouchCollector} would be a bright idea.
219         * 
220         * It is expected that all counter will be set to zero after that operation.       
221         * 
222         * @param mv           - {@link MethodVisitor} that is listener of code-generation events 
223         * @param className    - internal name (asm) of class being instrumented  
224         * @param counters_cnt - information about how many counters are expected to be used by instrumentation code. 
225         *                       In most cases the method is responsible for allocating objects that will be used to store counters.  
226         */
227        protected abstract void generateCINITmethod(MethodVisitor mv, String className, int counters_cnt);
228        
229        public void generateCoberturaInitMethod(ClassVisitor cv, String className, int countersCnt) {
230                MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, 
231                        COBERTURA_INIT_METHOD_NAME, "()V", null, null);
232                mv.visitCode();
233                generateCINITmethod(mv, className, countersCnt);                
234                mv.visitInsn(Opcodes.RETURN);
235                mv.visitMaxs(0, 0); //will be recalculated by writer
236                mv.visitEnd();
237        }
238                
239        public void generateCallCoberturaInitMethod(MethodVisitor mv, String className) {
240                mv.visitMethodInsn(Opcodes.INVOKESTATIC, className, COBERTURA_INIT_METHOD_NAME, "()V");
241        }
242}