From 4741fb65cbebd799fece1c36ebc131cb0945a159 Mon Sep 17 00:00:00 2001 From: Evgeny Mandrikov Date: Fri, 17 Aug 2018 21:37:09 +0200 Subject: Add filter for bytecode that ECJ generates for String in switch (#735) --- .../core/internal/analysis/MethodAnalyzer.java | 28 +++++- .../core/internal/analysis/filter/Filters.java | 7 +- .../internal/analysis/filter/IFilterOutput.java | 14 +++ .../analysis/filter/StringSwitchEcjFilter.java | 108 +++++++++++++++++++++ 4 files changed, 152 insertions(+), 5 deletions(-) create mode 100644 org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/StringSwitchEcjFilter.java (limited to 'org.jacoco.core/src') diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/MethodAnalyzer.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/MethodAnalyzer.java index ba862adc..82b97466 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/MethodAnalyzer.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/MethodAnalyzer.java @@ -173,6 +173,13 @@ public class MethodAnalyzer extends MethodProbesVisitor } } + private final Map> replacements = new HashMap>(); + + public void replaceBranches(final AbstractInsnNode source, + final Set newTargets) { + replacements.put(source, newTargets); + } + @Override public void visitLabel(final Label label) { currentLabel.add(label); @@ -362,6 +369,7 @@ public class MethodAnalyzer extends MethodProbesVisitor for (final CoveredProbe p : coveredProbes) { p.instruction.setCovered(p.branch); } + // Merge: for (final Instruction i : instructions) { final AbstractInsnNode m = i.getNode(); @@ -371,6 +379,7 @@ public class MethodAnalyzer extends MethodProbesVisitor nodeToInstruction.get(r).merge(i); } } + // Report result: coverage.ensureCapacity(firstLine, lastLine); for (final Instruction i : instructions) { @@ -378,8 +387,23 @@ public class MethodAnalyzer extends MethodProbesVisitor continue; } - final int total = i.getBranches(); - final int covered = i.getCoveredBranches(); + final int total; + final int covered; + final Set r = replacements.get(i.getNode()); + if (r != null) { + int cb = 0; + for (AbstractInsnNode b : r) { + if (nodeToInstruction.get(b).getCoveredBranches() > 0) { + cb++; + } + } + total = r.size(); + covered = cb; + } else { + total = i.getBranches(); + covered = i.getCoveredBranches(); + } + final ICounter instrCounter = covered == 0 ? CounterImpl.COUNTER_1_0 : CounterImpl.COUNTER_0_1; final ICounter branchCounter = total > 1 diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/Filters.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/Filters.java index b235cfb8..bdb0854b 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/Filters.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/Filters.java @@ -31,9 +31,10 @@ public final class Filters implements IFilter { new TryWithResourcesJavac11Filter(), new TryWithResourcesJavacFilter(), new TryWithResourcesEcjFilter(), new FinallyFilter(), new PrivateEmptyNoArgConstructorFilter(), - new StringSwitchJavacFilter(), new EnumEmptyConstructorFilter(), - new AnnotationGeneratedFilter(), new KotlinGeneratedFilter(), - new KotlinLateinitFilter(), new KotlinWhenSealedFilter()); + new StringSwitchJavacFilter(), new StringSwitchEcjFilter(), + new EnumEmptyConstructorFilter(), new AnnotationGeneratedFilter(), + new KotlinGeneratedFilter(), new KotlinLateinitFilter(), + new KotlinWhenSealedFilter()); private final IFilter[] filters; diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/IFilterOutput.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/IFilterOutput.java index 4ca9b814..bbcbf00e 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/IFilterOutput.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/IFilterOutput.java @@ -11,6 +11,8 @@ *******************************************************************************/ package org.jacoco.core.internal.analysis.filter; +import java.util.Set; + import org.objectweb.asm.tree.AbstractInsnNode; /** @@ -41,4 +43,16 @@ public interface IFilterOutput { */ void merge(AbstractInsnNode i1, AbstractInsnNode i2); + /** + * Marks instruction whose outgoing branches should be replaced during + * computation of coverage. + * + * @param source + * instruction which branches should be replaced + * @param newTargets + * new targets of branches + */ + void replaceBranches(AbstractInsnNode source, + Set newTargets); + } diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/StringSwitchEcjFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/StringSwitchEcjFilter.java new file mode 100644 index 00000000..cbd2a216 --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/StringSwitchEcjFilter.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2009, 2018 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import java.util.HashSet; +import java.util.Set; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LookupSwitchInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TableSwitchInsnNode; + +/** + * Filters code that is generated by ECJ for a switch statement + * with a String. + */ +public final class StringSwitchEcjFilter implements IFilter { + + public void filter(final MethodNode methodNode, + final IFilterContext context, final IFilterOutput output) { + final Matcher matcher = new Matcher(); + for (AbstractInsnNode i = methodNode.instructions + .getFirst(); i != null; i = i.getNext()) { + matcher.match(i, output); + } + } + + private static class Matcher extends AbstractMatcher { + public void match(final AbstractInsnNode start, + final IFilterOutput output) { + + cursor = start; + + nextIsVar(Opcodes.ASTORE, "s"); + nextIsInvokeVirtual("java/lang/String", "hashCode"); + nextIsSwitch(); + if (cursor == null) { + return; + } + + final AbstractInsnNode s = cursor; + final int hashCodes; + final LabelNode defaultLabel; + if (s.getOpcode() == Opcodes.LOOKUPSWITCH) { + final LookupSwitchInsnNode lookupSwitch = (LookupSwitchInsnNode) cursor; + defaultLabel = lookupSwitch.dflt; + hashCodes = lookupSwitch.labels.size(); + } else { + final TableSwitchInsnNode tableSwitch = (TableSwitchInsnNode) cursor; + defaultLabel = tableSwitch.dflt; + hashCodes = tableSwitch.labels.size(); + } + + final Set replacements = new HashSet(); + replacements.add(instructionAfterLabel(defaultLabel)); + + for (int i = 0; i < hashCodes; i++) { + while (true) { + nextIsVar(Opcodes.ALOAD, "s"); + nextIs(Opcodes.LDC); + nextIsInvokeVirtual("java/lang/String", "equals"); + // jump to case + nextIs(Opcodes.IFNE); + if (cursor == null) { + return; + } + + replacements.add(instructionAfterLabel( + ((JumpInsnNode) cursor).label)); + + if (cursor.getNext().getOpcode() == Opcodes.GOTO) { + // end of comparisons for same hashCode + // jump to default + nextIs(Opcodes.GOTO); + break; + } + } + } + + output.ignore(s.getNext(), cursor); + output.replaceBranches(s, replacements); + } + } + + private static AbstractInsnNode instructionAfterLabel( + final LabelNode label) { + AbstractInsnNode i = label.getNext(); + while (i.getType() == AbstractInsnNode.FRAME + || i.getType() == AbstractInsnNode.LABEL + || i.getType() == AbstractInsnNode.LINE) { + i = i.getNext(); + } + return i; + } + +} -- cgit v1.2.3