aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/java_tools/junitrunner/java/com/google/testing/coverage/MethodProbesMapper.java
blob: 6900a37f71e416bca66f86dee27739d305f3e0b5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
// Copyright 2016 The Bazel Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.testing.coverage;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.jacoco.core.internal.flow.IFrame;
import org.jacoco.core.internal.flow.Instruction;
import org.jacoco.core.internal.flow.LabelInfo;
import org.jacoco.core.internal.flow.MethodProbesVisitor;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;

/**
 * The mapper is a probes visitor that will cache control flow information as well as keeping track
 * of the probes as the main driver generates the probe ids. Upon finishing the method it uses the
 * information collected to generate the mapping information between probes and the instructions.
 */
public class MethodProbesMapper extends MethodProbesVisitor {
  /*
   * The implementation roughly follows the same pattern of the Analyzer class of Jacoco.
   *
   * The mapper has a few states:
   *
   * - lineMappings: a mapping between line number and labels
   *
   * - a sequence of "instructions", where each instruction has one or more predecessors. The
   * predecessor field has a sole purpose of propagating probe id. The 'merge' nodes in the CFG has
   * no predecessors, since the branch stops at theses points.
   *
   * - The instructions each has states that keep track of the probes that are associated with the
   * instruction.
   *
   * Initially the probe ids are assigned to the instructions that immediately precede the probe. At
   * the end of visiting the methods, the probe ids are propagated through the predecessor chains.
   */

  // States
  //
  // These are state variables that needs to be updated in the visitor methods.
  // The values usually changes as we traverse the byte code.
  private Instruction lastInstruction = null;
  private int currentLine = -1;
  private List<Label> currentLabels = new ArrayList<>();

  // Result
  private Map<Integer, BranchExp> lineToBranchExp = new TreeMap();

  public Map<Integer, BranchExp> result() {
    return lineToBranchExp;
  }

  // Intermediate results
  //
  // These values are built up during the visitor methods. They will be used to compute
  // the final results.
  private List<Instruction> instructions = new ArrayList<Instruction>();
  private List<Jump> jumps = new ArrayList<>();
  private Map<Integer, Instruction> probeToInsn = new TreeMap<>();

  // A map which associates intructions with their coverage expressions.
  private final Map<Instruction, CovExp> insnToCovExp = new HashMap();

  // A map which associates a instruction to the branch index in its predecessor
  // e.g., the instruction that follows a conditional jump instruction must exists in
  // this map.
  private final Map<Instruction, Integer> insnToIdx = new HashMap();

  // Local cache
  //
  // These are maps corresponding to data structures available in JaCoCo in other form.
  // We use local data structure to avoid need to change the JaCoCo internal code.
  private Map<Instruction, Instruction> predecessors = new HashMap<>();
  private Map<Label, Instruction> labelToInsn = new HashMap<>();

  /** Visitor method to append a new Instruction */
  private void visitInsn() {
    Instruction instruction = new Instruction(currentLine);
    instructions.add(instruction);
    if (lastInstruction != null) {
      instruction.setPredecessor(lastInstruction); // Update branch of lastInstruction
      predecessors.put(instruction, lastInstruction); // Update local cache
    }

    for (Label label : currentLabels) {
      labelToInsn.put(label, instruction);
    }
    currentLabels.clear(); // Update states
    lastInstruction = instruction;
  }

  // Plain visitors: called from adapter when no probe is needed
  @Override
  public void visitInsn(int opcode) {
    visitInsn();
  }

  @Override
  public void visitIntInsn(int opcode, int operand) {
    visitInsn();
  }

  @Override
  public void visitVarInsn(int opcode, int variable) {
    visitInsn();
  }

  @Override
  public void visitTypeInsn(int opcode, String type) {
    visitInsn();
  }

  @Override
  public void visitFieldInsn(int opcode, String owner, String name, String desc) {
    visitInsn();
  }

