1
2
3
4
5 package mockit.coverage.modification;
6
7 import static mockit.asm.jvmConstants.Opcodes.ACONST_NULL;
8 import static mockit.asm.jvmConstants.Opcodes.DCONST_0;
9 import static mockit.asm.jvmConstants.Opcodes.DUP;
10 import static mockit.asm.jvmConstants.Opcodes.DUP2_X1;
11 import static mockit.asm.jvmConstants.Opcodes.DUP_X1;
12 import static mockit.asm.jvmConstants.Opcodes.DUP_X2;
13 import static mockit.asm.jvmConstants.Opcodes.FCONST_0;
14 import static mockit.asm.jvmConstants.Opcodes.GETFIELD;
15 import static mockit.asm.jvmConstants.Opcodes.GETSTATIC;
16 import static mockit.asm.jvmConstants.Opcodes.GOTO;
17 import static mockit.asm.jvmConstants.Opcodes.ICONST_0;
18 import static mockit.asm.jvmConstants.Opcodes.INVOKESPECIAL;
19 import static mockit.asm.jvmConstants.Opcodes.INVOKESTATIC;
20 import static mockit.asm.jvmConstants.Opcodes.INVOKEVIRTUAL;
21 import static mockit.asm.jvmConstants.Opcodes.IRETURN;
22 import static mockit.asm.jvmConstants.Opcodes.LCONST_0;
23 import static mockit.asm.jvmConstants.Opcodes.POP;
24 import static mockit.asm.jvmConstants.Opcodes.POP2;
25 import static mockit.asm.jvmConstants.Opcodes.PUTSTATIC;
26 import static mockit.asm.jvmConstants.Opcodes.RETURN;
27 import static mockit.asm.jvmConstants.Opcodes.SIPUSH;
28
29 import edu.umd.cs.findbugs.annotations.NonNull;
30 import edu.umd.cs.findbugs.annotations.Nullable;
31
32 import mockit.asm.annotations.AnnotationVisitor;
33 import mockit.asm.controlFlow.Label;
34 import mockit.asm.methods.MethodWriter;
35 import mockit.asm.methods.WrappingMethodVisitor;
36 import mockit.coverage.data.FileCoverageData;
37 import mockit.coverage.lines.PerFileLineCoverage;
38
39 import org.checkerframework.checker.index.qual.NonNegative;
40
41 final class MethodModifier extends WrappingMethodVisitor {
42 private static final String DATA_RECORDING_CLASS = "mockit/coverage/TestRun";
43
44 @NonNull
45 private final String sourceFileName;
46 @NonNull
47 private final FileCoverageData fileData;
48 @NonNull
49 private final PerFileLineCoverage lineCoverageInfo;
50 @NonNull
51 private final CFGTracking cfgTracking;
52 private boolean foundInterestingInstruction;
53 @NonNegative
54 int currentLine;
55
56 MethodModifier(@NonNull MethodWriter mw, @NonNull String sourceFileName, @NonNull FileCoverageData fileData) {
57 super(mw);
58 this.sourceFileName = sourceFileName;
59 this.fileData = fileData;
60 lineCoverageInfo = fileData.getLineCoverageData();
61 cfgTracking = new CFGTracking(lineCoverageInfo);
62 }
63
64 @Override
65 public AnnotationVisitor visitAnnotation(@NonNull String desc) {
66 boolean isTestMethod = desc.startsWith("Lorg/junit/") || desc.startsWith("Lorg/testng/");
67
68 if (isTestMethod) {
69 throw VisitInterruptedException.INSTANCE;
70 }
71
72 return mw.visitAnnotation(desc);
73 }
74
75 @Override
76 public void visitLineNumber(@NonNegative int line, @NonNull Label start) {
77 lineCoverageInfo.addLine(line);
78 currentLine = line;
79 cfgTracking.startNewLine();
80 generateCallToRegisterLineExecution();
81 mw.visitLineNumber(line, start);
82 }
83
84 private void generateCallToRegisterLineExecution() {
85 mw.visitIntInsn(SIPUSH, fileData.index);
86 pushCurrentLineOnTheStack();
87 mw.visitMethodInsn(INVOKESTATIC, DATA_RECORDING_CLASS, "lineExecuted", "(II)V", false);
88 }
89
90 private void pushCurrentLineOnTheStack() {
91 if (currentLine <= Short.MAX_VALUE) {
92 mw.visitIntInsn(SIPUSH, currentLine);
93 } else {
94 mw.visitLdcInsn(currentLine);
95 }
96 }
97
98 @Override
99 public void visitLabel(@NonNull Label label) {
100 mw.visitLabel(label);
101 cfgTracking.afterNewLabel(currentLine, label);
102 }
103
104 @Override
105 public void visitJumpInsn(@NonNegative int opcode, @NonNull Label label) {
106 Label jumpSource = mw.getCurrentBlock();
107 assert jumpSource != null;
108
109 mw.visitJumpInsn(opcode, label);
110
111 if (opcode == GOTO) {
112 cfgTracking.afterGoto();
113 } else {
114 cfgTracking.afterConditionalJump(this, jumpSource, label);
115 }
116 }
117
118 private void generateCallToRegisterBranchTargetExecutionIfPending() {
119 cfgTracking.generateCallToRegisterBranchTargetExecutionIfPending(this);
120 }
121
122 void generateCallToRegisterBranchTargetExecution(@NonNegative int branchIndex) {
123 mw.visitIntInsn(SIPUSH, fileData.index);
124 pushCurrentLineOnTheStack();
125 mw.visitIntInsn(SIPUSH, branchIndex);
126 mw.visitMethodInsn(INVOKESTATIC, DATA_RECORDING_CLASS, "branchExecuted", "(III)V", false);
127 }
128
129 @Override
130 public void visitInsn(@NonNegative int opcode) {
131 boolean isReturn = opcode >= IRETURN && opcode <= RETURN;
132
133 if (!isReturn && !isDefaultReturnValue(opcode)) {
134 foundInterestingInstruction = true;
135 }
136
137 if (isReturn && !foundInterestingInstruction && cfgTracking.hasOnlyOneLabelBeingVisited()) {
138 lineCoverageInfo.getOrCreateLineData(currentLine).markAsUnreachable();
139 } else {
140 cfgTracking.beforeNoOperandInstruction(this, opcode);
141 }
142
143 mw.visitInsn(opcode);
144 }
145
146 private static boolean isDefaultReturnValue(@NonNegative int opcode) {
147 return opcode == ACONST_NULL || opcode == ICONST_0 || opcode == LCONST_0 || opcode == FCONST_0
148 || opcode == DCONST_0;
149 }
150
151 @Override
152 public void visitIntInsn(@NonNegative int opcode, int operand) {
153 foundInterestingInstruction = true;
154 generateCallToRegisterBranchTargetExecutionIfPending();
155 mw.visitIntInsn(opcode, operand);
156 }
157
158 @Override
159 public void visitVarInsn(@NonNegative int opcode, @NonNegative int varIndex) {
160 generateCallToRegisterBranchTargetExecutionIfPending();
161 mw.visitVarInsn(opcode, varIndex);
162 }
163
164 @Override
165 public void visitTypeInsn(@NonNegative int opcode, @NonNull String typeDesc) {
166 generateCallToRegisterBranchTargetExecutionIfPending();
167 mw.visitTypeInsn(opcode, typeDesc);
168 }
169
170 @Override
171 public void visitFieldInsn(@NonNegative int opcode, @NonNull String owner, @NonNull String name,
172 @NonNull String desc) {
173
174 boolean getField = opcode == GETSTATIC || opcode == GETFIELD;
175 boolean isStatic = opcode == PUTSTATIC || opcode == GETSTATIC;
176 char fieldType = desc.charAt(0);
177 boolean size2 = fieldType == 'J' || fieldType == 'D';
178 String classAndFieldNames = null;
179 boolean fieldHasData = false;
180
181 if (!owner.startsWith("java/")) {
182 classAndFieldNames = owner.substring(owner.lastIndexOf('/') + 1) + '.' + name;
183 fieldHasData = fileData.dataCoverageInfo.isFieldWithCoverageData(classAndFieldNames);
184
185 if (fieldHasData && !isStatic) {
186 generateCodeToSaveInstanceReferenceOnTheStack(getField, size2);
187 }
188 }
189
190 generateCallToRegisterBranchTargetExecutionIfPending();
191 mw.visitFieldInsn(opcode, owner, name, desc);
192
193 if (opcode == GETSTATIC && "$assertionsDisabled".equals(name)) {
194 cfgTracking.registerAssertFoundInCurrentLine();
195 }
196
197 if (fieldHasData) {
198 generateCallToRegisterFieldCoverage(getField, isStatic, size2, classAndFieldNames);
199 }
200 }
201
202 private void generateCodeToSaveInstanceReferenceOnTheStack(boolean getField, boolean size2) {
203 if (getField) {
204 mw.visitInsn(DUP);
205 } else {
206 if (size2) {
207 mw.visitInsn(DUP2_X1);
208 mw.visitInsn(POP2);
209 mw.visitInsn(DUP_X2);
210 mw.visitInsn(DUP_X2);
211 } else {
212 mw.visitInsn(DUP_X1);
213 mw.visitInsn(POP);
214 mw.visitInsn(DUP_X1);
215 mw.visitInsn(DUP_X1);
216 }
217 mw.visitInsn(POP);
218 }
219 }
220
221 private void generateCallToRegisterFieldCoverage(boolean getField, boolean isStatic, boolean size2,
222 @NonNull String classAndFieldNames) {
223 if (!isStatic && getField) {
224 if (size2) {
225 mw.visitInsn(DUP2_X1);
226 mw.visitInsn(POP2);
227 } else {
228 mw.visitInsn(DUP_X1);
229 mw.visitInsn(POP);
230 }
231 }
232
233 mw.visitLdcInsn(sourceFileName);
234 mw.visitLdcInsn(classAndFieldNames);
235
236 String methodToCall = getField ? "fieldRead" : "fieldAssigned";
237 String methodDesc = isStatic ? "(Ljava/lang/String;Ljava/lang/String;)V"
238 : "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V";
239
240 mw.visitMethodInsn(INVOKESTATIC, DATA_RECORDING_CLASS, methodToCall, methodDesc, false);
241 }
242
243 @Override
244 public void visitMethodInsn(@NonNegative int opcode, @NonNull String owner, @NonNull String name,
245 @NonNull String desc, boolean itf) {
246
247
248
249
250 if (opcode == INVOKEVIRTUAL && "java/lang/Class".equals(owner) && "desiredAssertionStatus".equals(name)) {
251 cfgTracking.registerAssertFoundInCurrentLine();
252 }
253
254 if (opcode != INVOKESPECIAL || !"()V".equals(desc)) {
255 foundInterestingInstruction = true;
256 }
257
258 generateCallToRegisterBranchTargetExecutionIfPending();
259 mw.visitMethodInsn(opcode, owner, name, desc, itf);
260 cfgTracking.afterMethodInstruction(opcode, owner, name);
261 }
262
263 @Override
264 public void visitLdcInsn(@NonNull Object cst) {
265 foundInterestingInstruction = true;
266 generateCallToRegisterBranchTargetExecutionIfPending();
267 mw.visitLdcInsn(cst);
268 }
269
270 @Override
271 public void visitIincInsn(@NonNegative int varIndex, int increment) {
272 generateCallToRegisterBranchTargetExecutionIfPending();
273 mw.visitIincInsn(varIndex, increment);
274 }
275
276 @Override
277 public void visitTryCatchBlock(@NonNull Label start, @NonNull Label end, @NonNull Label handler,
278 @Nullable String type) {
279 generateCallToRegisterBranchTargetExecutionIfPending();
280 mw.visitTryCatchBlock(start, end, handler, type);
281 }
282
283 @Override
284 public void visitLookupSwitchInsn(@NonNull Label dflt, @NonNull int[] keys, @NonNull Label[] labels) {
285 cfgTracking.beforeLookupSwitchInstruction();
286 generateCallToRegisterBranchTargetExecutionIfPending();
287 mw.visitLookupSwitchInsn(dflt, keys, labels);
288 }
289
290 @Override
291 public void visitTableSwitchInsn(@NonNegative int min, @NonNegative int max, @NonNull Label dflt,
292 @NonNull Label... labels) {
293 generateCallToRegisterBranchTargetExecutionIfPending();
294 mw.visitTableSwitchInsn(min, max, dflt, labels);
295 }
296
297 @Override
298 public void visitMultiANewArrayInsn(@NonNull String desc, @NonNegative int dims) {
299 generateCallToRegisterBranchTargetExecutionIfPending();
300 mw.visitMultiANewArrayInsn(desc, dims);
301 }
302
303 @Override
304 public void visitMaxStack(@NonNegative int maxStack) {
305 if (maxStack > 1) {
306 lineCoverageInfo.markLineAsReachable(currentLine);
307 }
308
309 mw.visitMaxStack(maxStack);
310 }
311 }