1
2
3
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
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 }