/******************************************************************************* * Copyright (c) 2009, 2021 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: * Evgeny Mandrikov - initial API and implementation * *******************************************************************************/ package org.jacoco.core.internal.analysis; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.jacoco.core.analysis.ISourceNode; import org.jacoco.core.internal.analysis.filter.IFilterOutput; import org.objectweb.asm.tree.AbstractInsnNode; /** * Calculates the filtered coverage of a single method. A instance of this class * can be first used as {@link IFilterOutput} before the coverage result is * calculated. */ class MethodCoverageCalculator implements IFilterOutput { private final Map instructions; private final Set ignored; /** * Instructions that should be merged form disjoint sets. Coverage * information from instructions of one set will be merged into * representative instruction of set. * * Each such set is represented as a singly linked list: each element except * one references another element from the same set, element without * reference - is a representative of this set. * * This map stores reference (value) for elements of sets (key). */ private final Map merged; private final Map> replacements; MethodCoverageCalculator( final Map instructions) { this.instructions = instructions; this.ignored = new HashSet(); this.merged = new HashMap(); this.replacements = new HashMap>(); } /** * Applies all specified filtering commands and calculates the resulting * coverage. * * @param coverage * the result is added to this coverage node */ void calculate(final MethodCoverageImpl coverage) { applyMerges(); applyReplacements(); ensureCapacity(coverage); for (final Entry entry : instructions .entrySet()) { if (!ignored.contains(entry.getKey())) { final Instruction instruction = entry.getValue(); coverage.increment(instruction.getInstructionCounter(), instruction.getBranchCounter(), instruction.getLine()); } } coverage.incrementMethodCounter(); } private void applyMerges() { // Merge to the representative: for (final Entry entry : merged .entrySet()) { final AbstractInsnNode node = entry.getKey(); final Instruction instruction = instructions.get(node); final AbstractInsnNode representativeNode = findRepresentative( node); ignored.add(node); instructions.put(representativeNode, instructions.get(representativeNode).merge(instruction)); entry.setValue(representativeNode); } // Get merged value back from representative for (final Entry entry : merged .entrySet()) { instructions.put(entry.getKey(), instructions.get(entry.getValue())); } } private void applyReplacements() { for (final Entry> entry : replacements .entrySet()) { final Set replacements = entry.getValue(); final List newBranches = new ArrayList( replacements.size()); for (final AbstractInsnNode b : replacements) { newBranches.add(instructions.get(b)); } final AbstractInsnNode node = entry.getKey(); instructions.put(node, instructions.get(node).replaceBranches(newBranches)); } } private void ensureCapacity(final MethodCoverageImpl coverage) { // Determine line range: int firstLine = ISourceNode.UNKNOWN_LINE; int lastLine = ISourceNode.UNKNOWN_LINE; for (final Entry entry : instructions .entrySet()) { if (!ignored.contains(entry.getKey())) { final int line = entry.getValue().getLine(); if (line != ISourceNode.UNKNOWN_LINE) { if (firstLine > line || lastLine == ISourceNode.UNKNOWN_LINE) { firstLine = line; } if (lastLine < line) { lastLine = line; } } } } // Performance optimization to avoid incremental increase of line array: coverage.ensureCapacity(firstLine, lastLine); } private AbstractInsnNode findRepresentative(AbstractInsnNode i) { AbstractInsnNode r; while ((r = merged.get(i)) != null) { i = r; } return i; } // === IFilterOutput API === public void ignore(final AbstractInsnNode fromInclusive, final AbstractInsnNode toInclusive) { for (AbstractInsnNode i = fromInclusive; i != toInclusive; i = i .getNext()) { ignored.add(i); } ignored.add(toInclusive); } public void merge(AbstractInsnNode i1, AbstractInsnNode i2) { i1 = findRepresentative(i1); i2 = findRepresentative(i2); if (i1 != i2) { merged.put(i2, i1); } } public void replaceBranches(final AbstractInsnNode source, final Set newTargets) { replacements.put(source, newTargets); } }