KotlinSerializableFilter.java
/*******************************************************************************
* Copyright (c) 2009, 2026 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
* https://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.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.LineNumberNode;
import org.objectweb.asm.tree.MethodNode;
/**
* Filters methods generated by <a href=
* "https://github.com/JetBrains/kotlin/tree/v2.1.20/plugins/kotlinx-serialization">Kotlin
* serialization compiler plugin</a>.
*/
final class KotlinSerializableFilter implements IFilter {
private int lineNumber = -2;
public void filter(final MethodNode methodNode,
final IFilterContext context, final IFilterOutput output) {
if (context.getClassName().endsWith("$Companion")
&& methodNode.name.equals("<init>")
&& methodNode.desc.equals("()V")) {
lineNumber = -1;
for (final AbstractInsnNode i : methodNode.instructions) {
if (i.getType() == AbstractInsnNode.LINE) {
lineNumber = ((LineNumberNode) i).line;
return;
}
}
return;
}
if (methodNode.name.equals("serializer") && methodNode.desc
.equals("()Lkotlinx/serialization/KSerializer;")) {
final Matcher matcher = new Matcher();
if (matcher.matchSerializer(methodNode, lineNumber)
|| matcher.matchCachedSerializer(methodNode,
context.getClassName())) {
output.ignore(methodNode.instructions.getFirst(),
methodNode.instructions.getLast());
}
return;
}
if ((methodNode.access & Opcodes.ACC_SYNTHETIC) == 0) {
return;
}
if (methodNode.name.startsWith("write$Self$")) {
output.ignore(methodNode.instructions.getFirst(),
methodNode.instructions.getLast());
return;
}
if ((methodNode.name.equals("get$cachedSerializer")
// https://github.com/JetBrains/kotlin/blob/v2.2.20/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrBuilderWithPluginContext.kt#L61
|| methodNode.name.startsWith("_init_$_anonymous_")
|| methodNode.name.startsWith("_childSerializers$_anonymous_"))
&& methodNode.desc
.equals("()Lkotlinx/serialization/KSerializer;")) {
output.ignore(methodNode.instructions.getFirst(),
methodNode.instructions.getLast());
return;
}
final Type[] argumentTypes = Type.getArgumentTypes(methodNode.desc);
if (argumentTypes.length > 1 && argumentTypes[argumentTypes.length - 1]
.getClassName()
.equals("kotlinx.serialization.internal.SerializationConstructorMarker")) {
output.ignore(methodNode.instructions.getFirst(),
methodNode.instructions.getLast());
}
}
private static class Matcher extends AbstractMatcher {
public boolean matchSerializer(final MethodNode methodNode,
final int lineNumber) {
cursor = methodNode.instructions.getFirst();
nextIs(Opcodes.GETSTATIC);
final FieldInsnNode getStaticInstruction = (FieldInsnNode) cursor;
if (cursor == null) {
return false;
}
final AbstractInsnNode lineNumberInstruction = cursor.getPrevious();
nextIsType(Opcodes.CHECKCAST, "kotlinx/serialization/KSerializer");
nextIs(Opcodes.ARETURN);
return cursor != null
&& getStaticInstruction.name.equals("INSTANCE")
&& (lineNumberInstruction instanceof LineNumberNode)
&& (lineNumber == -1
|| lineNumber == ((LineNumberNode) lineNumberInstruction).line);
}
private boolean matchCachedSerializer(final MethodNode methodNode,
final String className) {
firstIsALoad0(methodNode);
nextIsInvoke(Opcodes.INVOKESPECIAL, className,
"get$cachedSerializer",
"()Lkotlinx/serialization/KSerializer;");
nextIs(Opcodes.ARETURN);
return cursor != null;
}
}
}