View Javadoc
1   /*
2    * MIT License
3    * Copyright (c) 2006-2025 JMockit developers
4    * See LICENSE file for full license text.
5    */
6   package mockit.coverage.modification;
7   
8   import static mockit.asm.jvmConstants.Opcodes.ICONST_0;
9   import static mockit.asm.jvmConstants.Opcodes.ICONST_1;
10  import static mockit.asm.jvmConstants.Opcodes.INVOKEVIRTUAL;
11  
12  import edu.umd.cs.findbugs.annotations.NonNull;
13  
14  import java.util.ArrayList;
15  import java.util.List;
16  
17  import mockit.asm.controlFlow.Label;
18  import mockit.coverage.lines.BranchCoverageData;
19  import mockit.coverage.lines.LineCoverageData;
20  import mockit.coverage.lines.PerFileLineCoverage;
21  
22  import org.checkerframework.checker.index.qual.NonNegative;
23  
24  final class CFGTracking {
25      @NonNull
26      private final PerFileLineCoverage lineCoverageInfo;
27      @NonNull
28      private final List<Label> visitedLabels;
29      @NonNull
30      private final List<Label> jumpTargetsForCurrentLine;
31      @NonNull
32      private final List<Integer> pendingBranches;
33      @NonNegative
34      private int lineExpectingInstructionAfterJump;
35      private boolean assertFoundInCurrentLine;
36      private boolean ignoreUntilNextLabel;
37      @NonNegative
38      private int foundPotentialBooleanExpressionValue;
39      @NonNegative
40      private int ignoreUntilNextSwitch;
41  
42      CFGTracking(@NonNull PerFileLineCoverage lineCoverageInfo) {
43          this.lineCoverageInfo = lineCoverageInfo;
44          visitedLabels = new ArrayList<>();
45          jumpTargetsForCurrentLine = new ArrayList<>(4);
46          pendingBranches = new ArrayList<>(6);
47      }
48  
49      void startNewLine() {
50          if (!pendingBranches.isEmpty()) {
51              pendingBranches.clear();
52          }
53  
54          jumpTargetsForCurrentLine.clear();
55      }
56  
57      void afterNewLabel(@NonNegative int currentLine, @NonNull Label label) {
58          if (ignoreUntilNextLabel || ignoreUntilNextSwitch > 0) {
59              ignoreUntilNextLabel = false;
60              return;
61          }
62  
63          visitedLabels.add(label);
64  
65          int jumpTargetIndex = jumpTargetsForCurrentLine.indexOf(label);
66  
67          if (jumpTargetIndex >= 0) {
68              label.jumpTargetLine = label.line > 0 ? label.line : currentLine;
69              int targetBranchIndex = 2 * jumpTargetIndex + 1;
70              pendingBranches.add(targetBranchIndex);
71              assertFoundInCurrentLine = false;
72          }
73  
74          foundPotentialBooleanExpressionValue = 0;
75      }
76  
77      void afterGoto() {
78          assertFoundInCurrentLine = false;
79  
80          if (foundPotentialBooleanExpressionValue == 1) {
81              foundPotentialBooleanExpressionValue = 2;
82          }
83      }
84  
85      void afterConditionalJump(@NonNull MethodModifier methodModifier, @NonNull Label jumpSource,
86              @NonNull Label jumpTarget) {
87          int currentLine = methodModifier.currentLine;
88  
89          if (currentLine == 0 || ignoreUntilNextLabel || ignoreUntilNextSwitch > 0
90                  || visitedLabels.contains(jumpTarget)) {
91              assertFoundInCurrentLine = false;
92              return;
93          }
94  
95          jumpSource.jumpTargetLine = currentLine;
96  
97          if (!jumpTargetsForCurrentLine.contains(jumpTarget)) {
98              jumpTargetsForCurrentLine.add(jumpTarget);
99          }
100 
101         LineCoverageData lineData = lineCoverageInfo.getOrCreateLineData(currentLine);
102         int sourceBranchIndex = lineData.addBranchingPoint(jumpSource, jumpTarget);
103         pendingBranches.add(sourceBranchIndex);
104 
105         if (assertFoundInCurrentLine) {
106             BranchCoverageData branchData = lineCoverageInfo.getBranchData(currentLine, sourceBranchIndex + 1);
107             branchData.markAsUnreachable();
108         }
109 
110         lineExpectingInstructionAfterJump = 0;
111         generateCallToRegisterBranchTargetExecutionIfPending(methodModifier);
112         lineExpectingInstructionAfterJump = currentLine;
113     }
114 
115     void generateCallToRegisterBranchTargetExecutionIfPending(@NonNull MethodModifier methodModifier) {
116         if (ignoreUntilNextLabel || ignoreUntilNextSwitch > 0) {
117             return;
118         }
119 
120         foundPotentialBooleanExpressionValue = 0;
121 
122         if (!pendingBranches.isEmpty()) {
123             for (Integer pendingBranchIndex : pendingBranches) {
124                 methodModifier.generateCallToRegisterBranchTargetExecution(pendingBranchIndex);
125             }
126 
127             pendingBranches.clear();
128         }
129 
130         if (lineExpectingInstructionAfterJump > 0) {
131             if (methodModifier.currentLine > lineExpectingInstructionAfterJump) {
132                 lineCoverageInfo.markLastLineSegmentAsEmpty(lineExpectingInstructionAfterJump);
133             }
134 
135             lineExpectingInstructionAfterJump = 0;
136         }
137     }
138 
139     boolean hasOnlyOneLabelBeingVisited() {
140         return visitedLabels.size() == 1;
141     }
142 
143     void registerAssertFoundInCurrentLine() {
144         assertFoundInCurrentLine = true;
145         ignoreUntilNextLabel = true;
146     }
147 
148     void beforeNoOperandInstruction(@NonNull MethodModifier methodModifier, @NonNegative int opcode) {
149         if ((opcode == ICONST_0 || opcode == ICONST_1) && foundPotentialBooleanExpressionValue == 0) {
150             generateCallToRegisterBranchTargetExecutionIfPending(methodModifier);
151             foundPotentialBooleanExpressionValue = 1;
152         } else {
153             generateCallToRegisterBranchTargetExecutionIfPending(methodModifier);
154         }
155     }
156 
157     void afterMethodInstruction(@NonNegative int opcode, @NonNull String owner, @NonNull String name) {
158         if (opcode == INVOKEVIRTUAL && "hashCode".equals(name) && "java/lang/String".equals(owner)
159                 && ignoreUntilNextSwitch == 0) {
160             ignoreUntilNextSwitch = 1;
161         }
162     }
163 
164     void beforeLookupSwitchInstruction() {
165         if (ignoreUntilNextSwitch == 1) {
166             ignoreUntilNextSwitch = 2;
167         }
168     }
169 }