From bfeff81867344cfc577217db728c885ff92d06f4 Mon Sep 17 00:00:00 2001 From: AngryGami Date: Wed, 26 Apr 2023 12:28:57 +0200 Subject: [PATCH] Presence of (transient) delegated field in the serialized class breaks deserialization (#5103) Do not include delegated field into generated constructor even though it might have backing field. Test that shows issue was added. Properties that are explicitly marked as delegated are now excluded. Fixes https://github.com/Kotlin/kotlinx.serialization/issues/2091 --- .../backend/ir/SerializableIrGenerator.kt | 2 +- .../testData/boxIr/delegatedProperty.kt | 109 ++++++++++++++++++ ...tionFirLightTreeBlackBoxTestGenerated.java | 6 + .../SerializationIrBoxTestGenerated.java | 6 + 4 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 plugins/kotlinx-serialization/testData/boxIr/delegatedProperty.kt diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableIrGenerator.kt b/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableIrGenerator.kt index 36c0b96da16..99c0cff7dc8 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableIrGenerator.kt +++ b/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableIrGenerator.kt @@ -82,7 +82,7 @@ class SerializableIrGenerator( it is IrProperty && it.backingField != null -> { if (it in serialDescs) { current = it - } else if (it.backingField?.initializer != null) { + } else if (it.backingField?.initializer != null && !it.isDelegated) { // skip transient lateinit or deferred properties (with null initializer) val expression = initializerAdapter(it.backingField!!.initializer!!) diff --git a/plugins/kotlinx-serialization/testData/boxIr/delegatedProperty.kt b/plugins/kotlinx-serialization/testData/boxIr/delegatedProperty.kt new file mode 100644 index 00000000000..7161283c291 --- /dev/null +++ b/plugins/kotlinx-serialization/testData/boxIr/delegatedProperty.kt @@ -0,0 +1,109 @@ +// TARGET_BACKEND: JVM_IR + +// WITH_STDLIB + +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.json.* +import kotlin.reflect.KProperty +import kotlin.properties.* + +@Serializable +data class SimpleDTO( + val realProp: Int, +) { + @Transient + private val additionalProperties: Map = mapOf("delegatedProp" to 123) + val delegatedProp: Int? by additionalProperties +} + +// optimized properties must also work +// https://kotlinlang.org/docs/whatsnew1720.html#more-optimized-cases-of-delegated-properties +// A named object: +object NamedObject { + operator fun getValue(thisRef: Any?, property: KProperty<*>): String = "test-string" +} + +@Serializable +data class DelegatedByObjectProperty( + val realProp: Int +) { + val delegatedProp: String by NamedObject +} + +// A final val property with a backing field and a default getter in the same module: +val impl: ReadOnlyProperty = object : ReadOnlyProperty { + override operator fun getValue(thisRef: Any?, property: KProperty<*>): String = "test-string" +} + +@Serializable +class DelegatedByFinalVal( + val realProp: Int +) { + val delegatedProp: String by impl +} + +// A var property with a backing field and a default getter in the same module: +var implvar: ReadWriteProperty = object : ReadWriteProperty { + private var value = "test-string" + override operator fun getValue(thisRef: Any?, property: KProperty<*>): String = value + override operator fun setValue( + thisRef: Any?, + property: KProperty<*>, + value: String) { this.value = value } +} + +@Serializable +class DelegatedByVar( + val realProp: Int +) { + var delegatedProp: String by implvar +} + +// delegated by this +@Serializable +class DelegatedByThis(val realProp: Int) { + operator fun getValue(thisRef: Any?, property: KProperty<*>) = "test-string" + + val delegatedProp by this +} + +fun box(): String { + val simpleDTO = SimpleDTO(123) + val simpleDTOJsonStr = Json.encodeToString(simpleDTO) + val simpleDTODecoded = Json.decodeFromString(simpleDTOJsonStr) + if (simpleDTOJsonStr != """{"realProp":123}""") return simpleDTOJsonStr + if (simpleDTODecoded.delegatedProp != simpleDTO.delegatedProp) return "SimpleDTO Delegate is incorrect!" + if (simpleDTODecoded.realProp !== 123) return "SimpleDTO Deserialization failed" + + val objProp = DelegatedByObjectProperty(123) + val objPropJsonStr = Json.encodeToString(objProp) + val objPropDecoded = Json.decodeFromString(objPropJsonStr) + if (objPropJsonStr != """{"realProp":123}""") return simpleDTOJsonStr + if (objPropDecoded.delegatedProp != objProp.delegatedProp) return "DelegatedByObjectProperty Delegate is incorrect!" + if (objPropDecoded.realProp !== 123) return "DelegatedByObjectProperty Deserialization failed" + + val byFinal = DelegatedByFinalVal(123) + val byFinalJsonStr = Json.encodeToString(byFinal) + val byFinalDecoded = Json.decodeFromString(byFinalJsonStr) + if (byFinalJsonStr != """{"realProp":123}""") return simpleDTOJsonStr + if (byFinalDecoded.delegatedProp != byFinal.delegatedProp) return "DelegatedByFinalVal Delegate is incorrect!" + if (byFinalDecoded.realProp !== 123) return "DelegatedByFinalVal Deserialization failed" + + val byVar = DelegatedByVar(123) + val byVarJsonStr = Json.encodeToString(byVar) + val byVarDecoded = Json.decodeFromString(byVarJsonStr) + if (byVarJsonStr != """{"realProp":123}""") return simpleDTOJsonStr + if (byVarDecoded.delegatedProp != byVar.delegatedProp) return "DelegatedByVar Delegate is incorrect!" + if (byVarDecoded.realProp !== 123) return "DelegatedByVar Deserialization failed" + + val byThisExp = DelegatedByThis(123) + val byThisJsonStr = Json.encodeToString(byThisExp) + val byThisDecoded = Json.decodeFromString(byThisJsonStr) + if (byThisJsonStr != """{"realProp":123}""") return simpleDTOJsonStr + if (byThisDecoded.delegatedProp != byThisExp.delegatedProp) return "DelegatedByThis Delegate is incorrect!" + if (byThisDecoded.realProp !== 123) return "DelegatedByThis Deserialization failed" + + return "OK" +} \ No newline at end of file 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 ed7befd0d05..6ec2466bd18 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 @@ -69,6 +69,12 @@ public class SerializationFirLightTreeBlackBoxTestGenerated extends AbstractSeri runTest("plugins/kotlinx-serialization/testData/boxIr/delegatedInterface.kt"); } + @Test + @TestMetadata("delegatedProperty.kt") + public void testDelegatedProperty() throws Exception { + runTest("plugins/kotlinx-serialization/testData/boxIr/delegatedProperty.kt"); + } + @Test @TestMetadata("enumsAreCached.kt") public void testEnumsAreCached() throws Exception { 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 9f48ddb9078..0603fc9c26d 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 @@ -67,6 +67,12 @@ public class SerializationIrBoxTestGenerated extends AbstractSerializationIrBoxT runTest("plugins/kotlinx-serialization/testData/boxIr/delegatedInterface.kt"); } + @Test + @TestMetadata("delegatedProperty.kt") + public void testDelegatedProperty() throws Exception { + runTest("plugins/kotlinx-serialization/testData/boxIr/delegatedProperty.kt"); + } + @Test @TestMetadata("enumsAreCached.kt") public void testEnumsAreCached() throws Exception {