aboutsummaryrefslogtreecommitdiff
path: root/org.jacoco.core/src/org/jacoco/core/internal/analysis/InstructionsBuilder.java
blob: b22d872a591896f8bee7a5a582ba4fafb125755a (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
/*******************************************************************************
 * Copyright (c) 2009, 2019 Mountainminds GmbH & Co. KG and Contributors
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Marc R. Hoffmann - initial API and implementation
 *    
 *******************************************************************************/
package org.jacoco.core.internal.analysis;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jacoco.core.analysis.ISourceNode;
import org.jacoco.core.internal.flow.LabelInfo;
import org.objectweb.asm.Label;
import org.objectweb.asm.tree.AbstractInsnNode;

/**
 * Stateful builder for the {@link Instruction}s of a method. All instructions
 * of a method must be added in their original sequence along with additional
 * information like line numbers. Afterwards the instructions can be obtained
 * with the <code>getInstructions()</code> method.
 */
class InstructionsBuilder {

	/** Probe array of the class the analyzed method belongs to. */
	private final boolean[] probes;

	/** The line which belong to subsequently added instructions. */
	private int currentLine;

	/** The last instruction which has been added. */
	private Instruction currentInsn;

	/**
	 * All instructions of a method mapped from the ASM node to the
	 * corresponding {@link Instruction} instance.
	 */
	private final Map<AbstractInsnNode, Instruction> instructions;

	/**
	 * The labels which mark the subsequent instructions.
	 * 
	 * Due to ASM issue #315745 there can be more than one label per instruction
	 */
	private final List<Label> currentLabel;

	/**
	 * List of all jumps within the control flow. We need to store jumps
	 * temporarily as the target {@link Instruction} may not been known yet.
	 */
	private final List<Jump> jumps;

	/**
	 * Creates a new builder instance which can be used to analyze a single
	 * method.
	 * 
	 * @param probes
	 *            probe array of the corresponding class used to determine the
	 *            coverage status of every instruction.
	 */
	InstructionsBuilder(final boolean[] probes) {
		this.probes = probes;
		this.currentLine = ISourceNode.UNKNOWN_LINE;
		this.currentInsn = null;
		this.instructions = new HashMap<AbstractInsnNode, Instruction>();
		this.currentLabel = new ArrayList<Label>(2);
		this.jumps = new ArrayList<Jump>();
	}

	/**
	 * Sets the current source line. All subsequently added instructions will be
	 * assigned to this line. If no line is set (e.g. for classes compiled
	 * without debug information) {@link ISourceNode#UNKNOWN_LINE} is assigned
	 * to the instructions.
	 */
	void setCurrentLine(final int line) {
		currentLine = line;
	}

	/**
	 * Adds a label which applies to the subsequently added instruction. Due to
	 * ASM internals multiple {@link Label}s can be added to an instruction.
	 */
	void addLabel(final Label label) {
		currentLabel.add(label);
		if (!LabelInfo.isSuccessor(label)) {
			noSuccessor();
		}
	}

	/**
	 * Adds a new instruction. Instructions are by default linked with the
	 * previous instruction unless specified otherwise.
	 */
	void addInstruction(final AbstractInsnNode node) {
		final Instruction insn = new Instruction(currentLine);
		final int labelCount = currentLabel.size();
		if (labelCount > 0) {
			for (int i = labelCount; --i >= 0;) {
				LabelInfo.setInstruction(currentLabel.get(i), insn);
			}
			currentLabel.clear();
		}
		if (currentInsn != null) {
			currentInsn.addBranch(insn, 0);
		}
		currentInsn = insn;
		instructions.put(node, insn);
	}

	/**
	 * Declares that the next instruction will not be a successor of the current
	 * instruction. This is the case with an unconditional jump or technically
	 * when a probe was inserted before.
	 */
	void noSuccessor() {
		currentInsn = null;
	}

	/**
	 * Adds a jump from the last added instruction.
	 * 
	 * @param target
	 *            jump target
	 * @param branch
	 *            unique branch number
	 */
	void addJump(final Label target, final int branch) {
		jumps.add(new Jump(currentInsn, target, branch));
	}

	/**
	 * Adds a new probe for the last instruction.
	 * 
	 * @param probeId
	 *            index in the probe array
	 * @param branch
	 *            unique branch number for the last instruction
	 */
	void addProbe(final int probeId, final int branch) {
		final boolean executed = probes != null && probes[probeId];
		currentInsn.addBranch(executed, branch);
	}

	/**
	 * Returns the status for all instructions of this method. This method must
	 * be called exactly once after the instructions have been added.
	 * 
	 * @return map of ASM instruction nodes to corresponding {@link Instruction}
	 *         instances
	 */
	Map<AbstractInsnNode, Instruction> getInstructions() {
		// Wire jumps:
		for (final Jump j : jumps) {
			j.wire();
		}

		return instructions;
	}

	private static class Jump {

		private final Instruction source;
		private final Label target;
		private final int branch;

		Jump(final Instruction source, final Label target, final int branch) {
			this.source = source;
			this.target = target;
			this.branch = branch;
		}

		void wire() {
			source.addBranch(LabelInfo.getInstruction(target), branch);
		}

	}

}