diff options
9 files changed, 424 insertions, 6 deletions
diff --git a/org.jacoco.core.test/src-java7/org/jacoco/core/test/filter/StringSwitchTest.java b/org.jacoco.core.test/src-java7/org/jacoco/core/test/filter/StringSwitchTest.java new file mode 100644 index 00000000..93bc55be --- /dev/null +++ b/org.jacoco.core.test/src-java7/org/jacoco/core/test/filter/StringSwitchTest.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.test.filter; + +import org.jacoco.core.analysis.ICounter; +import org.jacoco.core.test.filter.targets.StringSwitch; +import org.jacoco.core.test.validation.ValidationTestBase; +import org.junit.Test; + +/** + * Test of filtering of a bytecode that is generated for a String in switch + * statement. + */ +public class StringSwitchTest extends ValidationTestBase { + + public StringSwitchTest() { + super("src-java7", StringSwitch.class); + } + + /** + * {@link StringSwitch#covered(String)} + */ + @Test + public void covered() { + if (isJDKCompiler) { + assertLine("covered.switch", ICounter.FULLY_COVERED, 0, 4); + } else { + assertLine("covered.switch", ICounter.PARTLY_COVERED, 2, 7); + } + assertLine("covered.case1", ICounter.FULLY_COVERED, 0, 0); + assertLine("covered.case2", ICounter.FULLY_COVERED, 0, 0); + assertLine("covered.case3", ICounter.FULLY_COVERED, 0, 0); + assertLine("covered.default", ICounter.FULLY_COVERED, 0, 0); + } + + /** + * {@link StringSwitch#notCovered(String)} + */ + @Test + public void notCovered() { + assertLine("notCovered", ICounter.NOT_COVERED, isJDKCompiler ? 4 : 9, + 0); + } + + /** + * {@link StringSwitch#handwritten(String)} + */ + @Test + public void handwritten() { + assertLine("handwritten.firstSwitch", ICounter.FULLY_COVERED, 2, 1); + assertLine("handwritten.ignored", ICounter.FULLY_COVERED); + assertLine("handwritten.secondSwitch", ICounter.FULLY_COVERED, 3, 1); + assertLine("handwritten.case1", ICounter.FULLY_COVERED); + assertLine("handwritten.case2", ICounter.NOT_COVERED); + } + +} diff --git a/org.jacoco.core.test/src-java7/org/jacoco/core/test/filter/targets/StringSwitch.java b/org.jacoco.core.test/src-java7/org/jacoco/core/test/filter/targets/StringSwitch.java new file mode 100644 index 00000000..623c80ec --- /dev/null +++ b/org.jacoco.core.test/src-java7/org/jacoco/core/test/filter/targets/StringSwitch.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.test.filter.targets; + +import static org.jacoco.core.test.validation.targets.Stubs.nop; + +/** + * This test target is a switch statement with a String. + */ +public class StringSwitch { + + private static void covered(String s) { + switch (s) { // $line-covered.switch$ + case "a": + nop("case a"); // $line-covered.case1$ + break; + case "b": + nop("case b"); // $line-covered.case2$ + break; + case "\0a": + nop("case \0a"); // $line-covered.case3$ + break; + default: + nop("case default"); // $line-covered.default$ + break; + } + } + + private static void notCovered(String s) { + switch (s) { // $line-notCovered$ + case "a": + nop("case a"); + break; + case "b": + nop("case b"); + break; + case "\0a": + nop("case \0a"); + break; + default: + nop("default"); + break; + } + } + + private static void handwritten(String s) { + int c = -1; + switch (s.hashCode()) { // $line-handwritten.firstSwitch$ + case 97: + if ("a".equals(s)) { // $line-handwritten.ignored$ + c = 0; + } else if ("\0a".equals(s)) { + c = 1; + } + break; + case 98: + if ("b".equals(s)) { + c = 2; + } + break; + } + switch (c) { // $line-handwritten.secondSwitch$ + case 0: + nop("case a"); // $line-handwritten.case1$ + break; + case 1: + nop("case \0a"); // $line-handwritten.case2$ + break; + case 2: + nop("case b"); + break; + default: + nop("default"); + break; + } + } + + public static void main(String[] args) { + covered(""); + covered("a"); + covered("b"); + covered("\0a"); + + handwritten("a"); + } + +} diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/StringSwitchJavacFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/StringSwitchJavacFilterTest.java new file mode 100644 index 00000000..5a7cdefb --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/StringSwitchJavacFilterTest.java @@ -0,0 +1,155 @@ +/******************************************************************************* + * 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import org.jacoco.core.internal.instr.InstrSupport; +import org.junit.Test; +import org.objectweb.asm.Label; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.MethodNode; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class StringSwitchJavacFilterTest implements IFilterOutput { + + private final IFilter filter = new StringSwitchJavacFilter(); + + private final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "name", "()V", null, null); + + private AbstractInsnNode fromInclusive; + private AbstractInsnNode toInclusive; + + @Test + public void should_filter_code_generated_by_javac() { + final Label h1 = new Label(); + final Label h1_2 = new Label(); + final Label h2 = new Label(); + final Label secondSwitch = new Label(); + final Label cases = new Label(); + + m.visitInsn(Opcodes.ICONST_M1); + m.visitVarInsn(Opcodes.ISTORE, 2); + + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "hashCode", + "()I", false); + m.visitLookupSwitchInsn(secondSwitch, new int[] { 97, 98 }, + new Label[] { h1, h2 }); + final AbstractInsnNode fromInclusive = m.instructions.getLast(); + + m.visitLabel(h1); + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitLdcInsn("a"); + m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", + "(Ljava/lang/Object;)Z", false); + // if not equal "a", then goto next comparison + m.visitJumpInsn(Opcodes.IFEQ, h1_2); + m.visitInsn(Opcodes.ICONST_0); + m.visitVarInsn(Opcodes.ISTORE, 2); + + // goto secondSwitch + m.visitJumpInsn(Opcodes.GOTO, secondSwitch); + + m.visitLabel(h1_2); + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitLdcInsn("\0a"); + m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", + "(Ljava/lang/Object;)Z", false); + // if not equal "\0a", then goto second switch + m.visitJumpInsn(Opcodes.IFEQ, secondSwitch); + m.visitInsn(Opcodes.ICONST_1); + m.visitVarInsn(Opcodes.ISTORE, 2); + + // goto secondSwitch + m.visitJumpInsn(Opcodes.GOTO, secondSwitch); + + m.visitLabel(h2); + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitLdcInsn("b"); + m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", + "(Ljava/lang/Object;)Z", false); + // if not equal "b", then goto second switch + m.visitJumpInsn(Opcodes.IFEQ, secondSwitch); + m.visitInsn(Opcodes.ICONST_2); + m.visitVarInsn(Opcodes.ISTORE, 2); + + m.visitLabel(secondSwitch); + final AbstractInsnNode toInclusive = m.instructions.getLast(); + m.visitVarInsn(Opcodes.ILOAD, 2); + m.visitTableSwitchInsn(0, 2, cases); + m.visitLabel(cases); + + filter.filter("Foo", "java/lang/Object", m, this); + + assertEquals(fromInclusive, this.fromInclusive); + assertEquals(toInclusive, this.toInclusive); + } + + @Test + public void should_not_filter_code_generated_by_ECJ() { + final Label h1 = new Label(); + final Label h2 = new Label(); + final Label cases = new Label(); + + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "hashCode", + "()I", false); + m.visitTableSwitchInsn(0, 2, cases, h1, h2); + + m.visitLabel(h1); + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitLdcInsn("a"); + m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", + "(Ljava/lang/Object;)Z", false); + // if equal "a", then goto its case + m.visitJumpInsn(Opcodes.IFNE, cases); + + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitLdcInsn("\0a"); + m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", + "(Ljava/lang/Object;)Z", false); + // if equal "\0a", then goto its case + m.visitJumpInsn(Opcodes.IFNE, cases); + + // goto default case + m.visitJumpInsn(Opcodes.GOTO, cases); + + m.visitLabel(h2); + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitLdcInsn("b"); + m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", + "(Ljava/lang/Object;)Z", false); + // if equal "b", then goto its case + m.visitJumpInsn(Opcodes.IFNE, cases); + + // goto default case + m.visitJumpInsn(Opcodes.GOTO, cases); + + m.visitLabel(cases); + + filter.filter("Foo", "java/lang/Object", m, this); + + assertNull(this.fromInclusive); + assertNull(this.toInclusive); + } + + public void ignore(final AbstractInsnNode fromInclusive, + final AbstractInsnNode toInclusive) { + assertNull(this.fromInclusive); + this.fromInclusive = fromInclusive; + this.toInclusive = toInclusive; + } + +} 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 8e07e56f..328d079a 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 @@ -24,6 +24,7 @@ import org.jacoco.core.internal.analysis.filter.IFilter; import org.jacoco.core.internal.analysis.filter.IFilterOutput; import org.jacoco.core.internal.analysis.filter.LombokGeneratedFilter; import org.jacoco.core.internal.analysis.filter.PrivateEmptyNoArgConstructorFilter; +import org.jacoco.core.internal.analysis.filter.StringSwitchJavacFilter; import org.jacoco.core.internal.analysis.filter.SynchronizedFilter; import org.jacoco.core.internal.analysis.filter.SyntheticFilter; import org.jacoco.core.internal.analysis.filter.TryWithResourcesEcjFilter; @@ -49,7 +50,8 @@ public class MethodAnalyzer extends MethodProbesVisitor private static final IFilter[] FILTERS = new IFilter[] { new EnumFilter(), new SyntheticFilter(), new SynchronizedFilter(), new TryWithResourcesJavacFilter(), new TryWithResourcesEcjFilter(), - new PrivateEmptyNoArgConstructorFilter(), new LombokGeneratedFilter() }; + new PrivateEmptyNoArgConstructorFilter(), + new StringSwitchJavacFilter(), new LombokGeneratedFilter() }; private final String className; diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AbstractMatcher.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AbstractMatcher.java index 50a295de..c5edb2e4 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AbstractMatcher.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AbstractMatcher.java @@ -25,14 +25,13 @@ abstract class AbstractMatcher { AbstractInsnNode cursor; - final void nextIsAddSuppressed() { + final void nextIsInvokeVirtual(final String owner, final String name) { nextIs(Opcodes.INVOKEVIRTUAL); if (cursor == null) { return; } final MethodInsnNode m = (MethodInsnNode) cursor; - if ("java/lang/Throwable".equals(m.owner) - && "addSuppressed".equals(m.name)) { + if (owner.equals(m.owner) && name.equals(m.name)) { return; } cursor = null; diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/StringSwitchJavacFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/StringSwitchJavacFilter.java new file mode 100644 index 00000000..2824fce9 --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/StringSwitchJavacFilter.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +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 javac for a switch statement with a String. + */ +public final class StringSwitchJavacFilter implements IFilter { + + public void filter(final String className, final String superClassName, + final MethodNode methodNode, final IFilterOutput output) { + AbstractInsnNode i = methodNode.instructions.getFirst(); + while (i != null) { + filter(i, output); + i = i.getNext(); + } + } + + /** + * javac generates two switches. First one by {@link String#hashCode()}. + * Number of handlers in the second switch is equal to number of handlers in + * source code, so it is enough to completely filter-out first switch. + * Handler for default case of the first switch - is the second switch. + */ + private void filter(final AbstractInsnNode start, + final IFilterOutput output) { + final LabelNode dflt; + if (start.getOpcode() == Opcodes.LOOKUPSWITCH) { + dflt = ((LookupSwitchInsnNode) start).dflt; + } else if (start.getOpcode() == Opcodes.TABLESWITCH) { + dflt = ((TableSwitchInsnNode) start).dflt; + } else { + return; + } + if (new Matcher().match(start, dflt)) { + output.ignore(start, dflt); + } + } + + private static class Matcher extends AbstractMatcher { + boolean match(final AbstractInsnNode start, + final AbstractInsnNode secondSwitchLabel) { + cursor = start; + for (int i = 0; cursor != null && i < 4; i++) { + cursor = cursor.getPrevious(); + } + if (cursor == null || cursor.getOpcode() != Opcodes.ICONST_M1) { + return false; + } + nextIsVar(Opcodes.ISTORE, "c"); + nextIsVar(Opcodes.ALOAD, "s"); + nextIsInvokeVirtual("java/lang/String", "hashCode"); + next(); + while (true) { + nextIsVar(Opcodes.ALOAD, "s"); + nextIs(Opcodes.LDC); + nextIsInvokeVirtual("java/lang/String", "equals"); + // jump to next comparison or second switch + nextIs(Opcodes.IFEQ); + // ICONST, BIPUSH or SIPUSH + next(); + nextIsVar(Opcodes.ISTORE, "c"); + if (cursor == null) { + return false; + } + if (cursor.getNext() == secondSwitchLabel) { + break; + } + nextIs(Opcodes.GOTO); + if (((JumpInsnNode) cursor).label != secondSwitchLabel) { + return false; + } + } + nextIsVar(Opcodes.ILOAD, "c"); + nextIs(Opcodes.TABLESWITCH); + return cursor != null; + } + } + +} diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesEcjFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesEcjFilter.java index 7d7d396c..aeceeae4 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesEcjFilter.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesEcjFilter.java @@ -200,7 +200,7 @@ public final class TryWithResourcesEcjFilter implements IFilter { // "primaryExc.addSuppressed(suppressedExc)" nextIsVar(Opcodes.ALOAD, "primaryExc"); nextIsVar(Opcodes.ALOAD, suppressedExc); - nextIsAddSuppressed(); + nextIsInvokeVirtual("java/lang/Throwable", "addSuppressed"); nextIsLabel(endLabel); return cursor != null; } diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesJavacFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesJavacFilter.java index 02ed47e5..49cacb3a 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesJavacFilter.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesJavacFilter.java @@ -214,7 +214,7 @@ public final class TryWithResourcesJavacFilter implements IFilter { // "primaryExc.addSuppressed(t)" nextIsVar(Opcodes.ALOAD, "primaryExc"); nextIsVar(Opcodes.ALOAD, ctx + "t"); - nextIsAddSuppressed(); + nextIsInvokeVirtual("java/lang/Throwable", "addSuppressed"); nextIs(Opcodes.GOTO); // "r.close()" nextIsClose(); diff --git a/org.jacoco.doc/docroot/doc/changes.html b/org.jacoco.doc/docroot/doc/changes.html index dc8d06b2..5ae14dd5 100644 --- a/org.jacoco.doc/docroot/doc/changes.html +++ b/org.jacoco.doc/docroot/doc/changes.html @@ -39,6 +39,9 @@ (GitHub <a href="https://github.com/jacoco/jacoco/issues/513">#513</a>).</li> <li>Exclude from a report private empty constructors that do not have arguments (GitHub <a href="https://github.com/jacoco/jacoco/issues/529">#529</a>).</li> + <li>Exclude from a report a part of bytecode that javac generates for a + String in switch statement + (GitHub <a href="https://github.com/jacoco/jacoco/issues/596">#596</a>).</li> <li>Maven aggregated reports will now also include modules of <code>runtime</code> and <code>provided</code> dependencies (GitHub <a href="https://github.com/jacoco/jacoco/issues/498">#498</a>, |