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