Skip to content

Commit bfeff81

Browse files
authored
Presence of (transient) delegated field in the serialized class breaks deserialization (JetBrains#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 Kotlin/kotlinx.serialization#2091
1 parent 63a5a74 commit bfeff81

File tree

4 files changed

+122
-1
lines changed

4 files changed

+122
-1
lines changed

plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableIrGenerator.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ class SerializableIrGenerator(
8282
it is IrProperty && it.backingField != null -> {
8383
if (it in serialDescs) {
8484
current = it
85-
} else if (it.backingField?.initializer != null) {
85+
} else if (it.backingField?.initializer != null && !it.isDelegated) {
8686
// skip transient lateinit or deferred properties (with null initializer)
8787
val expression = initializerAdapter(it.backingField!!.initializer!!)
8888

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// TARGET_BACKEND: JVM_IR
2+
3+
// WITH_STDLIB
4+
5+
import kotlinx.serialization.*
6+
import kotlinx.serialization.descriptors.*
7+
import kotlinx.serialization.encoding.*
8+
import kotlinx.serialization.json.*
9+
import kotlin.reflect.KProperty
10+
import kotlin.properties.*
11+
12+
@Serializable
13+
data class SimpleDTO(
14+
val realProp: Int,
15+
) {
16+
@Transient
17+
private val additionalProperties: Map<String, Int> = mapOf("delegatedProp" to 123)
18+
val delegatedProp: Int? by additionalProperties
19+
}
20+
21+
// optimized properties must also work
22+
// https://kotlinlang.org/docs/whatsnew1720.html#more-optimized-cases-of-delegated-properties
23+
// A named object:
24+
object NamedObject {
25+
operator fun getValue(thisRef: Any?, property: KProperty<*>): String = "test-string"
26+
}
27+
28+
@Serializable
29+
data class DelegatedByObjectProperty(
30+
val realProp: Int
31+
) {
32+
val delegatedProp: String by NamedObject
33+
}
34+
35+
// A final val property with a backing field and a default getter in the same module:
36+
val impl: ReadOnlyProperty<Any?, String> = object : ReadOnlyProperty<Any?, String> {
37+
override operator fun getValue(thisRef: Any?, property: KProperty<*>): String = "test-string"
38+
}
39+
40+
@Serializable
41+
class DelegatedByFinalVal(
42+
val realProp: Int
43+
) {
44+
val delegatedProp: String by impl
45+
}
46+
47+
// A var property with a backing field and a default getter in the same module:
48+
var implvar: ReadWriteProperty<Any?, String> = object : ReadWriteProperty<Any?, String> {
49+
private var value = "test-string"
50+
override operator fun getValue(thisRef: Any?, property: KProperty<*>): String = value
51+
override operator fun setValue(
52+
thisRef: Any?,
53+
property: KProperty<*>,
54+
value: String) { this.value = value }
55+
}
56+
57+
@Serializable
58+
class DelegatedByVar(
59+
val realProp: Int
60+
) {
61+
var delegatedProp: String by implvar
62+
}
63+
64+
// delegated by this
65+
@Serializable
66+
class DelegatedByThis(val realProp: Int) {
67+
operator fun getValue(thisRef: Any?, property: KProperty<*>) = "test-string"
68+
69+
val delegatedProp by this
70+
}
71+
72+
fun box(): String {
73+
val simpleDTO = SimpleDTO(123)
74+
val simpleDTOJsonStr = Json.encodeToString(simpleDTO)
75+
val simpleDTODecoded = Json.decodeFromString<SimpleDTO>(simpleDTOJsonStr)
76+
if (simpleDTOJsonStr != """{"realProp":123}""") return simpleDTOJsonStr
77+
if (simpleDTODecoded.delegatedProp != simpleDTO.delegatedProp) return "SimpleDTO Delegate is incorrect!"
78+
if (simpleDTODecoded.realProp !== 123) return "SimpleDTO Deserialization failed"
79+
80+
val objProp = DelegatedByObjectProperty(123)
81+
val objPropJsonStr = Json.encodeToString(objProp)
82+
val objPropDecoded = Json.decodeFromString<DelegatedByObjectProperty>(objPropJsonStr)
83+
if (objPropJsonStr != """{"realProp":123}""") return simpleDTOJsonStr
84+
if (objPropDecoded.delegatedProp != objProp.delegatedProp) return "DelegatedByObjectProperty Delegate is incorrect!"
85+
if (objPropDecoded.realProp !== 123) return "DelegatedByObjectProperty Deserialization failed"
86+
87+
val byFinal = DelegatedByFinalVal(123)
88+
val byFinalJsonStr = Json.encodeToString(byFinal)
89+
val byFinalDecoded = Json.decodeFromString<DelegatedByObjectProperty>(byFinalJsonStr)
90+
if (byFinalJsonStr != """{"realProp":123}""") return simpleDTOJsonStr
91+
if (byFinalDecoded.delegatedProp != byFinal.delegatedProp) return "DelegatedByFinalVal Delegate is incorrect!"
92+
if (byFinalDecoded.realProp !== 123) return "DelegatedByFinalVal Deserialization failed"
93+
94+
val byVar = DelegatedByVar(123)
95+
val byVarJsonStr = Json.encodeToString(byVar)
96+
val byVarDecoded = Json.decodeFromString<DelegatedByObjectProperty>(byVarJsonStr)
97+
if (byVarJsonStr != """{"realProp":123}""") return simpleDTOJsonStr
98+
if (byVarDecoded.delegatedProp != byVar.delegatedProp) return "DelegatedByVar Delegate is incorrect!"
99+
if (byVarDecoded.realProp !== 123) return "DelegatedByVar Deserialization failed"
100+
101+
val byThisExp = DelegatedByThis(123)
102+
val byThisJsonStr = Json.encodeToString(byThisExp)
103+
val byThisDecoded = Json.decodeFromString<DelegatedByObjectProperty>(byThisJsonStr)
104+
if (byThisJsonStr != """{"realProp":123}""") return simpleDTOJsonStr
105+
if (byThisDecoded.delegatedProp != byThisExp.delegatedProp) return "DelegatedByThis Delegate is incorrect!"
106+
if (byThisDecoded.realProp !== 123) return "DelegatedByThis Deserialization failed"
107+
108+
return "OK"
109+
}

plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirLightTreeBlackBoxTestGenerated.java

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationIrBoxTestGenerated.java

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)