InstructionsBuilder.java

/*******************************************************************************
 * Copyright (c) 2009, 2024 Mountainminds GmbH & Co. KG and Contributors
 * This program and the accompanying materials are made available under
 * the terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * 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);
		}

	}

}