[KxSerialization] Fixed access private custom serializer on property
When a custom serializer is specified on a type and this type is used in a property of another serializable class, then on the JVM this leads to an error accessing the custom serializer class - because it is private and located in another package. Fixes https://github.com/Kotlin/kotlinx.serialization/issues/2495 Merge-request: KT-MR-12877 Merged-by: Sergei Shanshin <Sergey.Shanshin@jetbrains.com>
This commit is contained in:
committed by
Space Team
parent
ab76f94aa7
commit
96d7dc4fa6
+51
-5
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.backend.jvm.functionByName
|
|||||||
import org.jetbrains.kotlin.backend.jvm.ir.fileParent
|
import org.jetbrains.kotlin.backend.jvm.ir.fileParent
|
||||||
import org.jetbrains.kotlin.backend.jvm.ir.representativeUpperBound
|
import org.jetbrains.kotlin.backend.jvm.ir.representativeUpperBound
|
||||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||||
|
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
||||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||||
import org.jetbrains.kotlin.ir.builders.*
|
import org.jetbrains.kotlin.ir.builders.*
|
||||||
import org.jetbrains.kotlin.ir.declarations.*
|
import org.jetbrains.kotlin.ir.declarations.*
|
||||||
@@ -280,6 +281,7 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override
|
|||||||
compilerContext,
|
compilerContext,
|
||||||
property.type,
|
property.type,
|
||||||
property.genericIndex,
|
property.genericIndex,
|
||||||
|
property.ir.parentClassOrNull,
|
||||||
genericGetter
|
genericGetter
|
||||||
)
|
)
|
||||||
val (functionToCall, args: List<IrExpression>) = if (innerSerial != null) whenHaveSerializer(innerSerial, sti) else whenDoNot(sti)
|
val (functionToCall, args: List<IrExpression>) = if (innerSerial != null) whenHaveSerializer(innerSerial, sti) else whenDoNot(sti)
|
||||||
@@ -287,7 +289,7 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override
|
|||||||
return irInvoke(encoder, functionToCall, typeArguments = typeArgs, valueArguments = args, returnTypeHint = returnTypeHint)
|
return irInvoke(encoder, functionToCall, typeArguments = typeArgs, valueArguments = args, returnTypeHint = returnTypeHint)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun IrBuilderWithScope.callSerializerFromCompanion(
|
private fun IrBuilderWithScope.callSerializerFromCompanion(
|
||||||
thisIrType: IrSimpleType,
|
thisIrType: IrSimpleType,
|
||||||
typeArgs: List<IrType>,
|
typeArgs: List<IrType>,
|
||||||
args: List<IrExpression>,
|
args: List<IrExpression>,
|
||||||
@@ -323,6 +325,26 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun IrBuilderWithScope.callSerializerFromObject(
|
||||||
|
thisIrType: IrSimpleType,
|
||||||
|
args: List<IrExpression>,
|
||||||
|
// type parameters not allowed for object class, so its missed
|
||||||
|
): IrExpression? {
|
||||||
|
val baseClass = thisIrType.getClass() ?: return null
|
||||||
|
val serializerProviderFunction = baseClass.declarations.singleOrNull {
|
||||||
|
it is IrFunction && it.name == SerialEntityNames.SERIALIZER_PROVIDER_NAME && it.valueParameters.size == baseClass.typeParameters.size
|
||||||
|
} ?: return null
|
||||||
|
|
||||||
|
with(serializerProviderFunction as IrFunction) {
|
||||||
|
return irInvoke(
|
||||||
|
irGetObject(baseClass),
|
||||||
|
symbol,
|
||||||
|
emptyList(),
|
||||||
|
args.takeIf { it.size == valueParameters.size }.orEmpty()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Does not use sti and therefore does not perform encoder calls optimization
|
// Does not use sti and therefore does not perform encoder calls optimization
|
||||||
fun IrBuilderWithScope.serializerTower(
|
fun IrBuilderWithScope.serializerTower(
|
||||||
generator: SerializerIrGenerator,
|
generator: SerializerIrGenerator,
|
||||||
@@ -346,7 +368,8 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override
|
|||||||
serializerClassSymbol,
|
serializerClassSymbol,
|
||||||
compilerContext,
|
compilerContext,
|
||||||
property.type,
|
property.type,
|
||||||
genericIndex = property.genericIndex
|
genericIndex = property.genericIndex,
|
||||||
|
property.ir.parentClassOrNull,
|
||||||
) { it, _ ->
|
) { it, _ ->
|
||||||
val ir = generator.localSerializersFieldsDescriptors[it]
|
val ir = generator.localSerializersFieldsDescriptors[it]
|
||||||
irGetField(irGet(dispatchReceiverParameter), ir.backingField!!)
|
irGetField(irGet(dispatchReceiverParameter), ir.backingField!!)
|
||||||
@@ -469,6 +492,7 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override
|
|||||||
compilerContext,
|
compilerContext,
|
||||||
property.type,
|
property.type,
|
||||||
null,
|
null,
|
||||||
|
serializableClass,
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -489,7 +513,8 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override
|
|||||||
pluginContext: SerializationPluginContext,
|
pluginContext: SerializationPluginContext,
|
||||||
kType: IrType,
|
kType: IrType,
|
||||||
genericIndex: Int? = null,
|
genericIndex: Int? = null,
|
||||||
genericGetter: ((Int, IrType) -> IrExpression)? = null
|
rootSerializableClass: IrClass? = null,
|
||||||
|
genericGetter: ((Int, IrType) -> IrExpression)? = null,
|
||||||
): IrExpression? {
|
): IrExpression? {
|
||||||
val nullableSerClass = compilerContext.referenceProperties(SerialEntityNames.wrapIntoNullableCallableId).single()
|
val nullableSerClass = compilerContext.referenceProperties(SerialEntityNames.wrapIntoNullableCallableId).single()
|
||||||
if (serializerClassOriginal == null) {
|
if (serializerClassOriginal == null) {
|
||||||
@@ -497,7 +522,26 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override
|
|||||||
return genericGetter?.invoke(genericIndex, kType)
|
return genericGetter?.invoke(genericIndex, kType)
|
||||||
}
|
}
|
||||||
if (serializerClassOriginal.owner.kind == ClassKind.OBJECT) {
|
if (serializerClassOriginal.owner.kind == ClassKind.OBJECT) {
|
||||||
return irGetObject(serializerClassOriginal)
|
val serializerClass = serializerClassOriginal.owner
|
||||||
|
|
||||||
|
val samePackage = serializerClass.packageFqName == rootSerializableClass?.packageFqName
|
||||||
|
|
||||||
|
// rootSerializableClass is only if the serializer is obtained for serializer getter
|
||||||
|
// In this case, the private serializer will always be located in the same package, otherwise a syntax error will occur.
|
||||||
|
return if (rootSerializableClass == null || serializerClass.visibility != DescriptorVisibilities.PRIVATE || samePackage) {
|
||||||
|
// we can access the serializer object directly only if it is not private, or is located in the same package as the class using it
|
||||||
|
irGetObject(serializerClassOriginal)
|
||||||
|
} else {
|
||||||
|
val simpleType = (kType as? IrSimpleType) ?: error("Don't know how to work with type ${kType.classFqName}")
|
||||||
|
|
||||||
|
if (simpleType.getClass()?.isObject == true) {
|
||||||
|
callSerializerFromObject(simpleType, emptyList())
|
||||||
|
?: error("Can't get serializer from 'serializer()' function for object ${kType.classFqName}")
|
||||||
|
} else {
|
||||||
|
callSerializerFromCompanion(simpleType, emptyList(), emptyList(), serializerClassOriginal.owner.classId)
|
||||||
|
?: error("Can't get serializer from companion's 'serializer()' function for type ${kType.classFqName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fun instantiate(serializer: IrClassSymbol?, type: IrType): IrExpression? {
|
fun instantiate(serializer: IrClassSymbol?, type: IrType): IrExpression? {
|
||||||
val expr = serializerInstance(
|
val expr = serializerInstance(
|
||||||
@@ -505,6 +549,7 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override
|
|||||||
pluginContext,
|
pluginContext,
|
||||||
type,
|
type,
|
||||||
type.genericIndex,
|
type.genericIndex,
|
||||||
|
rootSerializableClass,
|
||||||
genericGetter
|
genericGetter
|
||||||
) ?: return null
|
) ?: return null
|
||||||
return wrapWithNullableSerializerIfNeeded(type, expr, nullableSerClass)
|
return wrapWithNullableSerializerIfNeeded(type, expr, nullableSerClass)
|
||||||
@@ -593,7 +638,8 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override
|
|||||||
serializer,
|
serializer,
|
||||||
pluginContext,
|
pluginContext,
|
||||||
type,
|
type,
|
||||||
type.genericIndex
|
type.genericIndex,
|
||||||
|
rootSerializableClass
|
||||||
) { _, genericType ->
|
) { _, genericType ->
|
||||||
serializerInstance(
|
serializerInstance(
|
||||||
pluginContext.referenceClass(polymorphicSerializerId),
|
pluginContext.referenceClass(polymorphicSerializerId),
|
||||||
|
|||||||
+2
-1
@@ -342,7 +342,8 @@ class SerializableIrGenerator(
|
|||||||
serial,
|
serial,
|
||||||
compilerContext,
|
compilerContext,
|
||||||
arg.typeOrNull!!,
|
arg.typeOrNull!!,
|
||||||
genericIdx
|
genericIdx,
|
||||||
|
irClass
|
||||||
) { it, _ ->
|
) { it, _ ->
|
||||||
irGet(writeSelfFunction.valueParameters[3 + it])
|
irGet(writeSelfFunction.valueParameters[3 + it])
|
||||||
}!!
|
}!!
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
// TARGET_BACKEND: JVM_IR
|
||||||
|
|
||||||
|
// WITH_STDLIB
|
||||||
|
|
||||||
|
// FILE: serializer.kt
|
||||||
|
|
||||||
|
package a
|
||||||
|
|
||||||
|
import kotlinx.serialization.*
|
||||||
|
import kotlinx.serialization.encoding.*
|
||||||
|
import kotlinx.serialization.descriptors.*
|
||||||
|
import kotlinx.serialization.json.*
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
@Serializable(DataSerializer::class)
|
||||||
|
data class Data(
|
||||||
|
val i: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@Serializer(forClass = Data::class)
|
||||||
|
private object DataSerializer
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
object SerializableObject
|
||||||
|
|
||||||
|
private object CustomSerializer : KSerializer<SerializableObject> {
|
||||||
|
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("My.SerializableObject", PrimitiveKind.STRING)
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: SerializableObject) {
|
||||||
|
encoder.encodeString("custom.serializer")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): SerializableObject {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class DataWithObject(
|
||||||
|
// property is object and have custom private serializer
|
||||||
|
@Serializable(CustomSerializer::class) val obj: SerializableObject
|
||||||
|
)
|
||||||
|
|
||||||
|
class Outer {
|
||||||
|
private object CustomPrimitiveSerializer : KSerializer<JsonPrimitive> {
|
||||||
|
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("My.CustomPrimitiveSerializer", PrimitiveKind.STRING)
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: JsonPrimitive) {
|
||||||
|
encoder.encodeString("custom.primitive.serializer")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): JsonPrimitive {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun serialize(): String {
|
||||||
|
@Serializable
|
||||||
|
data class Wrapper(
|
||||||
|
// property in local class with private custom serializer
|
||||||
|
@Serializable(CustomPrimitiveSerializer::class)
|
||||||
|
val value: JsonPrimitive,
|
||||||
|
)
|
||||||
|
|
||||||
|
return Json.encodeToString(Wrapper(JsonNull))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// FILE: holder.kt
|
||||||
|
|
||||||
|
package b
|
||||||
|
|
||||||
|
import kotlinx.serialization.*
|
||||||
|
import kotlinx.serialization.encoding.*
|
||||||
|
import kotlinx.serialization.descriptors.*
|
||||||
|
import kotlinx.serialization.json.*
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.test.*
|
||||||
|
import a.Data
|
||||||
|
import a.DataWithObject
|
||||||
|
import a.SerializableObject
|
||||||
|
import a.Outer
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Holder(
|
||||||
|
val data: Data
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
val json = Json.encodeToString(Holder(Data(1)))
|
||||||
|
if (json != "{\"data\":{\"i\":1}}") return json
|
||||||
|
|
||||||
|
val jsonWithObject = Json.encodeToString(DataWithObject(SerializableObject))
|
||||||
|
if (jsonWithObject != "{\"obj\":\"custom.serializer\"}") return jsonWithObject
|
||||||
|
|
||||||
|
val jsonFromOuter = Outer().serialize()
|
||||||
|
if (jsonFromOuter != "{\"value\":\"custom.primitive.serializer\"}") return jsonFromOuter
|
||||||
|
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
+6
@@ -225,6 +225,12 @@ public class SerializationFirLightTreeBlackBoxTestGenerated extends AbstractSeri
|
|||||||
runTest("plugins/kotlinx-serialization/testData/boxIr/namedCompanions.kt");
|
runTest("plugins/kotlinx-serialization/testData/boxIr/namedCompanions.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("privateCustomSerializer.kt")
|
||||||
|
public void testPrivateCustomSerializer() throws Exception {
|
||||||
|
runTest("plugins/kotlinx-serialization/testData/boxIr/privateCustomSerializer.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("repeatableSerialInfo.kt")
|
@TestMetadata("repeatableSerialInfo.kt")
|
||||||
public void testRepeatableSerialInfo() throws Exception {
|
public void testRepeatableSerialInfo() throws Exception {
|
||||||
|
|||||||
+6
@@ -223,6 +223,12 @@ public class SerializationIrBoxTestGenerated extends AbstractSerializationIrBoxT
|
|||||||
runTest("plugins/kotlinx-serialization/testData/boxIr/namedCompanions.kt");
|
runTest("plugins/kotlinx-serialization/testData/boxIr/namedCompanions.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("privateCustomSerializer.kt")
|
||||||
|
public void testPrivateCustomSerializer() throws Exception {
|
||||||
|
runTest("plugins/kotlinx-serialization/testData/boxIr/privateCustomSerializer.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("repeatableSerialInfo.kt")
|
@TestMetadata("repeatableSerialInfo.kt")
|
||||||
public void testRepeatableSerialInfo() throws Exception {
|
public void testRepeatableSerialInfo() throws Exception {
|
||||||
|
|||||||
Reference in New Issue
Block a user