Skip to content

Commit d07bc6b

Browse files
authored
Add filter for bytecode generated by Kotlin serialization compiler plugin (#1885)
1 parent 5e35fd5 commit d07bc6b

File tree

7 files changed

+379
-0
lines changed

7 files changed

+379
-0
lines changed

org.jacoco.core.test.validation.kotlin/pom.xml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@
4444
<artifactId>kotlinx-coroutines-core</artifactId>
4545
<version>1.8.0</version>
4646
</dependency>
47+
<dependency>
48+
<groupId>org.jetbrains.kotlinx</groupId>
49+
<artifactId>kotlinx-serialization-core-jvm</artifactId>
50+
<version>1.6.3</version>
51+
</dependency>
4752
</dependencies>
4853

4954
<build>
@@ -68,6 +73,18 @@
6873
</configuration>
6974
</execution>
7075
</executions>
76+
<configuration>
77+
<compilerPlugins>
78+
<plugin>kotlinx-serialization</plugin>
79+
</compilerPlugins>
80+
</configuration>
81+
<dependencies>
82+
<dependency>
83+
<groupId>org.jetbrains.kotlin</groupId>
84+
<artifactId>kotlin-maven-serialization</artifactId>
85+
<version>${kotlin.version}</version>
86+
</dependency>
87+
</dependencies>
7188
</plugin>
7289
</plugins>
7390
</build>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2009, 2025 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.KotlinSerializableTarget;
17+
import org.junit.Test;
18+
19+
/**
20+
* Test of code coverage in {@link KotlinSerializableTarget}.
21+
*/
22+
public class KotlinSerializableTest extends ValidationTestBase {
23+
24+
public KotlinSerializableTest() {
25+
super(KotlinSerializableTarget.class);
26+
}
27+
28+
@Test
29+
public void test_method_count() {
30+
assertMethodCount(
31+
/* main + 3 constructors + 3 getters + 1 method in companion */8);
32+
}
33+
34+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2009, 2025 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 kotlinx.serialization.KSerializer
16+
import kotlinx.serialization.SerialName
17+
import kotlinx.serialization.Serializable
18+
import kotlinx.serialization.descriptors.PrimitiveKind
19+
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
20+
import kotlinx.serialization.descriptors.SerialDescriptor
21+
import kotlinx.serialization.encoding.Decoder
22+
import kotlinx.serialization.encoding.Encoder
23+
24+
/**
25+
* Test target with [Serializable] class.
26+
*/
27+
object KotlinSerializableTarget {
28+
29+
@Serializable // assertFullyCovered()
30+
data class Example( // assertFullyCovered()
31+
@SerialName("d") val data: String // assertFullyCovered()
32+
) // assertEmpty()
33+
34+
@Serializable(with = CustomSerializer::class) // assertFullyCovered()
35+
data class ExampleWithCustomSerializer( // assertFullyCovered()
36+
val data: String // assertFullyCovered()
37+
) // assertEmpty()
38+
39+
object CustomSerializer : KSerializer<ExampleWithCustomSerializer> {
40+
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Example", PrimitiveKind.STRING)
41+
override fun serialize(encoder: Encoder, value: ExampleWithCustomSerializer) = encoder.encodeString(value.data)
42+
override fun deserialize(decoder: Decoder): ExampleWithCustomSerializer =
43+
ExampleWithCustomSerializer(decoder.decodeString())
44+
}
45+
46+
data class ExampleWithHandWrittenCompanion(
47+
val data: String
48+
) {
49+
companion object {
50+
fun serializer(): KSerializer<ExampleWithCustomSerializer> = CustomSerializer // assertNotCovered()
51+
}
52+
}
53+
54+
@JvmStatic
55+
fun main(args: Array<String>) {
56+
Example("").data
57+
ExampleWithCustomSerializer("").data
58+
ExampleWithHandWrittenCompanion("")
59+
}
60+
61+
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2009, 2025 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.Label;
17+
import org.objectweb.asm.Opcodes;
18+
import org.objectweb.asm.tree.MethodNode;
19+
20+
/**
21+
* Unit test for {@link KotlinSerializableFilter}.
22+
*/
23+
public class KotlinSerializableFilterTest extends FilterTestBase {
24+
25+
private final IFilter filter = new KotlinSerializableFilter();
26+
27+
/**
28+
* <pre>
29+
* &#064;kotlinx.serialization.Serializable
30+
* data class Example(val data: String)
31+
* </pre>
32+
*/
33+
@Test
34+
public void should_filter_synthetic_writeSelf_method() {
35+
final MethodNode m = new MethodNode(
36+
Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC
37+
| Opcodes.ACC_SYNTHETIC,
38+
"write$Self$module_name",
39+
"(Lpkg$Example;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V",
40+
null, null);
41+
m.visitInsn(Opcodes.NOP);
42+
43+
filter.filter(m, context, output);
44+
45+
assertMethodIgnored(m);
46+
}
47+
48+
/**
49+
* <pre>
50+
* &#064;kotlinx.serialization.Serializable
51+
* data class Example(val data: String)
52+
* </pre>
53+
*/
54+
@Test
55+
public void should_filter_synthetic_constructor() {
56+
final MethodNode m = new MethodNode(
57+
Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, "<init>",
58+
"(ILjava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V",
59+
null, null);
60+
m.visitInsn(Opcodes.NOP);
61+
62+
filter.filter(m, context, output);
63+
64+
assertMethodIgnored(m);
65+
}
66+
67+
/**
68+
* Kotlin 2.1.21 for
69+
*
70+
* <pre>
71+
* &#064;kotlinx.serialization.Serializable // line 1
72+
* data class Example(val data: String)
73+
* </pre>
74+
*/
75+
@Test
76+
public void should_filter_generated_serializer_method() {
77+
context.className = "Example$Companion";
78+
79+
final MethodNode initMethod = new MethodNode(Opcodes.ACC_PRIVATE,
80+
"<init>", "()V", null, null);
81+
// no line numbers
82+
filter.filter(initMethod, context, output);
83+
84+
final MethodNode m = new MethodNode(
85+
Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, "serializer",
86+
"()Lkotlinx/serialization/KSerializer;",
87+
"()Lkotlinx/serialization/KSerializer<LExample;>;", null);
88+
final Label label0 = new Label();
89+
m.visitLabel(label0);
90+
m.visitLineNumber(1, label0);
91+
m.visitFieldInsn(Opcodes.GETSTATIC, "Example$$serializer", "INSTANCE",
92+
"LExample$$serializer;");
93+
m.visitTypeInsn(Opcodes.CHECKCAST, "kotlinx/serialization/KSerializer");
94+
m.visitInsn(Opcodes.ARETURN);
95+
96+
filter.filter(m, context, output);
97+
98+
assertMethodIgnored(m);
99+
}
100+
101+
/**
102+
* <pre>
103+
* &#064;kotlinx.serialization.Serializable
104+
* data class Example(val data: String) {
105+
* companion object // line 2
106+
* }
107+
* </pre>
108+
*/
109+
@Test
110+
public void should_filter_generated_serializer_method_in_hand_written_companion() {
111+
context.className = "Example$Companion";
112+
113+
final MethodNode initMethod = new MethodNode(Opcodes.ACC_PRIVATE,
114+
"<init>", "()V", null, null);
115+
final Label initMethodLineNumberLabel = new Label();
116+
initMethod.visitLabel(initMethodLineNumberLabel);
117+
initMethod.visitLineNumber(2, initMethodLineNumberLabel);
118+
filter.filter(initMethod, context, output);
119+
120+
final MethodNode m = new MethodNode(
121+
Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, "serializer",
122+
"()Lkotlinx/serialization/KSerializer;",
123+
"()Lkotlinx/serialization/KSerializer<LExample;>;", null);
124+
final Label label0 = new Label();
125+
m.visitLabel(label0);
126+
m.visitLineNumber(2, label0);
127+
m.visitFieldInsn(Opcodes.GETSTATIC, "Example$$serializer", "INSTANCE",
128+
"LExample$$serializer;");
129+
m.visitTypeInsn(Opcodes.CHECKCAST, "kotlinx/serialization/KSerializer");
130+
m.visitInsn(Opcodes.ARETURN);
131+
132+
filter.filter(m, context, output);
133+
134+
assertMethodIgnored(m);
135+
}
136+
137+
/**
138+
* <pre>
139+
* data class Example(val data: String) {
140+
* companion object { // line 2
141+
* fun serializer(): KSerializer&lt;Example&gt; = CustomSerializer
142+
* }
143+
* }
144+
* </pre>
145+
*/
146+
@Test
147+
public void should_not_filter_hand_written_serializer_method() {
148+
context.className = "Example$Companion";
149+
150+
final MethodNode initMethod = new MethodNode(Opcodes.ACC_PRIVATE,
151+
"<init>", "()V", null, null);
152+
final Label initMethodLineNumberLabel = new Label();
153+
initMethod.visitLabel(initMethodLineNumberLabel);
154+
initMethod.visitLineNumber(2, initMethodLineNumberLabel);
155+
filter.filter(initMethod, context, output);
156+
157+
final MethodNode m = new MethodNode(
158+
Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, "serializer",
159+
"()Lkotlinx/serialization/KSerializer;",
160+
"()Lkotlinx/serialization/KSerializer<LExample;>;", null);
161+
final Label label0 = new Label();
162+
m.visitLabel(label0);
163+
m.visitLineNumber(3, label0);
164+
m.visitFieldInsn(Opcodes.GETSTATIC, "CustomSerializer", "INSTANCE",
165+
"LCustomSerializer;");
166+
m.visitTypeInsn(Opcodes.CHECKCAST, "kotlinx/serialization/KSerializer");
167+
m.visitInsn(Opcodes.ARETURN);
168+
169+
filter.filter(m, context, output);
170+
171+
assertIgnored(m);
172+
}
173+
174+
}

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
@@ -79,6 +79,7 @@ private static IFilter allKotlinFilters() {
7979
return new FilterSet( //
8080
new KotlinGeneratedFilter(), //
8181
new KotlinSyntheticAccessorsFilter(), //
82+
new KotlinSerializableFilter(), //
8283
new KotlinEnumFilter(), //
8384
new KotlinJvmOverloadsFilter(), //
8485
new KotlinSafeCallOperatorFilter(), //

0 commit comments

Comments
 (0)