From 139e1223ea6b119e54ab031aea94ddbe12e0adea Mon Sep 17 00:00:00 2001 From: Dmitriy Novozhilov Date: Tue, 28 Nov 2023 17:55:34 +0200 Subject: [PATCH] [Serialization] Workaround registering static `writeSelf` method in metadata `IrGeneratedDeclarationsRegistrar` assumes that all generated functions are correct from a Kotlin point of view. But `writeSelf` method on JVM is a static method outside any object/companion object So to properly calculate containing class for this method we should generate a dispatch receiver parameter, register the method in metadata, and then remove the parameter (to make function static) --- .../compiler/backend/ir/IrPreGenerator.kt | 21 +++++++++++++------ .../boxIr/allConstructorsAccessible.kt | 19 +++++++++++++++++ ...tionFirLightTreeBlackBoxTestGenerated.java | 6 ++++++ .../SerializationIrBoxTestGenerated.java | 6 ++++++ 4 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 plugins/kotlinx-serialization/testData/boxIr/allConstructorsAccessible.kt diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrPreGenerator.kt b/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrPreGenerator.kt index 48b316982c9..4f17a00afa9 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrPreGenerator.kt +++ b/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrPreGenerator.kt @@ -15,8 +15,10 @@ import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.isSingleFieldValueClass import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.companionObject +import org.jetbrains.kotlin.ir.util.copyTo import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.platform.jvm.isJvm +import org.jetbrains.kotlin.resolve.annotations.JVM_STATIC_ANNOTATION_CLASS_ID import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginContext import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames import org.jetbrains.kotlinx.serialization.compiler.resolve.bitMaskSlotCount @@ -58,17 +60,17 @@ class IrPreGenerator( modality = Modality.FINAL origin = SERIALIZATION_PLUGIN_ORIGIN } - method.apply { - dispatchReceiverParameter = null // function is static - excludeFromJsExport() - } + method.excludeFromJsExport() - val typeParams = irClass.typeParameters.map { + val typeParamsMap = irClass.typeParameters.associateWith { method.addTypeParameter( it.name.asString(), compilerContext.irBuiltIns.anyNType ) } - val typeParamsAsArguments = typeParams.map { it.defaultType } + // Despite this function should be static in bytecode, registerFunctionAsMetadataVisible needs a correct dispatch receiver + // to handle function metadata correctly. This dispatchReceiverParameter will become `null` later. + method.dispatchReceiverParameter = irClass.thisReceiver?.copyTo(method, remapTypeMap = typeParamsMap) + val typeParamsAsArguments = typeParamsMap.values.map { it.defaultType } // object method.addValueParameter( @@ -96,7 +98,14 @@ class IrPreGenerator( SERIALIZATION_PLUGIN_ORIGIN ) } + + compilerContext.referenceClass(JVM_STATIC_ANNOTATION_CLASS_ID) + ?.let { method.annotations += method.createAnnotationCallWithoutArgs(it) } + compilerContext.metadataDeclarationRegistrar.registerFunctionAsMetadataVisible(method) + + // Make function static in bytecode (see JvmStaticAnnotationLowering.makeStatic). + method.dispatchReceiverParameter = null } private fun preGenerateDeserializationConstructorIfNeeded() { diff --git a/plugins/kotlinx-serialization/testData/boxIr/allConstructorsAccessible.kt b/plugins/kotlinx-serialization/testData/boxIr/allConstructorsAccessible.kt new file mode 100644 index 00000000000..062001bfd0c --- /dev/null +++ b/plugins/kotlinx-serialization/testData/boxIr/allConstructorsAccessible.kt @@ -0,0 +1,19 @@ +// TARGET_BACKEND: JVM_IR +// WITH_REFLECT + +import kotlinx.serialization.Serializable +import kotlin.reflect.jvm.javaConstructor +import kotlin.reflect.jvm.kotlinFunction + +@Serializable +data class Tuple2( + val _1: A1, + val _2: A2, +) + +fun box(): String { + val cls = Tuple2::class + val ctor = cls.constructors.single { it.parameters.size == 4 } + val kf = ctor.javaConstructor?.kotlinFunction + return if (kf.toString() == "fun ``(kotlin.Int, A1?, A2?, kotlinx.serialization.internal.SerializationConstructorMarker?): Tuple2") "OK" else "Fail: $kf" +} diff --git a/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirLightTreeBlackBoxTestGenerated.java b/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirLightTreeBlackBoxTestGenerated.java index 41297a542be..d56913877f4 100644 --- a/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirLightTreeBlackBoxTestGenerated.java +++ b/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirLightTreeBlackBoxTestGenerated.java @@ -22,6 +22,12 @@ public class SerializationFirLightTreeBlackBoxTestGenerated extends AbstractSeri @TestMetadata("plugins/kotlinx-serialization/testData/boxIr") @TestDataPath("$PROJECT_ROOT") public class BoxIr { + @Test + @TestMetadata("allConstructorsAccessible.kt") + public void testAllConstructorsAccessible() throws Exception { + runTest("plugins/kotlinx-serialization/testData/boxIr/allConstructorsAccessible.kt"); + } + @Test public void testAllFilesPresentInBoxIr() throws Exception { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("plugins/kotlinx-serialization/testData/boxIr"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); diff --git a/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationIrBoxTestGenerated.java b/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationIrBoxTestGenerated.java index 1bdd3359aef..326378ac386 100644 --- a/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationIrBoxTestGenerated.java +++ b/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationIrBoxTestGenerated.java @@ -20,6 +20,12 @@ import java.util.regex.Pattern; @TestMetadata("plugins/kotlinx-serialization/testData/boxIr") @TestDataPath("$PROJECT_ROOT") public class SerializationIrBoxTestGenerated extends AbstractSerializationIrBoxTest { + @Test + @TestMetadata("allConstructorsAccessible.kt") + public void testAllConstructorsAccessible() throws Exception { + runTest("plugins/kotlinx-serialization/testData/boxIr/allConstructorsAccessible.kt"); + } + @Test public void testAllFilesPresentInBoxIr() throws Exception { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("plugins/kotlinx-serialization/testData/boxIr"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);