blob: 69fcc72b055e1bce0b4ab8ff7d49a2ccd9266b6a [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;
Charles Mita065e2e82021-09-07 06:38:32 -070019import java.util.HashSet;
Yue Ganaf3c4122016-12-05 14:36:02 +000020import java.util.List;
21import java.util.Map;
Charles Mita065e2e82021-09-07 06:38:32 -070022import java.util.Set;
Yue Ganaf3c4122016-12-05 14:36:02 +000023import java.util.TreeMap;
iirinaff1f7452019-05-20 09:02:49 -070024import org.jacoco.core.internal.analysis.Instruction;
Charles Mita065e2e82021-09-07 06:38:32 -070025import org.jacoco.core.internal.analysis.filter.IFilter;
26import org.jacoco.core.internal.analysis.filter.IFilterContext;
27import org.jacoco.core.internal.analysis.filter.IFilterOutput;
Yue Ganaf3c4122016-12-05 14:36:02 +000028import org.jacoco.core.internal.flow.IFrame;
Yue Ganaf3c4122016-12-05 14:36:02 +000029import org.jacoco.core.internal.flow.LabelInfo;
30import org.jacoco.core.internal.flow.MethodProbesVisitor;
31import org.objectweb.asm.Handle;
32import org.objectweb.asm.Label;
Charles Mita065e2e82021-09-07 06:38:32 -070033import org.objectweb.asm.MethodVisitor;
34import org.objectweb.asm.tree.AbstractInsnNode;
35import org.objectweb.asm.tree.MethodNode;
Yue Ganaf3c4122016-12-05 14:36:02 +000036
37/**
38 * The mapper is a probes visitor that will cache control flow information as well as keeping track
39 * of the probes as the main driver generates the probe ids. Upon finishing the method it uses the
40 * information collected to generate the mapping information between probes and the instructions.
41 */
Charles Mita065e2e82021-09-07 06:38:32 -070042public class MethodProbesMapper extends MethodProbesVisitor implements IFilterOutput {
Yue Ganaf3c4122016-12-05 14:36:02 +000043 /*
44 * The implementation roughly follows the same pattern of the Analyzer class of Jacoco.
45 *
46 * The mapper has a few states:
47 *
48 * - lineMappings: a mapping between line number and labels
49 *
50 * - a sequence of "instructions", where each instruction has one or more predecessors. The
51 * predecessor field has a sole purpose of propagating probe id. The 'merge' nodes in the CFG has
52 * no predecessors, since the branch stops at theses points.
53 *
54 * - The instructions each has states that keep track of the probes that are associated with the
55 * instruction.
56 *
57 * Initially the probe ids are assigned to the instructions that immediately precede the probe. At
58 * the end of visiting the methods, the probe ids are propagated through the predecessor chains.
59 */
60
61 // States
62 //
63 // These are state variables that needs to be updated in the visitor methods.
64 // The values usually changes as we traverse the byte code.
65 private Instruction lastInstruction = null;
66 private int currentLine = -1;
67 private List<Label> currentLabels = new ArrayList<>();
Charles Mita065e2e82021-09-07 06:38:32 -070068 private AbstractInsnNode currentInstructionNode = null;
69 private Map<AbstractInsnNode, Instruction> instructionMap = new HashMap<>();
70
71 // Filtering
72 private IFilter filter;
73 private IFilterContext filterContext;
74 private HashSet<AbstractInsnNode> ignored = new HashSet<>();
Yue Ganaf3c4122016-12-05 14:36:02 +000075
76 // Result
77 private Map<Integer, BranchExp> lineToBranchExp = new TreeMap();
Yue Ganaf3c4122016-12-05 14:36:02 +000078 public Map<Integer, BranchExp> result() {
79 return lineToBranchExp;
80 }
81
82 // Intermediate results
83 //
84 // These values are built up during the visitor methods. They will be used to compute
85 // the final results.
86 private List<Instruction> instructions = new ArrayList<Instruction>();
87 private List<Jump> jumps = new ArrayList<>();
88 private Map<Integer, Instruction> probeToInsn = new TreeMap<>();
89
90 // A map which associates intructions with their coverage expressions.
91 private final Map<Instruction, CovExp> insnToCovExp = new HashMap();
92
93 // A map which associates a instruction to the branch index in its predecessor
94 // e.g., the instruction that follows a conditional jump instruction must exists in
95 // this map.
96 private final Map<Instruction, Integer> insnToIdx = new HashMap();
97
98 // Local cache
99 //
100 // These are maps corresponding to data structures available in JaCoCo in other form.
101 // We use local data structure to avoid need to change the JaCoCo internal code.
102 private Map<Instruction, Instruction> predecessors = new HashMap<>();
103 private Map<Label, Instruction> labelToInsn = new HashMap<>();
104
Charles Mita065e2e82021-09-07 06:38:32 -0700105 public MethodProbesMapper(IFilterContext filterContext, IFilter filter) {
106 this.filterContext = filterContext;
107 this.filter = filter;
108 }
109
110 @Override
111 public void accept(MethodNode methodNode, MethodVisitor methodVisitor) {
112 methodVisitor.visitCode();
113 for (AbstractInsnNode i : methodNode.instructions) {
114 currentInstructionNode = i;
115 i.accept(methodVisitor);
116 }
117 filter.filter(methodNode, filterContext, this);
118 methodVisitor.visitEnd();
119 }
120
Yue Ganaf3c4122016-12-05 14:36:02 +0000121 /** Visitor method to append a new Instruction */
122 private void visitInsn() {
123 Instruction instruction = new Instruction(currentLine);
124 instructions.add(instruction);
125 if (lastInstruction != null) {
iirinaff1f7452019-05-20 09:02:49 -0700126 lastInstruction.addBranch(instruction, 0); // the first branch from last instruction
Yue Ganaf3c4122016-12-05 14:36:02 +0000127 predecessors.put(instruction, lastInstruction); // Update local cache
128 }
129
130 for (Label label : currentLabels) {
131 labelToInsn.put(label, instruction);
132 }
133 currentLabels.clear(); // Update states
134 lastInstruction = instruction;
Charles Mita065e2e82021-09-07 06:38:32 -0700135 instructionMap.put(currentInstructionNode, instruction);
Yue Ganaf3c4122016-12-05 14:36:02 +0000136 }
137
138 // Plain visitors: called from adapter when no probe is needed
139 @Override
140 public void visitInsn(int opcode) {
141 visitInsn();
142 }
143
144 @Override
145 public void visitIntInsn(int opcode, int operand) {
146 visitInsn();
147 }
148
149 @Override
150 public void visitVarInsn(int opcode, int variable) {
151 visitInsn();
152 }
153
154 @Override
155 public void visitTypeInsn(int opcode, String type) {
156 visitInsn();
157 }
158
159 @Override
160 public void visitFieldInsn(int opcode, String owner, String name, String desc) {
161 visitInsn();
162 }
163
164 @Override
165 public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
166 visitInsn();
167 }
168
169 @Override
170 public void visitInvokeDynamicInsn(String name, String desc, Handle handle, Object... args) {
171 visitInsn();
172 }
173
174 @Override
175 public void visitLdcInsn(Object cst) {
176 visitInsn();
177 }
178
179 @Override
180 public void visitIincInsn(int var, int inc) {
181 visitInsn();
182 }
183
184 @Override
185 public void visitMultiANewArrayInsn(String desc, int dims) {
186 visitInsn();
187 }
188
iirinaff1f7452019-05-20 09:02:49 -0700189
Yue Ganaf3c4122016-12-05 14:36:02 +0000190 // Methods that need to update the states
191 @Override
192 public void visitJumpInsn(int opcode, Label label) {
193 visitInsn();
iirinaff1f7452019-05-20 09:02:49 -0700194 jumps.add(new Jump(lastInstruction, label, 1));
Yue Ganaf3c4122016-12-05 14:36:02 +0000195 }
196
197 @Override
198 public void visitLabel(Label label) {
199 currentLabels.add(label);
200 if (!LabelInfo.isSuccessor(label)) {
201 lastInstruction = null;
202 }
203 }
204
205 @Override
206 public void visitLineNumber(int line, Label start) {
207 currentLine = line;
208 }
209
210 /** Visit a switch instruction with no probes */
211 private void visitSwitchInsn(Label dflt, Label[] labels) {
212 visitInsn();
213
214 // Handle default transition
215 LabelInfo.resetDone(dflt);
iirinaff1f7452019-05-20 09:02:49 -0700216 int branch = 0;
217 jumps.add(new Jump(lastInstruction, dflt, branch));
Yue Ganaf3c4122016-12-05 14:36:02 +0000218 LabelInfo.setDone(dflt);
219
220 // Handle other transitions
221 LabelInfo.resetDone(labels);
222 for (Label label : labels) {
223 if (!LabelInfo.isDone(label)) {
iirinaff1f7452019-05-20 09:02:49 -0700224 jumps.add(new Jump(lastInstruction, label, branch));
Yue Ganaf3c4122016-12-05 14:36:02 +0000225 LabelInfo.setDone(label);
226 }
227 }
228 }
229
230 @Override
231 public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
232 visitSwitchInsn(dflt, labels);
233 }
234
235 @Override
236 public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
237 visitSwitchInsn(dflt, labels);
238 }
239
240 private void addProbe(int probeId) {
241 // We do not add probes to the flow graph, but we need to update
242 // the branch count of the predecessor of the probe
iirinaff1f7452019-05-20 09:02:49 -0700243 lastInstruction.addBranch(false, 0);
Yue Ganaf3c4122016-12-05 14:36:02 +0000244 probeToInsn.put(probeId, lastInstruction);
245 }
246
247 // Probe visit methods
248 @Override
249 public void visitProbe(int probeId) {
250 // This function is only called when visiting a merge node which
251 // is a successor.
252 // It adds an probe point to the last instruction
253 assert (lastInstruction != null);
254
255 addProbe(probeId);
256 lastInstruction = null; // Merge point should have no predecessor.
257 }
258
259 @Override
260 public void visitJumpInsnWithProbe(int opcode, Label label, int probeId, IFrame frame) {
261 visitInsn();
262 addProbe(probeId);
263 }
264
265 @Override
266 public void visitInsnWithProbe(int opcode, int probeId) {
267 visitInsn();
268 addProbe(probeId);
269 }
270
271 @Override
272 public void visitTableSwitchInsnWithProbes(
273 int min, int max, Label dflt, Label[] labels, IFrame frame) {
274 visitSwitchInsnWithProbes(dflt, labels);
275 }
276
277 @Override
278 public void visitLookupSwitchInsnWithProbes(
279 Label dflt, int[] keys, Label[] labels, IFrame frame) {
280 visitSwitchInsnWithProbes(dflt, labels);
281 }
282
283 private void visitSwitchInsnWithProbes(Label dflt, Label[] labels) {
284 visitInsn();
285 LabelInfo.resetDone(dflt);
286 LabelInfo.resetDone(labels);
iirinaff1f7452019-05-20 09:02:49 -0700287 int branch = 0;
288 visitTargetWithProbe(dflt, branch);
Yue Ganaf3c4122016-12-05 14:36:02 +0000289 for (Label l : labels) {
iirinaff1f7452019-05-20 09:02:49 -0700290 visitTargetWithProbe(l, branch);
Yue Ganaf3c4122016-12-05 14:36:02 +0000291 }
292 }
293
iirinaff1f7452019-05-20 09:02:49 -0700294 private void visitTargetWithProbe(Label label, int branch) {
Yue Ganaf3c4122016-12-05 14:36:02 +0000295 if (!LabelInfo.isDone(label)) {
296 int id = LabelInfo.getProbeId(label);
297 if (id == LabelInfo.NO_PROBE) {
iirinaff1f7452019-05-20 09:02:49 -0700298 jumps.add(new Jump(lastInstruction, label, branch));
Yue Ganaf3c4122016-12-05 14:36:02 +0000299 } else {
300 // Note, in this case the instrumenter should insert intermediate labels
301 // for the probes. These probes will be added for the switch instruction.
302 //
303 // There is no direct jump between lastInstruction and the label either.
304 addProbe(id);
305 }
306 LabelInfo.setDone(label);
307 }
308 }
309
310 // If a CovExp of pred is ProbeExp, create a single-branch BranchExp and put it in the map.
311 // Also update the index of insn.
iirinaff1f7452019-05-20 09:02:49 -0700312 private BranchExp getPredBranchExp(Instruction predecessor) {
Yue Ganaf3c4122016-12-05 14:36:02 +0000313 BranchExp result = null;
314 CovExp exp = insnToCovExp.get(predecessor);
315 if (exp instanceof ProbeExp) {
316 result = new BranchExp(exp); // Change ProbeExp to BranchExp
317 insnToCovExp.put(predecessor, result);
318 // This can only happen if the internal data of Jacoco is inconsistent:
319 // the instruction is the predecessor of more than one instructions,
320 // but its branch count is not > 1.
321 } else {
322 result = (BranchExp) exp;
323 }
324 return result;
325 }
326
327 // Update a branch predecessor and returns whether the BranchExp of the predecessor is new.
328 private boolean updateBranchPredecessor(Instruction predecessor, Instruction insn, CovExp exp) {
329 CovExp predExp = insnToCovExp.get(predecessor);
330 if (predExp == null) {
331 BranchExp branchExp = new BranchExp(exp);
332 insnToCovExp.put(predecessor, branchExp);
333 insnToIdx.put(insn, 0); // current insn is the first branch
334 return true;
335 }
336
iirinaff1f7452019-05-20 09:02:49 -0700337 BranchExp branchExp = getPredBranchExp(predecessor);
Yue Ganaf3c4122016-12-05 14:36:02 +0000338 Integer branchIdx = insnToIdx.get(insn);
339 if (branchIdx == null) {
340 // Keep track of the instructions in the branches that are already added
341 branchIdx = branchExp.add(exp);
342 insnToIdx.put(insn, branchIdx);
343 }
344 // If the branch where the instruction is on is already added, no need to do anything as
345 // branchExp has a reference to exp already.
346 return false;
347 }
348
349 /** Finishing the method */
350 @Override
351 public void visitEnd() {
Charles Mita065e2e82021-09-07 06:38:32 -0700352 HashSet<Instruction> ignoredInstructions = new HashSet<>();
353 for (Map.Entry<AbstractInsnNode, Instruction> entry : instructionMap.entrySet()) {
354 if (ignored.contains(entry.getKey())) {
355 ignoredInstructions.add(entry.getValue());
356 }
357 }
Yue Ganaf3c4122016-12-05 14:36:02 +0000358
359 for (Jump jump : jumps) {
360 Instruction insn = labelToInsn.get(jump.target);
iirinaff1f7452019-05-20 09:02:49 -0700361 jump.source.addBranch(insn, jump.branch);
Yue Ganaf3c4122016-12-05 14:36:02 +0000362 predecessors.put(insn, jump.source);
363 }
364
365 // Compute CovExp for every instruction.
366 for (Map.Entry<Integer, Instruction> entry : probeToInsn.entrySet()) {
367 int probeId = entry.getKey();
368 Instruction ins = entry.getValue();
Charles Mita065e2e82021-09-07 06:38:32 -0700369 if (ignoredInstructions.contains(ins)) {
370 continue;
371 }
Yue Ganaf3c4122016-12-05 14:36:02 +0000372
373 Instruction insn = ins;
374 CovExp exp = new ProbeExp(probeId);
375
376 // Compute CovExp for the probed instruction.
377 CovExp existingExp = insnToCovExp.get(insn);
378 if (existingExp != null) {
379 // The instruction already has a branch, add the probeExp as
380 // a new branch.
381 if (existingExp instanceof BranchExp) {
382 BranchExp branchExp = (BranchExp) existingExp;
383 branchExp.add(exp);
384 } else {
385 // This can only happen if the internal data is inconsistent.
386 // The instruction is a predecessor of another instruction and also
387 // has a probe, but the branch count is not > 1.
388 }
389 } else {
iirinaff1f7452019-05-20 09:02:49 -0700390 if (insn.getBranchCounter().getTotalCount() > 1) {
Yue Ganaf3c4122016-12-05 14:36:02 +0000391 exp = new BranchExp(exp);
392 }
393 insnToCovExp.put(insn, exp);
394 }
395
396 Instruction predecessor = predecessors.get(insn);
397 while (predecessor != null) {
iirinaff1f7452019-05-20 09:02:49 -0700398 if (predecessor.getBranchCounter().getTotalCount() > 1) {
Yue Ganaf3c4122016-12-05 14:36:02 +0000399 boolean isNewBranch = updateBranchPredecessor(predecessor, insn, exp);
400 if (!isNewBranch) {
401 // If the branch already exists, no need to visit predecessors any more.
402 break;
403 }
404 } else {
405 // No branch at predecessor, use the same CovExp
406 insnToCovExp.put(predecessor, exp);
407 }
408 insn = predecessor;
409 exp = insnToCovExp.get(predecessor);
410 predecessor = predecessors.get(insn);
411 }
412 }
413
414 // Merge branches in the instructions on the same line
415 for (Instruction insn : instructions) {
Charles Mita065e2e82021-09-07 06:38:32 -0700416 if (ignoredInstructions.contains(insn)) {
417 continue;
418 }
iirinaff1f7452019-05-20 09:02:49 -0700419 if (insn.getBranchCounter().getTotalCount() > 1) {
Yue Ganaf3c4122016-12-05 14:36:02 +0000420 CovExp insnExp = insnToCovExp.get(insn);
421 if (insnExp != null && (insnExp instanceof BranchExp)) {
422 BranchExp exp = (BranchExp) insnExp;
423 BranchExp lineExp = lineToBranchExp.get(insn.getLine());
424 if (lineExp == null) {
425 lineToBranchExp.put(insn.getLine(), exp);
426 } else {
Charles Mitacd500e72021-09-10 00:13:30 -0700427 lineToBranchExp.put(insn.getLine(), lineExp.merge(exp));
Yue Ganaf3c4122016-12-05 14:36:02 +0000428 }
429 } else {
430 // If we reach here, the internal data of the mapping is inconsistent, either
431 // 1) An instruction has branches but we do not create BranchExp for it.
432 // 2) An instruction has branches but it does not have an associated CovExp.
433 }
434 }
435 }
436 }
437
Charles Mita065e2e82021-09-07 06:38:32 -0700438 /** IFilterOutput */
439 // Handle only ignore for now; most filters only use this.
440 @Override
441 public void ignore(AbstractInsnNode fromInclusive, AbstractInsnNode toInclusive) {
442 for (AbstractInsnNode n = fromInclusive; n != toInclusive; n = n.getNext()) {
443 ignored.add(n);
444 }
445 ignored.add(toInclusive);
446 }
447
448 @Override
449 public void merge(AbstractInsnNode i1, AbstractInsnNode i2) {
450 // TODO(cmita): Implement as part of https://github.com/bazelbuild/bazel/issues/12696
451 }
452
453 @Override
454 public void replaceBranches(AbstractInsnNode source, Set<AbstractInsnNode> newTargets) {
455 // TODO(cmita): Implement as part of https://github.com/bazelbuild/bazel/issues/12696
456 }
457
Yue Ganaf3c4122016-12-05 14:36:02 +0000458 /** Jumps between instructions and labels */
459 class Jump {
460 public final Instruction source;
461 public final Label target;
iirinaff1f7452019-05-20 09:02:49 -0700462 public final int branch;
Yue Ganaf3c4122016-12-05 14:36:02 +0000463
iirinaff1f7452019-05-20 09:02:49 -0700464 public Jump(Instruction i, Label l, int b) {
Yue Ganaf3c4122016-12-05 14:36:02 +0000465 source = i;
466 target = l;
iirinaff1f7452019-05-20 09:02:49 -0700467 branch = b;
Yue Ganaf3c4122016-12-05 14:36:02 +0000468 }
469 }
470}