From 04fe2006c0786a96462ef7336d162e4a527759e0 Mon Sep 17 00:00:00 2001 From: Evgeny Mandrikov <138671+Godin@users.noreply.github.com> Date: Sun, 9 Feb 2020 19:10:09 +0200 Subject: [PATCH] Add filter for Kotlin default methods (#1012) --- .../kotlin/KotlinDefaultMethodsTest.java | 27 ++++++ .../targets/KotlinDefaultMethodsTarget.kt | 36 ++++++++ .../KotlinDefaultMethodsFilterTest.java | 86 +++++++++++++++++++ .../internal/analysis/filter/Filters.java | 2 +- .../filter/KotlinDefaultMethodsFilter.java | 46 ++++++++++ org.jacoco.doc/docroot/doc/changes.html | 3 + 6 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/KotlinDefaultMethodsTest.java create mode 100644 org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinDefaultMethodsTarget.kt create mode 100644 org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinDefaultMethodsFilterTest.java create mode 100644 org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinDefaultMethodsFilter.java diff --git a/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/KotlinDefaultMethodsTest.java b/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/KotlinDefaultMethodsTest.java new file mode 100644 index 000000000..867fb814e --- /dev/null +++ b/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/KotlinDefaultMethodsTest.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2009, 2020 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.test.validation.kotlin; + +import org.jacoco.core.test.validation.ValidationTestBase; +import org.jacoco.core.test.validation.kotlin.targets.KotlinDefaultMethodsTarget; + +/** + * Test of code coverage in {@link KotlinDefaultMethodsTarget}. + */ +public class KotlinDefaultMethodsTest extends ValidationTestBase { + + public KotlinDefaultMethodsTest() { + super(KotlinDefaultMethodsTarget.class); + } + +} diff --git a/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinDefaultMethodsTarget.kt b/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinDefaultMethodsTarget.kt new file mode 100644 index 000000000..680e25534 --- /dev/null +++ b/org.jacoco.core.test.validation.kotlin/src/org/jacoco/core/test/validation/kotlin/targets/KotlinDefaultMethodsTarget.kt @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2009, 2020 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.test.validation.kotlin.targets + +/** + * This test target contains class implementing interface with default methods. + */ +object KotlinDefaultMethodsTarget { + + interface I { + fun m1() = Unit // assertNotCovered() + fun m2() = Unit // assertFullyCovered() + fun m3() = Unit // assertNotCovered() + } + + class C : I { // assertFullyCovered() + override fun m1() = Unit // assertFullyCovered() + } + + @JvmStatic + fun main(args: Array) { + C().m1() + C().m2() + } + +} diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinDefaultMethodsFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinDefaultMethodsFilterTest.java new file mode 100644 index 000000000..583b5c958 --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinDefaultMethodsFilterTest.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2009, 2020 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.filter; + +import org.jacoco.core.internal.instr.InstrSupport; +import org.junit.Test; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.MethodNode; + +/** + * Unit test for {@link KotlinDefaultMethodsFilter}. + */ +public class KotlinDefaultMethodsFilterTest extends FilterTestBase { + + private final IFilter filter = new KotlinDefaultMethodsFilter(); + + @Test + public void should_filter() { + context.classAnnotations + .add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC); + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "m", "()V", null, null); + m.visitVarInsn(Opcodes.ALOAD, 0); + m.visitMethodInsn(Opcodes.INVOKESTATIC, "Target$DefaultImpls", "m", + "(LTarget;)V", false); + m.visitInsn(Opcodes.RETURN); + + filter.filter(m, context, output); + + assertMethodIgnored(m); + } + + @Test + public void should_not_filter_when_invokestatic_owner_does_not_match() { + context.classAnnotations + .add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC); + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "m", "()V", null, null); + m.visitVarInsn(Opcodes.ALOAD, 0); + m.visitMethodInsn(Opcodes.INVOKESTATIC, "Target", "m", "(LTarget;)V", + false); + m.visitInsn(Opcodes.RETURN); + + filter.filter(m, context, output); + + assertIgnored(); + } + + @Test + public void should_not_filter_when_instructions_do_not_match() { + context.classAnnotations + .add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC); + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "m", "()V", null, null); + m.visitInsn(Opcodes.RETURN); + + filter.filter(m, context, output); + + assertIgnored(); + } + + @Test + public void should_not_filter_when_not_kotlin() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "m", "()V", null, null); + m.visitVarInsn(Opcodes.ALOAD, 0); + m.visitMethodInsn(Opcodes.INVOKESTATIC, "Target$DefaultImpls", "m", + "(LTarget;)V", false); + m.visitInsn(Opcodes.RETURN); + + filter.filter(m, context, output); + + assertIgnored(); + } + +} 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 bbe041f6f..b25bcf5c8 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 @@ -46,7 +46,7 @@ public static IFilter all() { new KotlinUnsafeCastOperatorFilter(), new KotlinNotNullOperatorFilter(), new KotlinDefaultArgumentsFilter(), new KotlinInlineFilter(), - new KotlinCoroutineFilter()); + new KotlinCoroutineFilter(), new KotlinDefaultMethodsFilter()); } private Filters(final IFilter... filters) { diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinDefaultMethodsFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinDefaultMethodsFilter.java new file mode 100644 index 000000000..65d783a17 --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinDefaultMethodsFilter.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2009, 2020 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.filter; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; + +/** + * Filters methods that Kotlin compiler generates for non-overridden + * non-abstract methods of interfaces. + */ +final class KotlinDefaultMethodsFilter implements IFilter { + + public void filter(final MethodNode methodNode, + final IFilterContext context, final IFilterOutput output) { + if (!KotlinGeneratedFilter.isKotlinClass(context)) { + return; + } + new Matcher().match(methodNode, output); + } + + private static class Matcher extends AbstractMatcher { + private void match(final MethodNode methodNode, + final IFilterOutput output) { + firstIsALoad0(methodNode); + nextIs(Opcodes.INVOKESTATIC); + if (cursor != null && ((MethodInsnNode) cursor).owner + .endsWith("$DefaultImpls")) { + output.ignore(methodNode.instructions.getFirst(), + methodNode.instructions.getLast()); + } + } + } + +} diff --git a/org.jacoco.doc/docroot/doc/changes.html b/org.jacoco.doc/docroot/doc/changes.html index da3817fc3..5f4ed69e8 100644 --- a/org.jacoco.doc/docroot/doc/changes.html +++ b/org.jacoco.doc/docroot/doc/changes.html @@ -29,6 +29,9 @@

New Features

(GitHub #990).
  • Bridge methods are filtered out during generation of report (GitHub #1010).
  • +
  • Methods generated by Kotlin compiler for non-overridden non-abstract methods + of interfaces are filtered out during generation of report + (GitHub #1012).
  • Non-functional Changes