blob: 6736acae1caeed87da30ed59781b5dba9aa57774 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2017 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.HashSet;
import java.util.List;
import java.util.Set;
import org.jacoco.core.analysis.ICounter;
import org.jacoco.core.analysis.IMethodCoverage;
import org.jacoco.core.analysis.ISourceNode;
import org.jacoco.core.internal.analysis.filter.IFilter;
import org.jacoco.core.internal.analysis.filter.IFilterOutput;
import org.jacoco.core.internal.analysis.filter.SynchronizedFilter;
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;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TryCatchBlockNode;
/**
* A {@link MethodProbesVisitor} that analyzes which statements and branches of
* a method have been executed based on given probe data.
*/
public class MethodAnalyzer extends MethodProbesVisitor
implements IFilterOutput {
private static final IFilter[] FILTERS = new IFilter[] {
new SynchronizedFilter() };
private final boolean[] probes;
private final MethodCoverageImpl coverage;
private int currentLine = ISourceNode.UNKNOWN_LINE;
private int firstLine = ISourceNode.UNKNOWN_LINE;
private int lastLine = ISourceNode.UNKNOWN_LINE;
// Due to ASM issue #315745 there can be more than one label per instruction
private final List<Label> currentLabel = new ArrayList<Label>(2);
/** List of all analyzed instructions */
private final List<Instruction> instructions = new ArrayList<Instruction>();
/** List of all predecessors of covered probes */
private final List<Instruction> coveredProbes = new ArrayList<Instruction>();
/** List of all jumps encountered */
private final List<Jump> jumps = new ArrayList<Jump>();
/** Last instruction in byte code sequence */
private Instruction lastInsn;
/**
* New Method analyzer for the given probe data.
*
* @param name
* method name
* @param desc
* method descriptor
* @param signature
* optional parameterized signature
*
* @param probes
* recorded probe date of the containing class or
* <code>null</code> if the class is not executed at all
*/
public MethodAnalyzer(final String name, final String desc,
final String signature, final boolean[] probes) {
super();
this.probes = probes;
this.coverage = new MethodCoverageImpl(name, desc, signature);
}
/**
* Returns the coverage data for this method after this visitor has been
* processed.
*
* @return coverage data for this method
*/
public IMethodCoverage getCoverage() {
return coverage;
}
/**
* {@link MethodNode#accept(MethodVisitor)}
*/
@Override
public void accept(final MethodNode methodNode,
final MethodVisitor methodVisitor) {
this.ignored.clear();
for (final IFilter filter : FILTERS) {
filter.filter(methodNode, this);
}
for (final TryCatchBlockNode n : methodNode.tryCatchBlocks) {
n.accept(methodVisitor);
}
currentNode = methodNode.instructions.getFirst();
while (currentNode != null) {
currentNode.accept(methodVisitor);
currentNode = currentNode.getNext();
}
methodVisitor.visitEnd();
}
private final Set<AbstractInsnNode> ignored = new HashSet<AbstractInsnNode>();
private AbstractInsnNode currentNode;
public void ignore(final AbstractInsnNode fromInclusive,
final AbstractInsnNode toInclusive) {
for (AbstractInsnNode i = fromInclusive; i != toInclusive; i = i
.getNext()) {
ignored.add(i);
}
ignored.add(toInclusive);
}
@Override
public void visitLabel(final Label label) {
currentLabel.add(label);
if (!LabelInfo.isSuccessor(label)) {
lastInsn = null;
}
}
@Override
public void visitLineNumber(final int line, final Label start) {
currentLine = line;
if (firstLine > line || lastLine == ISourceNode.UNKNOWN_LINE) {
firstLine = line;
}
if (lastLine < line) {
lastLine = line;
}
}
private void visitInsn() {
final Instruction insn = new Instruction(currentNode, currentLine);
instructions.add(insn);
if (lastInsn != null) {
insn.setPredecessor(lastInsn);
}
final int labelCount = currentLabel.size();
if (labelCount > 0) {
for (int i = labelCount; --i >= 0;) {
LabelInfo.setInstruction(currentLabel.get(i), insn);
}
currentLabel.clear();
}
lastInsn = insn;
}
@Override
public void visitInsn(final int opcode) {
visitInsn();
}
@Override
public void visitIntInsn(final int opcode, final int operand) {
visitInsn();
}
@Override
public void visitVarInsn(final int opcode, final int var) {
visitInsn();
}
@Override
public void visitTypeInsn(final int opcode, final String type) {
visitInsn();
}
@Override
public void visitFieldInsn(final int opcode, final String owner,
final String name, final String desc) {
visitInsn();
}
@Override
public void visitMethodInsn(final int opcode, final String owner,
final String name, final String desc, final boolean itf) {
visitInsn();
}
@Override
public void visitInvokeDynamicInsn(final String name, final String desc,
final Handle bsm, final Object... bsmArgs) {
visitInsn();
}
@Override
public void visitJumpInsn(final int opcode, final Label label) {
visitInsn();
jumps.add(new Jump(lastInsn, label));
}
@Override
public void visitLdcInsn(final Object cst) {
visitInsn();
}
@Override
public void visitIincInsn(final int var, final int increment) {
visitInsn();
}
@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) {
visitInsn();
LabelInfo.resetDone(labels);
jumps.add(new Jump(lastInsn, dflt));
LabelInfo.setDone(dflt);
for (final Label l : labels) {
if (!LabelInfo.isDone(l)) {
jumps.add(new Jump(lastInsn, l));
LabelInfo.setDone(l);
}
}
}
@Override
public void visitMultiANewArrayInsn(final String desc, final int dims) {
visitInsn();
}
@Override
public void visitProbe(final int probeId) {
addProbe(probeId);
lastInsn = null;
}
@Override
public void visitJumpInsnWithProbe(final int opcode, final Label label,
final int probeId, final IFrame frame) {
visitInsn();
addProbe(probeId);
}
@Override
public void visitInsnWithProbe(final int opcode, final int probeId) {
visitInsn();
addProbe(probeId);
}
@Override
public void visitTableSwitchInsnWithProbes(final int min, final int max,
final Label dflt, final Label[] labels, final IFrame frame) {
visitSwitchInsnWithProbes(dflt, labels);
}
@Override
public void visitLookupSwitchInsnWithProbes(final Label dflt,
final int[] keys, final Label[] labels, final IFrame frame) {
visitSwitchInsnWithProbes(dflt, labels);
}
private void visitSwitchInsnWithProbes(final Label dflt,
final Label[] labels) {
visitInsn();
LabelInfo.resetDone(dflt);
LabelInfo.resetDone(labels);
visitSwitchTarget(dflt);
for (final Label l : labels) {
visitSwitchTarget(l);
}
}
private void visitSwitchTarget(final Label label) {
final int id = LabelInfo.getProbeId(label);
if (!LabelInfo.isDone(label)) {
if (id == LabelInfo.NO_PROBE) {
jumps.add(new Jump(lastInsn, label));
} else {
addProbe(id);
}
LabelInfo.setDone(label);
}
}
@Override
public void visitEnd() {
// Wire jumps:
for (final Jump j : jumps) {
LabelInfo.getInstruction(j.target).setPredecessor(j.source);
}
// Propagate probe values:
for (final Instruction p : coveredProbes) {
p.setCovered();
}
// Report result:
coverage.ensureCapacity(firstLine, lastLine);
for (final Instruction i : instructions) {
if (ignored.contains(i.getNode())) {
continue;
}
final int total = i.getBranches();
final int covered = i.getCoveredBranches();
final ICounter instrCounter = covered == 0 ? CounterImpl.COUNTER_1_0
: CounterImpl.COUNTER_0_1;
final ICounter branchCounter = total > 1 ? CounterImpl.getInstance(
total - covered, covered) : CounterImpl.COUNTER_0_0;
coverage.increment(instrCounter, branchCounter, i.getLine());
}
coverage.incrementMethodCounter();
}
private void addProbe(final int probeId) {
lastInsn.addBranch();
if (probes != null && probes[probeId]) {
coveredProbes.add(lastInsn);
}
}
private static class Jump {
final Instruction source;
final Label target;
Jump(final Instruction source, final Label target) {
this.source = source;
this.target = target;
}
}
}