LabelFlowAnalyzer.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.flow;
import org.jacoco.core.internal.instr.InstrSupport;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.MethodNode;
/**
* Method visitor to collect flow related information about the {@link Label}s
* within a class. It calculates the properties "multitarget" and "successor"
* that can afterwards be obtained via {@link LabelInfo}.
*/
public final class LabelFlowAnalyzer extends MethodVisitor {
/**
* Marks all labels of the method with control flow information.
*
* @param method
* Method to mark labels
*/
public static void markLabels(final MethodNode method) {
// We do not use the accept() method as ASM resets labels after every
// call to accept()
final MethodVisitor lfa = new LabelFlowAnalyzer();
for (int i = method.tryCatchBlocks.size(); --i >= 0;) {
method.tryCatchBlocks.get(i).accept(lfa);
}
method.instructions.accept(lfa);
}
/**
* <code>true</code> if the current instruction is a potential successor of
* the previous instruction. Accessible for testing.
*/
boolean successor = false;
/**
* <code>true</code> for the very first instruction only. Accessible for
* testing.
*/
boolean first = true;
/**
* Label instance of the last line start.
*/
Label lineStart = null;
/**
* Create new instance.
*/
public LabelFlowAnalyzer() {
super(InstrSupport.ASM_API_VERSION);
}
@Override
public void visitTryCatchBlock(final Label start, final Label end,
final Label handler, final String type) {
// Enforce probe at the beginning of the block. Assuming the start of
// the block already is successor of some other code, adding a target
// makes the start a multitarget. However, if the start of the block
// also is the start of the method, no probe will be added.
LabelInfo.setTarget(start);
// Mark exception handler as possible target of the block
LabelInfo.setTarget(handler);
}
@Override
public void visitJumpInsn(final int opcode, final Label label) {
LabelInfo.setTarget(label);
if (opcode == Opcodes.JSR) {
throw new AssertionError("Subroutines not supported.");
}
successor = opcode != Opcodes.GOTO;
first = false;
}
@Override
public void visitLabel(final Label label) {
if (first) {
LabelInfo.setTarget(label);
}
if (successor) {
LabelInfo.setSuccessor(label);
}
}
@Override
public void visitLineNumber(final int line, final Label start) {
if (line == 0) {
// ASM versions prior to 9.5 were ignoring zero line numbers
// (https://gitlab.ow2.org/asm/asm/-/issues/317989)
// so we ignore them here to preserve exec file compatibility
return;
}
lineStart = start;
}
@Override
public void visitTableSwitchInsn(final int min, final int max,
final Label dflt, final Label... labels) {
visitSwitchInsn(dflt, labels);
}
@Override
public void visitLookupSwitchInsn(final Label dflt, final int[] keys,
final Label[] labels) {
visitSwitchInsn(dflt, labels);
}
private void visitSwitchInsn(final Label dflt, final Label[] labels) {
LabelInfo.resetDone(dflt);
LabelInfo.resetDone(labels);
setTargetIfNotDone(dflt);
for (final Label l : labels) {
setTargetIfNotDone(l);
}
successor = false;
first = false;
}
private static void setTargetIfNotDone(final Label label) {
if (!LabelInfo.isDone(label)) {
LabelInfo.setTarget(label);
LabelInfo.setDone(label);
}
}
@Override
public void visitInsn(final int opcode) {
switch (opcode) {
case Opcodes.IRETURN:
case Opcodes.LRETURN:
case Opcodes.FRETURN:
case Opcodes.DRETURN:
case Opcodes.ARETURN:
case Opcodes.RETURN:
case Opcodes.ATHROW:
successor = false;
break;
default:
successor = true;
break;
}
first = false;
}
@Override
public void visitIntInsn(final int opcode, final int operand) {
successor = true;
first = false;
}
@Override
public void visitVarInsn(final int opcode, final int var) {
if (Opcodes.RET == opcode) {
throw new AssertionError("Subroutines not supported.");
}
successor = true;
first = false;
}
@Override
public void visitTypeInsn(final int opcode, final String type) {
successor = true;
first = false;
}
@Override
public void visitFieldInsn(final int opcode, final String owner,
final String name, final String desc) {
successor = true;
first = false;
}
@Override
public void visitMethodInsn(final int opcode, final String owner,
final String name, final String desc, final boolean itf) {
successor = true;
first = false;
markMethodInvocationLine();
}
@Override
public void visitInvokeDynamicInsn(final String name, final String desc,
final Handle bsm, final Object... bsmArgs) {
successor = true;
first = false;
markMethodInvocationLine();
}
private void markMethodInvocationLine() {
if (lineStart != null) {
LabelInfo.setMethodInvocationLine(lineStart);
}
}
@Override
public void visitLdcInsn(final Object cst) {
successor = true;
first = false;
}
@Override
public void visitIincInsn(final int var, final int increment) {
successor = true;
first = false;
}
@Override
public void visitMultiANewArrayInsn(final String desc, final int dims) {
successor = true;
first = false;
}
}