  @Override
  public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
    visitInsn();
  }

  @Override
  public void visitInvokeDynamicInsn(String name, String desc, Handle handle, Object... args) {
    visitInsn();
  }

  @Override
  public void visitLdcInsn(Object cst) {
    visitInsn();
  }

  @Override
  public void visitIincInsn(int var, int inc) {
    visitInsn();
  }

  @Override
  public void visitMultiANewArrayInsn(String desc, int dims) {
    visitInsn();
  }

  // Methods that need to update the states
  @Override
  public void visitJumpInsn(int opcode, Label label) {
    visitInsn();
    jumps.add(new Jump(lastInstruction, label));
  }

  @Override
  public void visitLabel(Label label) {
    currentLabels.add(label);
    if (!LabelInfo.isSuccessor(label)) {
      lastInstruction = null;
    }
  }

  @Override
  public void visitLineNumber(int line, Label start) {
    currentLine = line;
  }

  /** Visit a switch instruction with no probes */
  private void visitSwitchInsn(Label dflt, Label[] labels) {
    visitInsn();

    // Handle default transition
    LabelInfo.resetDone(dflt);
    jumps.add(new Jump(lastInstruction, dflt));
    LabelInfo.setDone(dflt);

    // Handle other transitions
    LabelInfo.resetDone(labels);
    for (Label label : labels) {
      if (!LabelInfo.isDone(label)) {
        jumps.add(new Jump(lastInstruction, label));
        LabelInfo.setDone(label);
      }
    }
  }

  @Override
  public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
    visitSwitchInsn(dflt, labels);
  }

  @Override
  public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
    visitSwitchInsn(dflt, labels);
  }

  private void addProbe(int probeId) {
    // We do not add probes to the flow graph, but we need to update
    // the branch count of the predecessor of the probe
    lastInstruction.addBranch();
    probeToInsn.put(probeId, lastInstruction);
  }

  // Probe visit methods
  @Override
  public void visitProbe(int probeId) {
    // This function is only called when visiting a merge node which
    // is a successor.
    // It adds an probe point to the last instruction
    assert (lastInstruction != null);

    addProbe(probeId);
    lastInstruction = null; // Merge point should have no predecessor.
  }

  @Override
  public void visitJumpInsnWithProbe(int opcode, Label label, int probeId, IFrame frame) {
    visitInsn();
    addProbe(probeId);
  }

  @Override
  public void visitInsnWithProbe(int opcode, int probeId) {
    visitInsn();
    addProbe(probeId);
  }

  @Override
  public void visitTableSwitchInsnWithProbes(
      int min, int max, Label dflt, Label[] labels, IFrame frame) {
    visitSwitchInsnWithProbes(dflt, labels);
  }

  @Override
  public void visitLookupSwitchInsnWithProbes(
      Label dflt, int[] keys, Label[] labels, IFrame frame) {
    visitSwitchInsnWithProbes(dflt, labels);
  }

  private void visitSwitchInsnWithProbes(Label dflt, Label[] labels) {
    visitInsn();
    LabelInfo.resetDone(dflt);
    LabelInfo.resetDone(labels);

    visitTargetWithProbe(dflt);
    for (Label l : labels) {
      visitTargetWithProbe(l);
    }
  }

  private void visitTargetWithProbe(Label label) {
    if (!LabelInfo.isDone(label)) {
      int id = LabelInfo.getProbeId(label);
      if (id == LabelInfo.NO_PROBE) {
        jumps.add(new Jump(lastInstruction, label));
      } else {
        // Note, in this case the instrumenter should insert intermediate labels
        // for the probes. These probes will be added for the switch instruction.
        //
        // There is no direct jump between lastInstruction and the label either.
        addProbe(id);
      }
      LabelInfo.setDone(label);
    }
  }

  // If a CovExp of pred is ProbeExp, create a single-branch BranchExp and put it in the map.
  // Also update the index of insn.
  private BranchExp getPredBranchExp(Instruction predecessor, Instruction insn) {
    BranchExp result = null;
    CovExp exp = insnToCovExp.get(predecessor);
    if (exp instanceof ProbeExp) {
      result = new BranchExp(exp); // Change ProbeExp to BranchExp
      insnToCovExp.put(predecessor, result);
      // This can only happen if the internal data of Jacoco is inconsistent:
      // the instruction is the predecessor of more than one instructions,
      // but its branch count is not > 1.
    } else {
      result = (BranchExp) exp;
    }
    return result;
  }

  // Update a branch predecessor and returns whether the BranchExp of the predecessor is new.
  private boolean updateBranchPredecessor(Instruction predecessor, Instruction insn, CovExp exp) {
    CovExp predExp = insnToCovExp.get(predecessor);
    if (predExp == null) {
      BranchExp branchExp = new BranchExp(exp);
      insnToCovExp.put(predecessor, branchExp);
      insnToIdx.put(insn, 0); // current insn is the first branch
      return true;
    }

    BranchExp branchExp = getPredBranchExp(predecessor, insn);
    Integer branchIdx = insnToIdx.get(insn);
    if (branchIdx == null) {
      // Keep track of the instructions in the branches that are already added
      branchIdx = branchExp.add(exp);
      insnToIdx.put(insn, branchIdx);
    }
    // If the branch where the instruction is on is already added, no need to do anything as
    // branchExp has a reference to exp already.
    return false;
  }

  /** Finishing the method */
  @Override
  public void visitEnd() {

    for (Jump jump : jumps) {
      Instruction insn = labelToInsn.get(jump.target);
      insn.setPredecessor(jump.source);
      predecessors.put(insn, jump.source);
    }

    // Compute CovExp for every instruction.
    for (Map.Entry<Integer, Instruction> entry : probeToInsn.entrySet()) {
      int probeId = entry.getKey();
      Instruction ins = entry.getValue();

      Instruction insn = ins;
      CovExp exp = new ProbeExp(probeId);

      // Compute CovExp for the probed instruction.
      CovExp existingExp = insnToCovExp.get(insn);
      if (existingExp != null) {
        // The instruction already has a branch, add the probeExp as
        // a new branch.
        if (existingExp instanceof BranchExp) {
          BranchExp branchExp = (BranchExp) existingExp;
          branchExp.add(exp);
        } else {
          // This can only happen if the internal data is inconsistent.
          // The instruction is a predecessor of another instruction and also
          // has a probe, but the branch count is not > 1.
        }
      } else {
        if (insn.getBranches() > 1) {
          exp = new BranchExp(exp);
        }
        insnToCovExp.put(insn, exp);
      }

      Instruction predecessor = predecessors.get(insn);
      while (predecessor != null) {
        if (predecessor.getBranches() > 1) {
          boolean isNewBranch = updateBranchPredecessor(predecessor, insn, exp);
          if (!isNewBranch) {
            // If the branch already exists, no need to visit predecessors any more.
            break;
          }
        } else {
          // No branch at predecessor, use the same CovExp
          insnToCovExp.put(predecessor, exp);
        }
        insn = predecessor;
        exp = insnToCovExp.get(predecessor);
        predecessor = predecessors.get(insn);
      }
    }

    // Merge branches in the instructions on the same line
    for (Instruction insn : instructions) {
      if (insn.getBranches() > 1) {
        CovExp insnExp = insnToCovExp.get(insn);
        if (insnExp != null && (insnExp instanceof BranchExp)) {
          BranchExp exp = (BranchExp) insnExp;
          BranchExp lineExp = lineToBranchExp.get(insn.getLine());
          if (lineExp == null) {
            lineToBranchExp.put(insn.getLine(), exp);
          } else {
            lineExp.merge(exp);
          }
        } else {
          // If we reach here, the internal data of the mapping is inconsistent, either
          // 1) An instruction has branches but we do not create BranchExp for it.
          // 2) An instruction has branches but it does not have an associated CovExp.
        }
      }
    }
  }

  /** Jumps between instructions and labels */
  class Jump {
    public final Instruction source;
    public final Label target;

    public Jump(Instruction i, Label l) {
      source = i;
      target = l;
    }
  }
}