From dcc784a3d2aefb4b542bdc5226bd425a8147e2eb Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 17 Jan 2022 17:47:26 +0300 Subject: [PATCH] Best-effort kotlin-reflect instantiation avoidance Fixes #1819 --- core/api/kotlinx-serialization-core.api | 1 - .../src/kotlinx/serialization/Serializers.kt | 1 - .../kotlinx/serialization/SerializersJvm.kt | 38 ++++++++----------- .../serialization/internal/Platform.kt | 7 ++++ .../features/SerializerByTypeTest.kt | 16 ++++---- 5 files changed, 31 insertions(+), 32 deletions(-) diff --git a/core/api/kotlinx-serialization-core.api b/core/api/kotlinx-serialization-core.api index f55f65544..9159db420 100644 --- a/core/api/kotlinx-serialization-core.api +++ b/core/api/kotlinx-serialization-core.api @@ -117,7 +117,6 @@ public final class kotlinx/serialization/SerializersKt { public static final fun serializer (Lkotlin/reflect/KType;)Lkotlinx/serialization/KSerializer; public static final fun serializer (Lkotlinx/serialization/modules/SerializersModule;Ljava/lang/reflect/Type;)Lkotlinx/serialization/KSerializer; public static final fun serializer (Lkotlinx/serialization/modules/SerializersModule;Lkotlin/reflect/KType;)Lkotlinx/serialization/KSerializer; - public static final fun serializerOrNull (Ljava/lang/Class;)Lkotlinx/serialization/KSerializer; public static final fun serializerOrNull (Ljava/lang/reflect/Type;)Lkotlinx/serialization/KSerializer; public static final fun serializerOrNull (Lkotlin/reflect/KClass;)Lkotlinx/serialization/KSerializer; public static final fun serializerOrNull (Lkotlin/reflect/KType;)Lkotlinx/serialization/KSerializer; diff --git a/core/commonMain/src/kotlinx/serialization/Serializers.kt b/core/commonMain/src/kotlinx/serialization/Serializers.kt index d24768f0c..a52118777 100644 --- a/core/commonMain/src/kotlinx/serialization/Serializers.kt +++ b/core/commonMain/src/kotlinx/serialization/Serializers.kt @@ -126,7 +126,6 @@ internal fun SerializersModule.reflectiveOrContextual(kClass: KClass? = EmptySerializersMod */ @ExperimentalSerializationApi public fun SerializersModule.serializer(type: Type): KSerializer = - serializerByJavaTypeImpl(type, failOnMissingTypeArgSerializer = true) ?: type.kclass().serializerNotRegistered() + serializerByJavaTypeImpl(type, failOnMissingTypeArgSerializer = true) ?: type.prettyClass().serializerNotRegistered() /** * Retrieves serializer for the given reflective Java [type] using @@ -74,19 +74,6 @@ public fun SerializersModule.serializer(type: Type): KSerializer = public fun SerializersModule.serializerOrNull(type: Type): KSerializer? = serializerByJavaTypeImpl(type, failOnMissingTypeArgSerializer = false) -/** - * Retrieves a [KSerializer] for the given [java.lang.Class] instance of the Kotlin type or returns `null` if none is found. - * The given class must be annotated with [Serializable] or be one of the built-in types. - * - * The API avoids instantiation of the corresponding [KClass][Class.kotlin] on the best-effort basis. - * - * This is a [Class] counterpart of [KClass.serializerOrNull] with all the restrictions the original function implies, - * including the general recommendation to avoid uses of this API. - */ -@InternalSerializationApi -public fun Class.serializerOrNull(): KSerializer? = - constructSerializerForGivenTypeArgs() ?: kotlin.builtinSerializerOrNull() - @OptIn(ExperimentalSerializationApi::class) private fun SerializersModule.serializerByJavaTypeImpl(type: Type, failOnMissingTypeArgSerializer: Boolean = true): KSerializer? = when (type) { @@ -126,8 +113,7 @@ private fun SerializersModule.serializerByJavaTypeImpl(type: Type, failOnMissing // probably we should deprecate this method because it can't differ nullable vs non-nullable types // since it uses Java TypeToken, not Kotlin one val varargs = argsSerializers.map { it as KSerializer } - (rootClass.kotlin.constructSerializerForGivenTypeArgs(*(varargs.toTypedArray())) as? KSerializer) - ?: reflectiveOrContextual(rootClass.kotlin as KClass, varargs) + reflectiveOrContextual(rootClass as Class, varargs) } } } @@ -143,10 +129,17 @@ private fun SerializersModule.typeSerializer(type: Class<*>, failOnMissingTypeAr val arraySerializer = ArraySerializer(eType.kotlin as KClass, s) arraySerializer as KSerializer } else { - reflectiveOrContextual(type.kotlin as KClass, emptyList()) + reflectiveOrContextual(type as Class, emptyList()) } } +@OptIn(ExperimentalSerializationApi::class) +private fun SerializersModule.reflectiveOrContextual(jClass: Class, typeArgumentsSerializers: List>): KSerializer? { + jClass.constructSerializerForGivenTypeArgs(*typeArgumentsSerializers.toTypedArray())?.let { return it } + val kClass = jClass.kotlin + return kClass.builtinSerializerOrNull() ?: getContextual(kClass, typeArgumentsSerializers) +} + @OptIn(ExperimentalSerializationApi::class) private fun SerializersModule.genericArraySerializer( type: GenericArrayType, @@ -167,11 +160,10 @@ private fun SerializersModule.genericArraySerializer( return ArraySerializer(kclass, serializer) as KSerializer } -private fun Type.kclass(): KClass<*> = when (val it = this) { - is KClass<*> -> it - is Class<*> -> it.kotlin - is ParameterizedType -> it.rawType.kclass() - is WildcardType -> it.upperBounds.first().kclass() - is GenericArrayType -> it.genericComponentType.kclass() +private fun Type.prettyClass(): Class<*> = when (val it = this) { + is Class<*> -> it + is ParameterizedType -> it.rawType.prettyClass() + is WildcardType -> it.upperBounds.first().prettyClass() + is GenericArrayType -> it.genericComponentType.prettyClass() else -> throw IllegalArgumentException("typeToken should be an instance of Class, GenericArray, ParametrizedType or WildcardType, but actual type is $it ${it::class}") } diff --git a/core/jvmMain/src/kotlinx/serialization/internal/Platform.kt b/core/jvmMain/src/kotlinx/serialization/internal/Platform.kt index 552ca5c0f..9dbb5a06d 100644 --- a/core/jvmMain/src/kotlinx/serialization/internal/Platform.kt +++ b/core/jvmMain/src/kotlinx/serialization/internal/Platform.kt @@ -28,6 +28,13 @@ internal actual fun ArrayList.toNativeArrayImpl(eClass: KCl internal actual fun KClass<*>.platformSpecificSerializerNotRegistered(): Nothing = serializerNotRegistered() +internal fun Class<*>.serializerNotRegistered(): Nothing { + throw SerializationException( + "Serializer for class '${simpleName}' is not found.\n" + + "Mark the class as @Serializable or provide the serializer explicitly." + ) +} + internal actual fun KClass.constructSerializerForGivenTypeArgs(vararg args: KSerializer): KSerializer? { return java.constructSerializerForGivenTypeArgs(*args) } diff --git a/formats/json/jvmTest/src/kotlinx/serialization/features/SerializerByTypeTest.kt b/formats/json/jvmTest/src/kotlinx/serialization/features/SerializerByTypeTest.kt index 770813674..5227ca43a 100644 --- a/formats/json/jvmTest/src/kotlinx/serialization/features/SerializerByTypeTest.kt +++ b/formats/json/jvmTest/src/kotlinx/serialization/features/SerializerByTypeTest.kt @@ -56,12 +56,17 @@ class SerializerByTypeTest { assertSerializedWithType(IntBoxToken, """{"a":42}""", b) } + @Test + fun testNestedGenericParameter() { + val b = Box(Box(239)) + assertSerializedWithType(typeTokenOf>>(), """{"a":{"a":239}}""", b) + } + @Test fun testArray() { val myArr = arrayOf("a", "b", "c") val token = myArr::class.java assertSerializedWithType(token, """["a","b","c"]""", myArr) - myArr::class.assertKClassSerializer() } @Test @@ -175,7 +180,6 @@ class SerializerByTypeTest { fun testNamedCompanion() { val namedCompanion = WithNamedCompanion(1) assertSerializedWithType(WithNamedCompanion::class.java, """{"a":1}""", namedCompanion) - WithNamedCompanion::class.assertKClassSerializer() } @Test @@ -183,7 +187,6 @@ class SerializerByTypeTest { val token = typeTokenOf() val serial = serializer(token) assertSame(Int.serializer() as KSerializer<*>, serial) - Int::class.assertKClassSerializer() } @Test @@ -191,7 +194,6 @@ class SerializerByTypeTest { val token = typeTokenOf() val serial = serializer(token) assertEquals(SerializableObject.serializer().descriptor, serial.descriptor) - SerializableObject::class.assertKClassSerializer() } @Suppress("UNCHECKED_CAST") @@ -276,9 +278,9 @@ class SerializerByTypeTest { assertFailsWithMessage("for class 'NonSerializable'") { serializer(typeTokenOf>()) } - } - private fun KClass.assertKClassSerializer() { - assertEquals(serializerOrNull(), java.serializerOrNull()) + assertFailsWithMessage("for class 'NonSerializable'") { + serializer(typeTokenOf>()) + } } }