aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEvgeny Mandrikov <Godin@users.noreply.github.com>2018-02-12 13:45:50 +0100
committerMarc R. Hoffmann <hoffmann@mountainminds.com>2018-02-12 13:45:50 +0100
commit06e8f36dee7bd3f19efa91b486af78eb4d375b46 (patch)
tree15c62cfb452f076406584f23bdba61e5fc78275a
parent65595d05530621a240fc7511a041e2f3909057ed (diff)
downloadjacoco-06e8f36dee7bd3f19efa91b486af78eb4d375b46.tar.gz
Add filter for empty constructor without parameters in enum (#649)
-rw-r--r--org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/EnumEmptyConstructorFilterTest.java151
-rw-r--r--org.jacoco.core.test/src/org/jacoco/core/test/filter/EnumConstructorTest.java62
-rw-r--r--org.jacoco.core.test/src/org/jacoco/core/test/filter/targets/EnumConstructor.java49
-rw-r--r--org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AbstractMatcher.java32
-rw-r--r--org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/EnumEmptyConstructorFilter.java59
-rw-r--r--org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/Filters.java2
-rw-r--r--org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/PrivateEmptyNoArgConstructorFilter.java27
-rw-r--r--org.jacoco.doc/docroot/doc/changes.html7
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 &lt;init&gt;</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>