View Javadoc
1   /*
2    * Copyright (c) 2006 JMockit developers
3    * This file is subject to the terms of the MIT license (see LICENSE.txt).
4    */
5   package mockit.coverage.modification;
6   
7   import static mockit.asm.jvmConstants.Access.ABSTRACT;
8   import static mockit.asm.jvmConstants.Access.ANNOTATION;
9   import static mockit.asm.jvmConstants.Access.ENUM;
10  import static mockit.asm.jvmConstants.Access.FINAL;
11  import static mockit.asm.jvmConstants.Access.INTERFACE;
12  import static mockit.asm.jvmConstants.Access.STATIC;
13  import static mockit.asm.jvmConstants.Access.SUPER;
14  import static mockit.asm.jvmConstants.Access.SYNTHETIC;
15  
16  import edu.umd.cs.findbugs.annotations.NonNull;
17  import edu.umd.cs.findbugs.annotations.Nullable;
18  
19  import java.util.HashMap;
20  import java.util.Map;
21  
22  import mockit.asm.classes.ClassInfo;
23  import mockit.asm.classes.ClassReader;
24  import mockit.asm.classes.ClassWriter;
25  import mockit.asm.classes.WrappingClassVisitor;
26  import mockit.asm.fields.FieldVisitor;
27  import mockit.asm.methods.MethodVisitor;
28  import mockit.asm.methods.MethodWriter;
29  import mockit.coverage.data.CoverageData;
30  import mockit.coverage.data.FileCoverageData;
31  import mockit.internal.ClassFile;
32  
33  final class CoverageModifier extends WrappingClassVisitor {
34      private static final Map<String, CoverageModifier> INNER_CLASS_MODIFIERS = new HashMap<>();
35      private static final int FIELD_MODIFIERS_TO_IGNORE = FINAL + SYNTHETIC;
36  
37      @Nullable
38      static byte[] recoverModifiedByteCodeIfAvailable(@NonNull String innerClassName) {
39          CoverageModifier modifier = INNER_CLASS_MODIFIERS.remove(innerClassName);
40          return modifier == null ? null : modifier.toByteArray();
41      }
42  
43      @Nullable
44      private String internalClassName;
45      @Nullable
46      private String simpleClassName;
47      @NonNull
48      private String sourceFileName;
49      @Nullable
50      private FileCoverageData fileData;
51      private final boolean forInnerClass;
52      private boolean forEnumClass;
53      @Nullable
54      private String kindOfTopLevelType;
55  
56      CoverageModifier(@NonNull ClassReader cr) {
57          this(cr, false);
58      }
59  
60      private CoverageModifier(@NonNull ClassReader cr, boolean forInnerClass) {
61          super(new ClassWriter(cr));
62          sourceFileName = "";
63          this.forInnerClass = forInnerClass;
64      }
65  
66      private CoverageModifier(@NonNull ClassReader cr, @NonNull CoverageModifier other,
67              @Nullable String simpleClassName) {
68          this(cr, true);
69          sourceFileName = other.sourceFileName;
70          fileData = other.fileData;
71          internalClassName = other.internalClassName;
72          this.simpleClassName = simpleClassName;
73      }
74  
75      @Override
76      public void visit(int version, int access, @NonNull String name, @NonNull ClassInfo additionalInfo) {
77          if ((access & SYNTHETIC) != 0) {
78              throw new VisitInterruptedException();
79          }
80  
81          boolean nestedType = name.indexOf('$') > 0;
82  
83          if (!nestedType && kindOfTopLevelType == null) {
84              // noinspection ConstantConditions
85              kindOfTopLevelType = getKindOfJavaType(access, additionalInfo.superName);
86          }
87  
88          forEnumClass = (access & ENUM) != 0;
89  
90          String sourceFileDebugName = getSourceFileDebugName(additionalInfo);
91  
92          if (!forInnerClass) {
93              extractClassAndSourceFileName(name);
94  
95              boolean cannotModify = (access & ANNOTATION) != 0;
96  
97              if (cannotModify) {
98                  throw VisitInterruptedException.INSTANCE;
99              }
100 
101             registerAsInnerClassModifierIfApplicable(access, name, nestedType);
102             createFileData(sourceFileDebugName);
103         }
104 
105         cw.visit(version, access, name, additionalInfo);
106     }
107 
108     @NonNull
109     private static String getKindOfJavaType(int typeModifiers, @NonNull String superName) {
110         if ((typeModifiers & ANNOTATION) != 0) {
111             return "ant";
112         }
113         if ((typeModifiers & INTERFACE) != 0) {
114             return "itf";
115         }
116         if ((typeModifiers & ENUM) != 0) {
117             return "enm";
118         }
119         if ((typeModifiers & ABSTRACT) != 0) {
120             return "absCls";
121         }
122         if (superName.endsWith("Exception") || superName.endsWith("Error")) {
123             return "exc";
124         }
125         return "cls";
126     }
127 
128     @NonNull
129     private static String getSourceFileDebugName(@NonNull ClassInfo additionalInfo) {
130         String sourceFileDebugName = additionalInfo.sourceFileName;
131 
132         if (sourceFileDebugName == null || !sourceFileDebugName.endsWith(".java")) {
133             throw VisitInterruptedException.INSTANCE;
134         }
135 
136         return sourceFileDebugName;
137     }
138 
139     private void extractClassAndSourceFileName(@NonNull String className) {
140         internalClassName = className;
141         int p = className.lastIndexOf('/');
142 
143         if (p < 0) {
144             simpleClassName = className;
145             sourceFileName = "";
146         } else {
147             simpleClassName = className.substring(p + 1);
148             sourceFileName = className.substring(0, p + 1);
149         }
150     }
151 
152     private void registerAsInnerClassModifierIfApplicable(int access, @NonNull String name, boolean nestedType) {
153         if (!forEnumClass && (access & SUPER) != 0 && nestedType) {
154             INNER_CLASS_MODIFIERS.put(name.replace('/', '.'), this);
155         }
156     }
157 
158     private void createFileData(@NonNull String sourceFileDebugName) {
159         sourceFileName += sourceFileDebugName;
160         fileData = CoverageData.instance().getOrAddFile(sourceFileName, kindOfTopLevelType);
161     }
162 
163     @Override
164     public void visitInnerClass(@NonNull String name, @Nullable String outerName, @Nullable String innerName,
165             int access) {
166         cw.visitInnerClass(name, outerName, innerName, access);
167 
168         if (forInnerClass || isSyntheticOrEnumClass(access) || !isNestedInsideClassBeingModified(name, outerName)) {
169             return;
170         }
171 
172         String innerClassName = name.replace('/', '.');
173 
174         if (INNER_CLASS_MODIFIERS.containsKey(innerClassName)) {
175             return;
176         }
177 
178         ClassReader innerCR = ClassFile.createClassReader(CoverageModifier.class.getClassLoader(), name);
179 
180         if (innerCR != null) {
181             CoverageModifier innerClassModifier = new CoverageModifier(innerCR, this, innerName);
182             innerCR.accept(innerClassModifier);
183             INNER_CLASS_MODIFIERS.put(innerClassName, innerClassModifier);
184         }
185     }
186 
187     private static boolean isSyntheticOrEnumClass(int access) {
188         return (access & SYNTHETIC) != 0 || access == STATIC + ENUM;
189     }
190 
191     private boolean isNestedInsideClassBeingModified(@NonNull String internalName, @Nullable String outerName) {
192         String className = outerName == null ? internalName : outerName;
193         int p = className.indexOf('$');
194         String outerClassName = p < 0 ? className : className.substring(0, p);
195 
196         return outerClassName.equals(internalClassName);
197     }
198 
199     @Override
200     public FieldVisitor visitField(int access, @NonNull String name, @NonNull String desc, @Nullable String signature,
201             @Nullable Object value) {
202         if (fileData != null && simpleClassName != null && (access & FIELD_MODIFIERS_TO_IGNORE) == 0) {
203             fileData.dataCoverageInfo.addField(simpleClassName, name, (access & STATIC) != 0);
204         }
205 
206         return cw.visitField(access, name, desc, signature, value);
207     }
208 
209     @Override
210     public MethodVisitor visitMethod(int access, @NonNull String name, @NonNull String desc, @Nullable String signature,
211             @Nullable String[] exceptions) {
212         MethodWriter mw = cw.visitMethod(access, name, desc, signature, exceptions);
213 
214         if ((access & SYNTHETIC) != 0 || fileData == null || "<clinit>".equals(name) && forEnumClass) {
215             return mw;
216         }
217 
218         return new MethodModifier(mw, sourceFileName, fileData);
219     }
220 }