diff options
author | Evgeny Mandrikov <Godin@users.noreply.github.com> | 2018-02-12 13:45:50 +0100 |
---|---|---|
committer | Marc R. Hoffmann <hoffmann@mountainminds.com> | 2018-02-12 13:45:50 +0100 |
commit | 06e8f36dee7bd3f19efa91b486af78eb4d375b46 (patch) | |
tree | 15c62cfb452f076406584f23bdba61e5fc78275a | |
parent | 65595d05530621a240fc7511a041e2f3909057ed (diff) | |
download | jacoco-06e8f36dee7bd3f19efa91b486af78eb4d375b46.tar.gz |
Add filter for empty constructor without parameters in enum (#649)
8 files changed, 369 insertions, 20 deletions
diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/EnumEmptyConstructorFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/EnumEmptyConstructorFilterTest.java new file mode 100644 index 00000000..a2216546 --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/EnumEmptyConstructorFilterTest.java @@ -0,0 +1,151 @@ +/******************************************************************************* + * 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 org.jacoco.core.internal.instr.InstrSupport; +import org.junit.Test; +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; +import static org.junit.Assert.fail; + +public class EnumEmptyConstructorFilterTest implements IFilterOutput { + + private final EnumEmptyConstructorFilter filter = new EnumEmptyConstructorFilter(); + + private AbstractInsnNode fromInclusive; + private AbstractInsnNode toInclusive; + + @Test + public void should_filter() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, + Opcodes.ACC_PRIVATE, "<init>", "(Ljava/lang/String;I)V", null, + null); + m.visitVarInsn(Opcodes.ALOAD, 0); + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitVarInsn(Opcodes.ILOAD, 2); + m.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Enum", "<init>", + "(Ljava/lang/String;I)V", false); + m.visitInsn(Opcodes.RETURN); + + filter.filter("Foo", "java/lang/Enum", m, this); + + assertEquals(m.instructions.getFirst(), fromInclusive); + assertEquals(m.instructions.getLast(), toInclusive); + } + + /** + * <code><pre> + * enum E { + * ; + * private E() { + * ... + * } + * } + * </pre></code> + */ + @Test + public void should_not_filter_non_empty_constructor() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, + Opcodes.ACC_PRIVATE, "<init>", "(Ljava/lang/String;I)V", null, + null); + m.visitVarInsn(Opcodes.ALOAD, 0); + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitVarInsn(Opcodes.ILOAD, 2); + m.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Enum", "<init>", + "(Ljava/lang/String;I)V", false); + m.visitInsn(Opcodes.NOP); + m.visitInsn(Opcodes.RETURN); + + filter.filter("Foo", "java/lang/Enum", m, this); + + assertNull(fromInclusive); + assertNull(toInclusive); + } + + /** + * <code><pre> + * enum E { + * ; + * private E(long p) { + * } + * } + * </pre></code> + */ + @Test + public void should_not_filter_constructor_with_additional_parameters() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, + Opcodes.ACC_PRIVATE, "<init>", "(Ljava/lang/String;IJ)V", null, + null); + m.visitVarInsn(Opcodes.ALOAD, 0); + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitVarInsn(Opcodes.ILOAD, 2); + m.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Enum", "<init>", + "(Ljava/lang/String;I)V", false); + m.visitInsn(Opcodes.RETURN); + + filter.filter("Foo", "java/lang/Enum", m, this); + + assertNull(fromInclusive); + assertNull(toInclusive); + } + + /** + * <code><pre> + * enum E { + * ; + * private void method(String p1, int p2) { + * } + * } + * </pre></code> + */ + @Test + public void should_not_filter_non_constructor() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, + Opcodes.ACC_PRIVATE, "method", "(Ljava/lang/String;I)V", null, + null); + m.visitInsn(Opcodes.NOP); + + filter.filter("Foo", "java/lang/Enum", m, this); + + assertNull(fromInclusive); + assertNull(toInclusive); + } + + @Test + public void should_not_filter_non_Enum() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, + Opcodes.ACC_PRIVATE, "<init>", "(Ljava/lang/String;I)V", null, + null); + m.visitInsn(Opcodes.NOP); + + filter.filter("Foo", "java/lang/Object", m, this); + + assertNull(fromInclusive); + assertNull(toInclusive); + } + + public void ignore(AbstractInsnNode fromInclusive, + AbstractInsnNode toInclusive) { + assertNull(this.fromInclusive); + this.fromInclusive = fromInclusive; + this.toInclusive = toInclusive; + } + + public void merge(AbstractInsnNode i1, AbstractInsnNode i2) { + fail(); + } + +} diff --git a/org.jacoco.core.test/src/org/jacoco/core/test/filter/EnumConstructorTest.java b/org.jacoco.core.test/src/org/jacoco/core/test/filter/EnumConstructorTest.java new file mode 100644 index 00000000..fd88787b --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/test/filter/EnumConstructorTest.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * 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.test.filter; + +import org.jacoco.core.analysis.ICounter; +import org.jacoco.core.test.filter.targets.EnumConstructor; +import org.jacoco.core.test.validation.ValidationTestBase; +import org.junit.Test; + +/** + * Test of filtering of enum constructors. + */ +public class EnumConstructorTest extends ValidationTestBase { + + public EnumConstructorTest() { + super(EnumConstructor.class); + } + + /** + * {@link EnumConstructor.ImplicitConstructor} + */ + @Test + public void implicit_constructor_should_be_filtered() { + // without filter next line is partly covered: + assertLine("implicitConstructor", ICounter.FULLY_COVERED); + } + + /** + * {@link EnumConstructor.ExplicitNonEmptyConstructor#ExplicitNonEmptyConstructor()} + */ + @Test + public void explicit_non_empty_constructor_should_not_be_filtered() { + assertLine("explicitNonEmptyConstructor", ICounter.NOT_COVERED); + } + + /** + * {@link EnumConstructor.ExplicitEmptyConstructor#ExplicitEmptyConstructor()} + */ + @Test + public void explicit_empty_constructor_should_be_filtered() { + // without filter next line is not covered: + assertLine("explicitEmptyConstructor", ICounter.EMPTY); + } + + /** + * {@link EnumConstructor.ExplicitEmptyConstructor#ExplicitEmptyConstructor(Object)} + */ + @Test + public void explicit_empty_constructor_with_parameters_should_not_be_filtered() { + assertLine("explicitEmptyConstructorWithParameter", ICounter.NOT_COVERED); + } + +} diff --git a/org.jacoco.core.test/src/org/jacoco/core/test/filter/targets/EnumConstructor.java b/org.jacoco.core.test/src/org/jacoco/core/test/filter/targets/EnumConstructor.java new file mode 100644 index 00000000..c6eb1c53 --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/test/filter/targets/EnumConstructor.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * 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.test.filter.targets; + +import static org.jacoco.core.test.validation.targets.Stubs.nop; + +/** + * This test target is an enum constructor. + */ +public class EnumConstructor { + + private enum ImplicitConstructor { // $line-implicitConstructor$ + } + + private enum ExplicitNonEmptyConstructor { + ; + + ExplicitNonEmptyConstructor() { + nop(); // $line-explicitNonEmptyConstructor$ + } + } + + @SuppressWarnings("unused") + private enum ExplicitEmptyConstructor { + ; + + ExplicitEmptyConstructor() { + } // $line-explicitEmptyConstructor$ + + ExplicitEmptyConstructor(Object p) { + } // $line-explicitEmptyConstructorWithParameter$ + } + + public static void main(String[] args) { + ImplicitConstructor.values(); + ExplicitEmptyConstructor.values(); + ExplicitNonEmptyConstructor.values(); + } + +} 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 b5aea723..fcd1c888 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 @@ -17,6 +17,7 @@ import java.util.Map; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.VarInsnNode; abstract class AbstractMatcher { @@ -25,6 +26,35 @@ abstract class AbstractMatcher { AbstractInsnNode cursor; + /** + * Sets {@link #cursor} to first instruction of method if it is + * <code>ALOAD 0</code>, otherwise sets it to <code>null</code>. + */ + final void firstIsALoad0(final MethodNode methodNode) { + cursor = methodNode.instructions.getFirst(); + skipNonOpcodes(); + if (cursor.getOpcode() == Opcodes.ALOAD + && ((VarInsnNode) cursor).var == 0) { + return; + } + cursor = null; + } + + /** + * Moves {@link #cursor} to next instruction if it is + * <code>INVOKESPECIAL <init></code> with given owner and descriptor, + * otherwise sets it to <code>null</code>. + */ + final void nextIsInvokeSuper(final String owner, final String desc) { + nextIs(Opcodes.INVOKESPECIAL); + MethodInsnNode m = (MethodInsnNode) cursor; + if (m != null && owner.equals(m.owner) && "<init>".equals(m.name) + && desc.equals(m.desc)) { + return; + } + cursor = null; + } + final void nextIsInvokeVirtual(final String owner, final String name) { nextIs(Opcodes.INVOKEVIRTUAL); if (cursor == null) { @@ -76,7 +106,7 @@ abstract class AbstractMatcher { skipNonOpcodes(); } - final void skipNonOpcodes() { + private void skipNonOpcodes() { while (cursor != null && (cursor.getType() == AbstractInsnNode.FRAME || cursor.getType() == AbstractInsnNode.LABEL || cursor.getType() == AbstractInsnNode.LINE)) { diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/EnumEmptyConstructorFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/EnumEmptyConstructorFilter.java new file mode 100644 index 00000000..9bdc5077 --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/EnumEmptyConstructorFilter.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * 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 org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.MethodNode; + +/** + * Filters empty enum constructors. + * + * Constructor of enum is invoked from static initialization block to create + * instance of each enum constant. So it won't be executed if number of enum + * constants is zero. Such enums are sometimes used as alternative to classes + * with static utilities and private empty constructor. Implicit constructor of + * enum created by compiler doesn't have a synthetic flag and refers to a line + * of enum definition. Therefore in order to not have partial coverage of enum + * definition line in enums without enum constants and similarly to + * {@link PrivateEmptyNoArgConstructorFilter filter of private empty + * constructors} - empty constructor in enums without additional parameters + * should be filtered out even if it is not implicit. + */ +public final class EnumEmptyConstructorFilter implements IFilter { + + private static final String CONSTRUCTOR_NAME = "<init>"; + private static final String CONSTRUCTOR_DESC = "(Ljava/lang/String;I)V"; + + public void filter(String className, String superClassName, + MethodNode methodNode, IFilterOutput output) { + if ("java/lang/Enum".equals(superClassName) + && CONSTRUCTOR_NAME.equals(methodNode.name) + && CONSTRUCTOR_DESC.equals(methodNode.desc) + && new Matcher().match(methodNode, superClassName)) { + output.ignore(methodNode.instructions.getFirst(), + methodNode.instructions.getLast()); + } + } + + private static class Matcher extends AbstractMatcher { + private boolean match(final MethodNode methodNode, + final String superClassName) { + firstIsALoad0(methodNode); + nextIs(Opcodes.ALOAD); + nextIs(Opcodes.ILOAD); + nextIsInvokeSuper(superClassName, CONSTRUCTOR_DESC); + nextIs(Opcodes.RETURN); + return cursor != null; + } + } + +} 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 0f344180..40c0dfc3 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,7 +31,7 @@ public final class Filters implements IFilter { new TryWithResourcesJavacFilter(), new TryWithResourcesEcjFilter(), new FinallyFilter(), new PrivateEmptyNoArgConstructorFilter(), new StringSwitchJavacFilter(), new LombokGeneratedFilter(), - new GroovyGeneratedFilter()); + new GroovyGeneratedFilter(), new EnumEmptyConstructorFilter()); private final IFilter[] filters; diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/PrivateEmptyNoArgConstructorFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/PrivateEmptyNoArgConstructorFilter.java index 08c654e3..29214c5b 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/PrivateEmptyNoArgConstructorFilter.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/PrivateEmptyNoArgConstructorFilter.java @@ -12,20 +12,21 @@ package org.jacoco.core.internal.analysis.filter; import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; -import org.objectweb.asm.tree.VarInsnNode; /** * Filters private empty constructors that do not have arguments. */ public final class PrivateEmptyNoArgConstructorFilter implements IFilter { + private static final String CONSTRUCTOR_NAME = "<init>"; + private static final String CONSTRUCTOR_DESC = "()V"; + public void filter(final String className, final String superClassName, final MethodNode methodNode, final IFilterOutput output) { if ((methodNode.access & Opcodes.ACC_PRIVATE) != 0 - && "<init>".equals(methodNode.name) - && "()V".equals(methodNode.desc) + && CONSTRUCTOR_NAME.equals(methodNode.name) + && CONSTRUCTOR_DESC.equals(methodNode.desc) && new Matcher().match(methodNode, superClassName)) { output.ignore(methodNode.instructions.getFirst(), methodNode.instructions.getLast()); @@ -35,20 +36,10 @@ public final class PrivateEmptyNoArgConstructorFilter implements IFilter { private static class Matcher extends AbstractMatcher { private boolean match(final MethodNode methodNode, final String superClassName) { - cursor = methodNode.instructions.getFirst(); - skipNonOpcodes(); - if (cursor.getOpcode() != Opcodes.ALOAD - || ((VarInsnNode) cursor).var != 0) { - return false; - } - nextIs(Opcodes.INVOKESPECIAL); - MethodInsnNode m = (MethodInsnNode) cursor; - if (m != null && superClassName.equals(m.owner) - && "<init>".equals(m.name) && ("()V").equals(m.desc)) { - nextIs(Opcodes.RETURN); - return cursor != null; - } - return false; + firstIsALoad0(methodNode); + nextIsInvokeSuper(superClassName, CONSTRUCTOR_DESC); + nextIs(Opcodes.RETURN); + return cursor != null; } } diff --git a/org.jacoco.doc/docroot/doc/changes.html b/org.jacoco.doc/docroot/doc/changes.html index 352e2d7f..eebd90f7 100644 --- a/org.jacoco.doc/docroot/doc/changes.html +++ b/org.jacoco.doc/docroot/doc/changes.html @@ -20,6 +20,13 @@ <h2>Snapshot Build @qualified.bundle.version@ (@build.date@)</h2> +<h3>New Features</h3> +<ul> + <li>Empty constructor without parameters in enum is filtered out during + generation of report + (GitHub <a href="https://github.com/jacoco/jacoco/issues/649">#649</a>).</li> +</ul> + <h2>Release 0.8.0 (2018/01/02)</h2> <h3>New Features</h3> |