Skip to content

Commit adf9595

Browse files
authored
Add filter for Kotlin inline value classes (#1475)
1 parent 3bba375 commit adf9595

File tree

6 files changed

+293
-0
lines changed

6 files changed

+293
-0
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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 org.jacoco.core.test.validation.ValidationTestBase;
16+
import org.jacoco.core.test.validation.kotlin.targets.KotlinInlineClassTarget;
17+
18+
/**
19+
* Test of code coverage in {@link KotlinInlineClassTarget}.
20+
*/
21+
public class KotlinInlineClassTest extends ValidationTestBase {
22+
23+
public KotlinInlineClassTest() {
24+
super(KotlinInlineClassTarget.class);
25+
}
26+
27+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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 for `inline class`.
19+
*/
20+
object KotlinInlineClassTarget {
21+
22+
interface Base {
23+
fun base()
24+
}
25+
26+
@JvmInline
27+
value class I1(val value: String) : Base { // assertEmpty()
28+
29+
init { // assertEmpty()
30+
nop() // assertFullyCovered()
31+
} // assertEmpty()
32+
33+
constructor() : this("") { // assertFullyCovered()
34+
nop() // assertFullyCovered()
35+
} // assertEmpty()
36+
37+
val length: Int // assertEmpty()
38+
get() = value.length // assertFullyCovered()
39+
40+
fun f(p: String) { // assertEmpty()
41+
nop(p) // assertFullyCovered()
42+
} // assertFullyCovered()
43+
44+
fun f(p: I1) { // assertEmpty()
45+
nop(p) // assertFullyCovered()
46+
} // assertFullyCovered()
47+
48+
override fun base() { // assertEmpty()
49+
nop() // assertFullyCovered()
50+
} // assertFullyCovered()
51+
52+
} // assertEmpty()
53+
54+
@JvmInline
55+
value class I2(val value: String) { // assertEmpty()
56+
57+
override fun toString(): String { // assertEmpty()
58+
return "Value: $value" // assertNotCovered()
59+
} // assertEmpty()
60+
61+
} // assertEmpty()
62+
63+
@JvmStatic
64+
fun main(args: Array<String>) {
65+
val i = I1()
66+
i.value
67+
i.length
68+
i.f("")
69+
i.f(i)
70+
i.base()
71+
72+
I2("")
73+
}
74+
75+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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.internal.analysis.filter;
14+
15+
import org.junit.Test;
16+
import org.objectweb.asm.Opcodes;
17+
import org.objectweb.asm.tree.MethodNode;
18+
19+
/**
20+
* Unit tests for {@link KotlinInlineClassFilter}.
21+
*/
22+
public class KotlinInlineClassFilterTest extends FilterTestBase {
23+
24+
private final IFilter filter = new KotlinInlineClassFilter();
25+
26+
/**
27+
* <pre>
28+
* &#064;kotlin.jvm.JvmInline
29+
* value class Example(val value: String)
30+
* </pre>
31+
*/
32+
@Test
33+
public void should_filter() {
34+
context.classAnnotations
35+
.add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC);
36+
context.classAnnotations.add("Lkotlin/jvm/JvmInline;");
37+
final MethodNode m = new MethodNode(0, "getValue",
38+
"()Ljava/lang/String;", null, null);
39+
m.visitInsn(Opcodes.NOP);
40+
41+
filter.filter(m, context, output);
42+
43+
assertMethodIgnored(m);
44+
}
45+
46+
/**
47+
* <pre>
48+
* &#064;kotlin.jvm.JvmInline
49+
* value class Example(val value: String) {
50+
* fun f() { ... }
51+
* }
52+
* </pre>
53+
*/
54+
@Test
55+
public void should_not_filter_static() {
56+
context.classAnnotations
57+
.add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC);
58+
context.classAnnotations.add("Lkotlin/jvm/JvmInline;");
59+
final MethodNode m = new MethodNode(Opcodes.ACC_STATIC, "f-impl", "()V",
60+
null, null);
61+
m.visitInsn(Opcodes.NOP);
62+
63+
filter.filter(m, context, output);
64+
65+
assertIgnored();
66+
}
67+
68+
/**
69+
* <pre>
70+
* data class Example(val value: String)
71+
* </pre>
72+
*/
73+
@Test
74+
public void should_not_filter_when_no_JvmInline_annotation() {
75+
context.classAnnotations
76+
.add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC);
77+
final MethodNode m = new MethodNode(0, "getValue",
78+
"()Ljava/lang/String;", null, null);
79+
m.visitInsn(Opcodes.NOP);
80+
81+
filter.filter(m, context, output);
82+
83+
assertIgnored();
84+
}
85+
86+
@Test
87+
public void should_not_filter_when_not_kotlin() {
88+
context.classAnnotations.add("Lkotlin/jvm/JvmInline;");
89+
final MethodNode m = new MethodNode(0, "getValue",
90+
"()Ljava/lang/String;", null, null);
91+
m.visitInsn(Opcodes.NOP);
92+
93+
filter.filter(m, context, output);
94+
95+
assertIgnored();
96+
}
97+
98+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public static IFilter all() {
4747
new KotlinWhenStringFilter(),
4848
new KotlinUnsafeCastOperatorFilter(),
4949
new KotlinNotNullOperatorFilter(),
50+
new KotlinInlineClassFilter(),
5051
new KotlinDefaultArgumentsFilter(), new KotlinInlineFilter(),
5152
new KotlinCoroutineFilter(), new KotlinDefaultMethodsFilter(),
5253
new KotlinComposeFilter());
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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.internal.analysis.filter;
14+
15+
import org.objectweb.asm.Opcodes;
16+
import org.objectweb.asm.tree.MethodNode;
17+
18+
/**
19+
* Filters methods that Kotlin compiler generates for inline classes.
20+
*
21+
* For
22+
*
23+
* <pre>
24+
* &#064;kotlin.jvm.JvmInline
25+
* value class Example(val value: String) : Base {
26+
* fun f(p: String) { ... }
27+
* fun f(p: Example) { ... }
28+
* override fun base() { ... }
29+
* }
30+
* </pre>
31+
*
32+
* Kotlin compiler produces
33+
*
34+
* <pre>
35+
* &#064;kotlin.jvm.JvmInline
36+
* class Example implements Base {
37+
* private final String value;
38+
* public String getValue() { return value; }
39+
*
40+
* private synthetic Example(String value) { this.value = value; }
41+
*
42+
* public static String constructor-impl(String value) { ... }
43+
*
44+
* public static void f-impl(String value, String p) { ... }
45+
*
46+
* public static void f-ulP-heY(String value, String p) { ... }
47+
*
48+
* public void base() { base-impl(value); }
49+
* public static void base-impl(String value) { ... }
50+
*
51+
* public String toString() { return toString-impl(value); }
52+
* public static String toString-impl(String value) { ... }
53+
*
54+
* public boolean equals(Object other) { return equals-impl(value, other); }
55+
* public static boolean equals-impl(String value, Object other) { ... }
56+
*
57+
* public int hashCode() { return hashCode-impl(value); }
58+
* public static int hashCode-impl(String value) { ... }
59+
*
60+
* public final synthetic String unbox-impl() { return value; }
61+
* public static synthetic Example box-impl(String value) { return new Example(value); }
62+
*
63+
* public static equals-impl0(String value1, String value2) { ... }
64+
* }
65+
* </pre>
66+
*
67+
* Except getter all non-synthetic non-static methods delegate to corresponding
68+
* static methods. Non-static methods are provided for interoperability with
69+
* Java and can not be invoked from Kotlin without reflection and so should be
70+
* filtered out.
71+
*/
72+
final class KotlinInlineClassFilter implements IFilter {
73+
74+
public void filter(final MethodNode methodNode,
75+
final IFilterContext context, final IFilterOutput output) {
76+
if (!KotlinGeneratedFilter.isKotlinClass(context)) {
77+
return;
78+
}
79+
if (!context.getClassAnnotations().contains("Lkotlin/jvm/JvmInline;")) {
80+
return;
81+
}
82+
if ((methodNode.access & Opcodes.ACC_STATIC) != 0) {
83+
return;
84+
}
85+
output.ignore(methodNode.instructions.getFirst(),
86+
methodNode.instructions.getLast());
87+
}
88+
89+
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ <h3>New Features</h3>
2525
<li>Part of bytecode generated by the Kotlin Compose compiler plugin is
2626
filtered out during generation of report
2727
(GitHub <a href="https://github.com/jacoco/jacoco/issues/1616">#1616</a>).</li>
28+
<li>Part of bytecode generated by the Kotlin compiler for inline value classes is
29+
filtered out during generation of report
30+
(GitHub <a href="https://github.com/jacoco/jacoco/issues/1475">#1475</a>).</li>
2831
</ul>
2932

3033
<h2>Release 0.8.12 (2024/03/31)</h2>

0 commit comments

Comments
 (0)