blob: e1f00dadef78c824833b0a9f3751b7ad9ac4dbc2 [file] [log] [blame]
Yue Ganaf3c4122016-12-05 14:36:02 +00001// Copyright 2016 The Bazel Authors. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.testing.coverage;
16
17import java.util.ArrayList;
18import java.util.HashMap;
19import java.util.List;
20import java.util.Map;
21import java.util.TreeMap;
iirinaff1f7452019-05-20 09:02:49 -070022import org.jacoco.core.internal.analysis.Instruction;
Yue Ganaf3c4122016-12-05 14:36:02 +000023import org.jacoco.core.internal.flow.IFrame;
Yue Ganaf3c4122016-12-05 14:36:02 +000024import org.jacoco.core.internal.flow.LabelInfo;
25import org.jacoco.core.internal.flow.MethodProbesVisitor;
26import org.objectweb.asm.Handle;
27import org.objectweb.asm.Label;
28
29/**
30 * The mapper is a probes visitor that will cache control flow information as well as keeping track
31 * of the probes as the main driver generates the probe ids. Upon finishing the method it uses the
32 * information collected to generate the mapping information between probes and the instructions.
33 */
34public class MethodProbesMapper extends MethodProbesVisitor {
35 /*
36 * The implementation roughly follows the same pattern of the Analyzer class of Jacoco.
37 *
38 * The mapper has a few states:
39 *
40 * - lineMappings: a mapping between line number and labels
41 *
42 * - a sequence of "instructions", where each instruction has one or more predecessors. The
43 * predecessor field has a sole purpose of propagating probe id. The 'merge' nodes in the CFG has
44 * no predecessors, since the branch stops at theses points.
45 *
46 * - The instructions each has states that keep track of the probes that are associated with the
47 * instruction.
48 *
49 * Initially the probe ids are assigned to the instructions that immediately precede the probe. At
50 * the end of visiting the methods, the probe ids are propagated through the predecessor chains.
51 */
52
53 // States
54 //
55 // These are state variables that needs to be updated in the visitor methods.
56 // The values usually changes as we traverse the byte code.
57 private Instruction lastInstruction = null;
58 private int currentLine = -1;
59 private List<Label> currentLabels = new ArrayList<>();
60
61 // Result
62 private Map<Integer, BranchExp> lineToBranchExp = new TreeMap();
Yue Ganaf3c4122016-12-05 14:36:02 +000063 public Map<Integer, BranchExp> result() {
64 return lineToBranchExp;
65 }
66
67 // Intermediate results
68 //
69 // These values are built up during the visitor methods. They will be used to compute
70 // the final results.
71 private List<Instruction> instructions = new ArrayList<Instruction>();
72 private List<Jump> jumps = new ArrayList<>();
73 private Map<Integer, Instruction> probeToInsn = new TreeMap<>();
74
75 // A map which associates intructions with their coverage expressions.
76 private final Map<Instruction, CovExp> insnToCovExp = new HashMap();
77
78 // A map which associates a instruction to the branch index in its predecessor
79 // e.g., the instruction that follows a conditional jump instruction must exists in
80 // this map.
81 private final Map<Instruction, Integer> insnToIdx = new HashMap();
82
83 // Local cache
84 //
85 // These are maps corresponding to data structures available in JaCoCo in other form.
86 // We use local data structure to avoid need to change the JaCoCo internal code.
87 private Map<Instruction, Instruction> predecessors = new HashMap<>();
88 private Map<Label, Instruction> labelToInsn = new HashMap<>();
89
90 /** Visitor method to append a new Instruction */
91 private void visitInsn() {
92 Instruction instruction = new Instruction(currentLine);
93 instructions.add(instruction);
94 if (lastInstruction != null) {
iirinaff1f7452019-05-20 09:02:49 -070095 lastInstruction.addBranch(instruction, 0); // the first branch from last instruction
Yue Ganaf3c4122016-12-05 14:36:02 +000096 predecessors.put(instruction, lastInstruction); // Update local cache
97 }
98
99 for (Label label : currentLabels) {
100 labelToInsn.put(label, instruction);
101 }
102 currentLabels.clear(); // Update states
103 lastInstruction = instruction;
104 }
105
106 // Plain visitors: called from adapter when no probe is needed
107 @Override
108 public void visitInsn(int opcode) {
109 visitInsn();
110 }
111
112 @Override
113 public void visitIntInsn(int opcode, int operand) {
114 visitInsn();
115 }
116
117 @Override
118 public void visitVarInsn(int opcode, int variable) {
119 visitInsn();
120 }
121
122 @Override
123 public void visitTypeInsn(int opcode, String type) {
124 visitInsn();
125 }
126
127 @Override
128 public void visitFieldInsn(int opcode, String owner, String name, String desc) {
129 visitInsn();
130 }
131
132 @Override
133 public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
134 visitInsn();
135 }
136
137 @Override
138 public void visitInvokeDynamicInsn(String name, String desc, Handle handle, Object... args) {
139 visitInsn();
140 }
141
142 @Override
143 public void visitLdcInsn(Object cst) {
144 visitInsn();
145 }
146
147 @Override
148 public void visitIincInsn(int var, int inc) {
149 visitInsn();
150 }
151
152 @Override
153 public void visitMultiANewArrayInsn(String desc, int dims) {
154 visitInsn();
155 }
156
iirinaff1f7452019-05-20 09:02:49 -0700157
Yue Ganaf3c4122016-12-05 14:36:02 +0000158 // Methods that need to update the states
159 @Override
160 public void visitJumpInsn(int opcode, Label label) {
161 visitInsn();
iirinaff1f7452019-05-20 09:02:49 -0700162 jumps.add(new Jump(lastInstruction, label, 1));
Yue Ganaf3c4122016-12-05 14:36:02 +0000163 }
164
165 @Override
166 public void visitLabel(Label label) {
167 currentLabels.add(label);
168 if (!LabelInfo.isSuccessor(label)) {
169 lastInstruction = null;
170 }
171 }
172
173 @Override
174 public void visitLineNumber(int line, Label start) {
175 currentLine = line;
176 }
177
178 /** Visit a switch instruction with no probes */
179 private void visitSwitchInsn(Label dflt, Label[] labels) {
180 visitInsn();
181
182 // Handle default transition
183 LabelInfo.resetDone(dflt);
iirinaff1f7452019-05-20 09:02:49 -0700184 int branch = 0;
185 jumps.add(new Jump(lastInstruction, dflt, branch));
Yue Ganaf3c4122016-12-05 14:36:02 +0000186 LabelInfo.setDone(dflt);
187
188 // Handle other transitions
189 LabelInfo.resetDone(labels);
190 for (Label label : labels) {
191 if (!LabelInfo.isDone(label)) {
iirinaff1f7452019-05-20 09:02:49 -0700192 jumps.add(new Jump(lastInstruction, label, branch));
Yue Ganaf3c4122016-12-05 14:36:02 +0000193 LabelInfo.setDone(label);
194 }
195 }
196 }
197
198 @Override
199 public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
200 visitSwitchInsn(dflt, labels);
201 }
202
203 @Override
204 public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
205 visitSwitchInsn(dflt, labels);
206 }
207
208 private void addProbe(int probeId) {
209 // We do not add probes to the flow graph, but we need to update
210 // the branch count of the predecessor of the probe
iirinaff1f7452019-05-20 09:02:49 -0700211 lastInstruction.addBranch(false, 0);
Yue Ganaf3c4122016-12-05 14:36:02 +0000212 probeToInsn.put(probeId, lastInstruction);
213 }
214
215 // Probe visit methods
216 @Override
217 public void visitProbe(int probeId) {
218 // This function is only called when visiting a merge node which
219 // is a successor.
220 // It adds an probe point to the last instruction
221 assert (lastInstruction != null);
222
223 addProbe(probeId);
224 lastInstruction = null; // Merge point should have no predecessor.
225 }
226
227 @Override
228 public void visitJumpInsnWithProbe(int opcode, Label label, int probeId, IFrame frame) {
229 visitInsn();
230 addProbe(probeId);
231 }
232
233 @Override
234 public void visitInsnWithProbe(int opcode, int probeId) {
235 visitInsn();
236 addProbe(probeId);
237 }
238
239 @Override
240 public void visitTableSwitchInsnWithProbes(
241 int min, int max, Label dflt, Label[] labels, IFrame frame) {
242 visitSwitchInsnWithProbes(dflt, labels);
243 }
244
245 @Override
246 public void visitLookupSwitchInsnWithProbes(
247 Label dflt, int[] keys, Label[] labels, IFrame frame) {
248 visitSwitchInsnWithProbes(dflt, labels);
249 }
250
251 private void visitSwitchInsnWithProbes(Label dflt, Label[] labels) {
252 visitInsn();
253 LabelInfo.resetDone(dflt);
254 LabelInfo.resetDone(labels);
iirinaff1f7452019-05-20 09:02:49 -0700255 int branch = 0;
256 visitTargetWithProbe(dflt, branch);
Yue Ganaf3c4122016-12-05 14:36:02 +0000257 for (Label l : labels) {
iirinaff1f7452019-05-20 09:02:49 -0700258 visitTargetWithProbe(l, branch);
Yue Ganaf3c4122016-12-05 14:36:02 +0000259 }
260 }
261
iirinaff1f7452019-05-20 09:02:49 -0700262 private void visitTargetWithProbe(Label label, int branch) {
Yue Ganaf3c4122016-12-05 14:36:02 +0000263 if (!LabelInfo.isDone(label)) {
264 int id = LabelInfo.getProbeId(label);
265 if (id == LabelInfo.NO_PROBE) {
iirinaff1f7452019-05-20 09:02:49 -0700266 jumps.add(new Jump(lastInstruction, label, branch));
Yue Ganaf3c4122016-12-05 14:36:02 +0000267 } else {
268 // Note, in this case the instrumenter should insert intermediate labels
269 // for the probes. These probes will be added for the switch instruction.
270 //
271 // There is no direct jump between lastInstruction and the label either.
272 addProbe(id);
273 }
274 LabelInfo.setDone(label);
275 }
276 }
277
278 // If a CovExp of pred is ProbeExp, create a single-branch BranchExp and put it in the map.
279 // Also update the index of insn.
iirinaff1f7452019-05-20 09:02:49 -0700280 private BranchExp getPredBranchExp(Instruction predecessor) {
Yue Ganaf3c4122016-12-05 14:36:02 +0000281 BranchExp result = null;
282 CovExp exp = insnToCovExp.get(predecessor);
283 if (exp instanceof ProbeExp) {
284 result = new BranchExp(exp); // Change ProbeExp to BranchExp
285 insnToCovExp.put(predecessor, result);
286 // This can only happen if the internal data of Jacoco is inconsistent:
287 // the instruction is the predecessor of more than one instructions,
288 // but its branch count is not > 1.
289 } else {
290 result = (BranchExp) exp;
291 }
292 return result;
293 }
294
295 // Update a branch predecessor and returns whether the BranchExp of the predecessor is new.
296 private boolean updateBranchPredecessor(Instruction predecessor, Instruction insn, CovExp exp) {
297 CovExp predExp = insnToCovExp.get(predecessor);
298 if (predExp == null) {
299 BranchExp branchExp = new BranchExp(exp);
300 insnToCovExp.put(predecessor, branchExp);
301 insnToIdx.put(insn, 0); // current insn is the first branch
302 return true;
303 }
304
iirinaff1f7452019-05-20 09:02:49 -0700305 BranchExp branchExp = getPredBranchExp(predecessor);
Yue Ganaf3c4122016-12-05 14:36:02 +0000306 Integer branchIdx = insnToIdx.get(insn);
307 if (branchIdx == null) {
308 // Keep track of the instructions in the branches that are already added
309 branchIdx = branchExp.add(exp);
310 insnToIdx.put(insn, branchIdx);
311 }
312 // If the branch where the instruction is on is already added, no need to do anything as
313 // branchExp has a reference to exp already.
314 return false;
315 }
316
317 /** Finishing the method */
318 @Override
319 public void visitEnd() {
320
321 for (Jump jump : jumps) {
322 Instruction insn = labelToInsn.get(jump.target);
iirinaff1f7452019-05-20 09:02:49 -0700323 jump.source.addBranch(insn, jump.branch);
Yue Ganaf3c4122016-12-05 14:36:02 +0000324 predecessors.put(insn, jump.source);
325 }
326
327 // Compute CovExp for every instruction.
328 for (Map.Entry<Integer, Instruction> entry : probeToInsn.entrySet()) {
329 int probeId = entry.getKey();
330 Instruction ins = entry.getValue();
331
332 Instruction insn = ins;
333 CovExp exp = new ProbeExp(probeId);
334
335 // Compute CovExp for the probed instruction.
336 CovExp existingExp = insnToCovExp.get(insn);
337 if (existingExp != null) {
338 // The instruction already has a branch, add the probeExp as
339 // a new branch.
340 if (existingExp instanceof BranchExp) {
341 BranchExp branchExp = (BranchExp) existingExp;
342 branchExp.add(exp);
343 } else {
344 // This can only happen if the internal data is inconsistent.
345 // The instruction is a predecessor of another instruction and also
346 // has a probe, but the branch count is not > 1.
347 }
348 } else {
iirinaff1f7452019-05-20 09:02:49 -0700349 if (insn.getBranchCounter().getTotalCount() > 1) {
Yue Ganaf3c4122016-12-05 14:36:02 +0000350 exp = new BranchExp(exp);
351 }
352 insnToCovExp.put(insn, exp);
353 }
354
355 Instruction predecessor = predecessors.get(insn);
356 while (predecessor != null) {
iirinaff1f7452019-05-20 09:02:49 -0700357 if (predecessor.getBranchCounter().getTotalCount() > 1) {
Yue Ganaf3c4122016-12-05 14:36:02 +0000358 boolean isNewBranch = updateBranchPredecessor(predecessor, insn, exp);
359 if (!isNewBranch) {
360 // If the branch already exists, no need to visit predecessors any more.
361 break;
362 }
363 } else {
364 // No branch at predecessor, use the same CovExp
365 insnToCovExp.put(predecessor, exp);
366 }
367 insn = predecessor;
368 exp = insnToCovExp.get(predecessor);
369 predecessor = predecessors.get(insn);
370 }
371 }
372
373 // Merge branches in the instructions on the same line
374 for (Instruction insn : instructions) {
iirinaff1f7452019-05-20 09:02:49 -0700375 if (insn.getBranchCounter().getTotalCount() > 1) {
Yue Ganaf3c4122016-12-05 14:36:02 +0000376 CovExp insnExp = insnToCovExp.get(insn);
377 if (insnExp != null && (insnExp instanceof BranchExp)) {
378 BranchExp exp = (BranchExp) insnExp;
379 BranchExp lineExp = lineToBranchExp.get(insn.getLine());
380 if (lineExp == null) {
381 lineToBranchExp.put(insn.getLine(), exp);
382 } else {
383 lineExp.merge(exp);
384 }
385 } else {
386 // If we reach here, the internal data of the mapping is inconsistent, either
387 // 1) An instruction has branches but we do not create BranchExp for it.
388 // 2) An instruction has branches but it does not have an associated CovExp.
389 }
390 }
391 }
392 }
393
394 /** Jumps between instructions and labels */
395 class Jump {
396 public final Instruction source;
397 public final Label target;
iirinaff1f7452019-05-20 09:02:49 -0700398 public final int branch;
Yue Ganaf3c4122016-12-05 14:36:02 +0000399
iirinaff1f7452019-05-20 09:02:49 -0700400 public Jump(Instruction i, Label l, int b) {
Yue Ganaf3c4122016-12-05 14:36:02 +0000401 source = i;
402 target = l;
iirinaff1f7452019-05-20 09:02:49 -0700403 branch = b;
Yue Ganaf3c4122016-12-05 14:36:02 +0000404 }
405 }
406}