[KxSerialization] Fix NPE if sealed class has self-referencing property
Fixes Kotlin/kotlinx.serialization#2294 If the serializable class contains itself as a property, and its serializer is not an object (for example, for a sealed class), in this case there is a race in the initialization of the serializer and child serializers. To avoid this the serialization class should not cache its own serializer as a child. Merge-request: KT-MR-10019 Merged-by: Sergey Shanshin <Sergey.Shanshin@jetbrains.com>
This commit is contained in:
committed by
Space Team
parent
5a95d919c7
commit
c9bde57e44
+12
-2
@@ -429,14 +429,24 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun IrClass.createCachedChildSerializers(
|
fun IrClass.createCachedChildSerializers(
|
||||||
|
serializableClass: IrClass,
|
||||||
serializableProperties: List<IrSerializableProperty>
|
serializableProperties: List<IrSerializableProperty>
|
||||||
): List<IrExpression?> {
|
): List<IrExpression?> {
|
||||||
return DeclarationIrBuilder(compilerContext, symbol).run {
|
return DeclarationIrBuilder(compilerContext, symbol).run {
|
||||||
serializableProperties.map { cacheableChildSerializerInstance(it) }
|
serializableProperties.map { cacheableChildSerializerInstance(serializableClass, it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun IrBuilderWithScope.cacheableChildSerializerInstance(property: IrSerializableProperty): IrExpression? {
|
private fun IrBuilderWithScope.cacheableChildSerializerInstance(
|
||||||
|
serializableClass: IrClass,
|
||||||
|
property: IrSerializableProperty
|
||||||
|
): IrExpression? {
|
||||||
|
// to avoid a cyclical dependency between the serializer cache and the cache of child serializers,
|
||||||
|
// the class should not cache its serializer as a child
|
||||||
|
if (serializableClass.symbol == property.type.classifier) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
val serializer = getIrSerialTypeInfo(property, compilerContext).serializer ?: return null
|
val serializer = getIrSerialTypeInfo(property, compilerContext).serializer ?: return null
|
||||||
if (serializer.owner.kind == ClassKind.OBJECT) return null
|
if (serializer.owner.kind == ClassKind.OBJECT) return null
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -55,7 +55,7 @@ class SerializableIrGenerator(
|
|||||||
private val IrClass.isInternalSerializable: Boolean get() = kind == ClassKind.CLASS && hasSerializableOrMetaAnnotationWithoutArgs()
|
private val IrClass.isInternalSerializable: Boolean get() = kind == ClassKind.CLASS && hasSerializableOrMetaAnnotationWithoutArgs()
|
||||||
|
|
||||||
private val cachedChildSerializers: List<IrExpression?> =
|
private val cachedChildSerializers: List<IrExpression?> =
|
||||||
irClass.companionObject()!!.createCachedChildSerializers(properties.serializableProperties)
|
irClass.companionObject()!!.createCachedChildSerializers(irClass, properties.serializableProperties)
|
||||||
|
|
||||||
private val cachedChildSerializersProperty: IrProperty? =
|
private val cachedChildSerializersProperty: IrProperty? =
|
||||||
irClass.companionObject()!!.addCachedChildSerializersProperty(cachedChildSerializers)
|
irClass.companionObject()!!.addCachedChildSerializersProperty(cachedChildSerializers)
|
||||||
|
|||||||
+1
-1
@@ -89,7 +89,7 @@ open class SerializerIrGenerator(
|
|||||||
|
|
||||||
// non-object serializers which can be cached
|
// non-object serializers which can be cached
|
||||||
private val cacheableChildSerializers by lazy {
|
private val cacheableChildSerializers by lazy {
|
||||||
serializableIrClass.createCachedChildSerializers(properties.serializableProperties).map { it != null }
|
serializableIrClass.createCachedChildSerializers(serializableIrClass, properties.serializableProperties).map { it != null }
|
||||||
}
|
}
|
||||||
|
|
||||||
// null if was not found — we're in FIR
|
// null if was not found — we're in FIR
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// TARGET_BACKEND: JVM_IR
|
||||||
|
|
||||||
|
// WITH_STDLIB
|
||||||
|
|
||||||
|
import kotlinx.serialization.*
|
||||||
|
|
||||||
|
// Serializers for classes containing themselves as properties should be cached correctly
|
||||||
|
@Serializable
|
||||||
|
class Plain(val p: Plain? = null)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
sealed class Sealed(val p: Sealed? = null)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
open class Open(val p: Open? = null)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
abstract class Abstract(val p: Abstract? = null)
|
||||||
|
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
// A correctly cached class must be initialized correctly in order to exclude cyclic nesting of caches
|
||||||
|
Plain.serializer()
|
||||||
|
Sealed.serializer()
|
||||||
|
Open.serializer()
|
||||||
|
Abstract.serializer()
|
||||||
|
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
+6
@@ -33,6 +33,12 @@ public class SerializationFirLightTreeBlackBoxTestGenerated extends AbstractSeri
|
|||||||
runTest("plugins/kotlinx-serialization/testData/boxIr/annotationsOnFile.kt");
|
runTest("plugins/kotlinx-serialization/testData/boxIr/annotationsOnFile.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("caching.kt")
|
||||||
|
public void testCaching() throws Exception {
|
||||||
|
runTest("plugins/kotlinx-serialization/testData/boxIr/caching.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("classSerializerAsObject.kt")
|
@TestMetadata("classSerializerAsObject.kt")
|
||||||
public void testClassSerializerAsObject() throws Exception {
|
public void testClassSerializerAsObject() throws Exception {
|
||||||
|
|||||||
+6
@@ -31,6 +31,12 @@ public class SerializationIrBoxTestGenerated extends AbstractSerializationIrBoxT
|
|||||||
runTest("plugins/kotlinx-serialization/testData/boxIr/annotationsOnFile.kt");
|
runTest("plugins/kotlinx-serialization/testData/boxIr/annotationsOnFile.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("caching.kt")
|
||||||
|
public void testCaching() throws Exception {
|
||||||
|
runTest("plugins/kotlinx-serialization/testData/boxIr/caching.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("classSerializerAsObject.kt")
|
@TestMetadata("classSerializerAsObject.kt")
|
||||||
public void testClassSerializerAsObject() throws Exception {
|
public void testClassSerializerAsObject() throws Exception {
|
||||||
|
|||||||
Reference in New Issue
Block a user