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.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         // TODO: need to also process field instructions inside accessor methods (STATIC + SYNTHETIC, "access$nnn")
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         // This is to ignore bytecode belonging to a static initialization block inserted in a regular line of code by
247         // the Java
248         // compiler when the class contains at least one "assert" statement.
249         // Otherwise, that line of code would always appear as partially covered when running with assertions enabled.
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 }