Skip to content

Commit 58b3e65

Browse files
authored
SyntheticFilter should filter out only synthetic accessors in Kotlin classes (#1700)
1 parent 44b4e5a commit 58b3e65

File tree

9 files changed

+233
-27
lines changed

9 files changed

+233
-27
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2009, 2024 Mountainminds GmbH & Co. KG and Contributors
3+
* This program and the accompanying materials are made available under
4+
* the terms of the Eclipse Public License 2.0 which is available at
5+
* http://www.eclipse.org/legal/epl-2.0
6+
*
7+
* SPDX-License-Identifier: EPL-2.0
8+
*
9+
* Contributors:
10+
* Evgeny Mandrikov - initial API and implementation
11+
*
12+
*******************************************************************************/
13+
package org.jacoco.core.test.validation.kotlin;
14+
15+
import static org.junit.Assert.assertEquals;
16+
17+
import java.lang.reflect.Method;
18+
import java.util.Collections;
19+
import java.util.HashSet;
20+
21+
import org.jacoco.core.test.validation.ValidationTestBase;
22+
import org.jacoco.core.test.validation.kotlin.targets.KotlinInlineReifiedTarget;
23+
import org.junit.Test;
24+
25+
/**
26+
* Test of code coverage in {@link KotlinInlineReifiedTarget}.
27+
*/
28+
public class KotlinInlineReifiedTest extends ValidationTestBase {
29+
30+
public KotlinInlineReifiedTest() {
31+
super(KotlinInlineReifiedTarget.class);
32+
}
33+
34+
@Test
35+
public void compiler_should_generate_synthetic_method() {
36+
final HashSet<String> names = new HashSet<String>();
37+
for (final Method method : KotlinInlineReifiedTarget.class
38+
.getDeclaredMethods()) {
39+
if (method.isSynthetic()) {
40+
names.add(method.getName());
41+
}
42+
}
43+
44+
assertEquals(Collections.singleton("example"), names);
45+
}
46+
47+
@Test
48+
public void test_method_count() {
49+
assertMethodCount(2);
50+
}
51+
52+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2009, 2024 Mountainminds GmbH & Co. KG and Contributors
3+
* This program and the accompanying materials are made available under
4+
* the terms of the Eclipse Public License 2.0 which is available at
5+
* http://www.eclipse.org/legal/epl-2.0
6+
*
7+
* SPDX-License-Identifier: EPL-2.0
8+
*
9+
* Contributors:
10+
* Evgeny Mandrikov - initial API and implementation
11+
*
12+
*******************************************************************************/
13+
package org.jacoco.core.test.validation.kotlin;
14+
15+
import static org.junit.Assert.assertEquals;
16+
17+
import java.lang.reflect.Method;
18+
import java.util.Collections;
19+
import java.util.HashSet;
20+
21+
import org.jacoco.core.test.validation.ValidationTestBase;
22+
import org.jacoco.core.test.validation.kotlin.targets.KotlinJvmSyntheticTarget;
23+
import org.junit.Test;
24+
25+
/**
26+
* Test of code coverage in {@link KotlinJvmSyntheticTarget}.
27+
*/
28+
public class KotlinJvmSyntheticTest extends ValidationTestBase {
29+
30+
public KotlinJvmSyntheticTest() {
31+
super(KotlinJvmSyntheticTarget.class);
32+
}
33+
34+
@Test
35+
public void compiler_should_generate_synthetic_method() {
36+
final HashSet<String> names = new HashSet<String>();
37+
for (final Method method : KotlinJvmSyntheticTarget.class
38+
.getDeclaredMethods()) {
39+
if (method.isSynthetic()) {
40+
names.add(method.getName());
41+
}
42+
}
43+
44+
assertEquals(Collections.singleton("example"), names);
45+
}
46+
47+
@Test
48+
public void test_method_count() {
49+
assertMethodCount(2);
50+
}
51+
52+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2009, 2024 Mountainminds GmbH & Co. KG and Contributors
3+
* This program and the accompanying materials are made available under
4+
* the terms of the Eclipse Public License 2.0 which is available at
5+
* http://www.eclipse.org/legal/epl-2.0
6+
*
7+
* SPDX-License-Identifier: EPL-2.0
8+
*
9+
* Contributors:
10+
* Evgeny Mandrikov - initial API and implementation
11+
*
12+
*******************************************************************************/
13+
package org.jacoco.core.test.validation.kotlin.targets
14+
15+
import org.jacoco.core.test.validation.targets.Stubs.nop
16+
17+
/**
18+
* Test target containing `inline` function with
19+
* [`reified` type parameter](https://kotlinlang.org/docs/inline-functions.html#reified-type-parameters).
20+
*/
21+
object KotlinInlineReifiedTarget {
22+
23+
private inline fun <reified T> example() { // assertEmpty()
24+
nop(T::class) // assertFullyCovered()
25+
} // assertFullyCovered()
26+
27+
@JvmStatic
28+
fun main(args: Array<String>) {
29+
example<String>()
30+
}
31+
32+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2009, 2024 Mountainminds GmbH & Co. KG and Contributors
3+
* This program and the accompanying materials are made available under
4+
* the terms of the Eclipse Public License 2.0 which is available at
5+
* http://www.eclipse.org/legal/epl-2.0
6+
*
7+
* SPDX-License-Identifier: EPL-2.0
8+
*
9+
* Contributors:
10+
* Evgeny Mandrikov - initial API and implementation
11+
*
12+
*******************************************************************************/
13+
package org.jacoco.core.test.validation.kotlin.targets
14+
15+
import org.jacoco.core.test.validation.targets.Stubs.nop
16+
17+
/**
18+
* Test target containing [JvmSynthetic] function.
19+
*/
20+
object KotlinJvmSyntheticTarget {
21+
22+
@JvmSynthetic
23+
private fun example() { // assertEmpty()
24+
nop() // assertFullyCovered()
25+
} // assertFullyCovered()
26+
27+
@JvmStatic
28+
fun main(args: Array<String>) {
29+
example()
30+
}
31+
32+
}

org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/SyntheticFilterTest.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,59 @@ public void testLambda() {
5757
assertIgnored();
5858
}
5959

60+
/**
61+
* <pre>
62+
* &#064;JvmSynthetic
63+
* fun example() {
64+
* }
65+
* </pre>
66+
*
67+
* @see #should_filter_synthetic_accessor_methods_in_Kotlin_classes()
68+
*/
69+
@Test
70+
public void should_not_filter_synthetic_non_accessor_methods_in_Kotlin_classes() {
71+
context.classAnnotations
72+
.add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC);
73+
final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION,
74+
Opcodes.ACC_SYNTHETIC, "example", "()V", null, null);
75+
m.visitInsn(Opcodes.RETURN);
76+
77+
filter.filter(m, context, output);
78+
79+
assertIgnored();
80+
}
81+
82+
/**
83+
* <pre>
84+
* class Outer {
85+
* private var x = 42
86+
*
87+
* inner class Inner {
88+
* fun example() {
89+
* x += 1
90+
* }
91+
* }
92+
* }
93+
* </pre>
94+
*
95+
* @see #should_not_filter_synthetic_non_accessor_methods_in_Kotlin_classes()
96+
*/
97+
@Test
98+
public void should_filter_synthetic_accessor_methods_in_Kotlin_classes() {
99+
context.classAnnotations
100+
.add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC);
101+
final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION,
102+
Opcodes.ACC_SYNTHETIC, "access$getX$p", "(Outer;)I", null,
103+
null);
104+
m.visitVarInsn(Opcodes.ALOAD, 0);
105+
m.visitFieldInsn(Opcodes.GETFIELD, "Outer", "x", "I");
106+
m.visitInsn(Opcodes.IRETURN);
107+
108+
filter.filter(m, context, output);
109+
110+
assertMethodIgnored(m);
111+
}
112+
60113
@Test
61114
public void should_filter_synthetic_method_with_prefix_anonfun_in_non_Scala_classes() {
62115
final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION,

org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinCoroutineFilter.java

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import java.util.List;
1717

1818
import org.objectweb.asm.Opcodes;
19-
import org.objectweb.asm.Type;
2019
import org.objectweb.asm.tree.AbstractInsnNode;
2120
import org.objectweb.asm.tree.JumpInsnNode;
2221
import org.objectweb.asm.tree.LdcInsnNode;
@@ -29,17 +28,6 @@
2928
*/
3029
public final class KotlinCoroutineFilter implements IFilter {
3130

32-
static boolean isImplementationOfSuspendFunction(
33-
final MethodNode methodNode) {
34-
if (methodNode.name.startsWith("access$")) {
35-
return false;
36-
}
37-
final Type methodType = Type.getMethodType(methodNode.desc);
38-
final int lastArgument = methodType.getArgumentTypes().length - 1;
39-
return lastArgument >= 0 && "kotlin.coroutines.Continuation".equals(
40-
methodType.getArgumentTypes()[lastArgument].getClassName());
41-
}
42-
4331
public void filter(final MethodNode methodNode,
4432
final IFilterContext context, final IFilterOutput output) {
4533

org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinDefaultArgumentsFilter.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,13 @@
5252
*/
5353
public final class KotlinDefaultArgumentsFilter implements IFilter {
5454

55-
static boolean isDefaultArgumentsMethod(final MethodNode methodNode) {
55+
private static boolean isDefaultArgumentsMethod(
56+
final MethodNode methodNode) {
5657
return methodNode.name.endsWith("$default");
5758
}
5859

59-
static boolean isDefaultArgumentsConstructor(final MethodNode methodNode) {
60+
private static boolean isDefaultArgumentsConstructor(
61+
final MethodNode methodNode) {
6062
if (!"<init>".equals(methodNode.name)) {
6163
return false;
6264
}

org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/SyntheticFilter.java

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,7 @@ public void filter(final MethodNode methodNode,
4242
}
4343

4444
if (KotlinGeneratedFilter.isKotlinClass(context)) {
45-
if (KotlinDefaultArgumentsFilter
46-
.isDefaultArgumentsMethod(methodNode)) {
47-
return;
48-
}
49-
50-
if (KotlinDefaultArgumentsFilter
51-
.isDefaultArgumentsConstructor(methodNode)) {
52-
return;
53-
}
54-
55-
if (KotlinCoroutineFilter
56-
.isImplementationOfSuspendFunction(methodNode)) {
45+
if (!methodNode.name.startsWith("access$")) {
5746
return;
5847
}
5948
}

org.jacoco.doc/docroot/doc/changes.html

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,14 @@ <h2>Snapshot Build @qualified.bundle.version@ (@build.date@)</h2>
2222

2323
<h3>New Features</h3>
2424
<ul>
25-
<li>Calculation of line coverage for Kotlin inline functions
25+
<li>Calculation of line coverage for Kotlin <code>inline</code> functions
2626
(GitHub <a href="https://github.com/jacoco/jacoco/issues/1670">#1670</a>).</li>
27+
<li>Calculation of line coverage for Kotlin <code>inline</code> functions
28+
with <code>reified</code> type parameter
29+
(GitHub <a href="https://github.com/jacoco/jacoco/issues/1670">#1670</a>,
30+
<a href="https://github.com/jacoco/jacoco/issues/1700">#1700</a>).</li>
31+
<li>Calculation of coverage for Kotlin <code>JvmSynthetic</code> functions
32+
(GitHub <a href="https://github.com/jacoco/jacoco/issues/1700">#1700</a>).</li>
2733
<li>Experimental support for Java 24 class files
2834
(GitHub <a href="https://github.com/jacoco/jacoco/issues/1631">#1631</a>).</li>
2935
<li>Part of bytecode generated by the Kotlin Compose compiler plugin is

0 commit comments

Comments
 (0)