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.pass1;
027
028import java.util.Set;
029import java.util.concurrent.atomic.AtomicInteger;
030
031import net.sourceforge.cobertura.CoverageIgnore;
032import net.sourceforge.cobertura.instrument.ContextMethodAwareMethodAdapter;
033
034import org.objectweb.asm.AnnotationVisitor;
035import org.objectweb.asm.Label;
036import org.objectweb.asm.MethodVisitor;
037import org.objectweb.asm.Opcodes;
038import org.objectweb.asm.Type;
039
040public class DetectIgnoredCodeMethodVisitor extends ContextMethodAwareMethodAdapter {
041        final String superName;
042        final Set<Integer> ignoredLineIds;
043        final Set<String>  ignoredMethodNamesAndSignatures;
044         
045        final Set<String> ignoreMethodAnnotations;
046        final boolean ignoreTrivial;
047
048        enum IgnoredStatus {
049                POSSIBLE_TRIVIAL_GETTER,
050                POSSIBLE_TRIVIAL_SETTER,
051                POSSIBLE_TRIVIAL_INIT,
052                IGNORED_BY_ANNOTATION,
053                NOT_IGNORED;
054                
055                boolean isTrivial(){
056                        return (this == POSSIBLE_TRIVIAL_GETTER)
057                                || (this == POSSIBLE_TRIVIAL_SETTER)
058                                || (this == POSSIBLE_TRIVIAL_INIT);                           
059                }
060        }
061        
062        public IgnoredStatus ignoredStatus;
063        
064        public DetectIgnoredCodeMethodVisitor(MethodVisitor mv,
065                        Set<Integer> ignoredLineIds, Set<String> ignoredMethodNamesAndSignatures,
066                        boolean ignoreTrivial, Set<String> ignoreMethodAnnotations,
067                        String className, String superName, String methodName, String description,
068                        AtomicInteger lineIdGenerator) {
069                super(mv,className, methodName, description, lineIdGenerator);
070                this.superName = superName;
071                this.ignoredLineIds = ignoredLineIds;
072                this.ignoredMethodNamesAndSignatures = ignoredMethodNamesAndSignatures;
073                this.ignoreTrivial = ignoreTrivial;
074                this.ignoredStatus = checkForTrivialSignature(methodName, description);
075                this.ignoreMethodAnnotations = ignoreMethodAnnotations;
076        }
077
078        private static IgnoredStatus checkForTrivialSignature(String name, String desc) {
079          Type[] args = Type.getArgumentTypes(desc);
080          Type ret = Type.getReturnType(desc);    
081          if (name.equals("<init>")) {
082        return IgnoredStatus.POSSIBLE_TRIVIAL_INIT;
083          }
084        
085          // a "setter" method must:
086          // - have a name starting with "set"
087          // - take one arguments
088          // - return nothing (void)
089          if (name.startsWith("set") && args.length == 1 && ret.equals(Type.VOID_TYPE)) {
090                  return IgnoredStatus.POSSIBLE_TRIVIAL_SETTER;
091          }
092          
093          // a "getter" method must:
094          // - have a name starting with "get", "is", or "has"
095          // - take no arguments
096          // - return a value (non-void)
097          if ((name.startsWith("get") || name.startsWith("is") || name.startsWith("has")) &&
098             args.length == 0 && !ret.equals(Type.VOID_TYPE)) {
099                  return IgnoredStatus.POSSIBLE_TRIVIAL_GETTER;
100          }
101          
102          return IgnoredStatus.NOT_IGNORED;
103        }
104        
105        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {         
106           // Check to see if this annotation is one of the ones that we use to 
107           // trigger us to ignore this method
108           String clazz = Type.getObjectType(desc.substring(1).replace(';', ' ').trim()).getClassName();
109           if (ignoreMethodAnnotations.contains(clazz)
110                 || desc.equals(Type.getDescriptor(CoverageIgnore.class))){
111                   ignoredStatus = IgnoredStatus.IGNORED_BY_ANNOTATION;
112           }                    
113           return super.visitAnnotation(desc, visible);
114    }
115        
116        @Override
117        public void visitJumpInsn(int arg0, Label arg1) {               
118                markNotTrivial();
119                super.visitJumpInsn(arg0, arg1);
120        }
121        
122        public void visitFieldInsn(int opcode, String string, String string1, String string2){
123          super.visitFieldInsn(opcode, string, string1, string2);
124          if (ignoredStatus.isTrivial()) {
125            // trivial opcodes for accessing class fields are:
126                // - GETFIELD or PUTFIELD
127                if ((ignoredStatus == IgnoredStatus.POSSIBLE_TRIVIAL_GETTER && opcode != Opcodes.GETFIELD) ||
128                        (ignoredStatus == IgnoredStatus.POSSIBLE_TRIVIAL_SETTER && opcode != Opcodes.PUTFIELD) ||
129                        (ignoredStatus == IgnoredStatus.POSSIBLE_TRIVIAL_INIT && opcode != Opcodes.PUTFIELD)) {
130                  markNotTrivial();
131                }
132          }
133        }
134        
135        public void visitVarInsn(int opcode, int i1) {
136                super.visitVarInsn(opcode, i1);
137                if (ignoredStatus.isTrivial() &&
138                    opcode != Opcodes.ILOAD &&
139                    opcode != Opcodes.LLOAD &&
140                        opcode != Opcodes.FLOAD &&
141                        opcode != Opcodes.DLOAD &&
142                        opcode != Opcodes.ALOAD) {
143          markNotTrivial();
144            }
145         }
146        
147        @Override
148        public void visitTypeInsn(int arg0, String arg1) {
149          super.visitTypeInsn(arg0, arg1);
150          markNotTrivial();
151        }
152        
153        @Override
154        public void visitLookupSwitchInsn(Label arg0, int[] arg1, Label[] arg2) {
155                super.visitLookupSwitchInsn(arg0, arg1, arg2);
156                markNotTrivial();
157        }
158        
159        @Override
160        public void visitTableSwitchInsn(int arg0, int arg1, Label arg2,
161                        Label[] arg3) {
162          super.visitTableSwitchInsn(arg0, arg1, arg2, arg3);
163          markNotTrivial();
164        }
165        
166        @Override
167        public void visitMultiANewArrayInsn(String arg0, int arg1) {
168                super.visitMultiANewArrayInsn(arg0, arg1);
169                markNotTrivial();
170        }
171        
172        @Override
173        public void visitIincInsn(int arg0, int arg1) {
174                super.visitIincInsn(arg0, arg1);
175                markNotTrivial();
176        }
177        
178        @Override
179        public void visitLdcInsn(Object arg0) {
180                super.visitLdcInsn(arg0);
181                markNotTrivial();               
182        }
183        
184        @Override
185        public void visitIntInsn(int arg0, int arg1) {
186                super.visitIntInsn(arg0, arg1);
187                markNotTrivial();
188        }
189        
190        @Override
191        public void visitMethodInsn(int opcode, String owner, String name, String desc) {
192                if (ignoredStatus.isTrivial() &&
193                  !(ignoredStatus == IgnoredStatus.POSSIBLE_TRIVIAL_INIT
194                          && name.equals("<init>") && owner.equals(superName) && opcode == Opcodes.INVOKESPECIAL)) {
195                         markNotTrivial();
196                }               
197                super.visitMethodInsn(opcode, owner, name, desc);
198        }
199        
200        @Override
201        public void visitEnd() {
202                super.visitEnd();
203                if ((ignoredStatus == IgnoredStatus.IGNORED_BY_ANNOTATION)
204                    || (ignoreTrivial && ignoredStatus.isTrivial())) {
205                  ignoredMethodNamesAndSignatures.add(methodName + methodSignature);
206                }               
207        }
208        
209        public void markNotTrivial(){
210                if (ignoredStatus.isTrivial()) {
211                        ignoredStatus = IgnoredStatus.NOT_IGNORED;
212                }
213        }
214        
215}