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
This commit is contained in:
AngryGami
2023-04-26 12:28:57 +02:00
committed by GitHub
parent 63a5a74613
commit bfeff81867
4 changed files with 122 additions and 1 deletions
@@ -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!!)
@@ -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<String, Int> = 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<Any?, String> = object : ReadOnlyProperty<Any?, String> {
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<Any?, String> = object : ReadWriteProperty<Any?, String> {
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<SimpleDTO>(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<DelegatedByObjectProperty>(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<DelegatedByObjectProperty>(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<DelegatedByObjectProperty>(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<DelegatedByObjectProperty>(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"
}
@@ -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 {
@@ -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 {