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