From 090aec6b3be94453a2ac96d5b59334b50e6dbf5c Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Mon, 26 Sep 2022 19:00:48 +0200 Subject: [PATCH] Prohibit Array in @Serializable classes because it's impossible to correctly create array reflectively without knowing correct KClass. Fixes https://github.com/Kotlin/kotlinx.serialization/issues/1243 --- .../diagnostic/SerializationErrors.java | 2 ++ .../SerializationPluginDeclarationChecker.kt | 17 ++++++++++++++++- .../SerializationPluginErrorsRendering.kt | 4 ++++ .../fir/checkers/FirSerializationErrors.kt | 2 +- .../FirSerializationPluginClassChecker.kt | 15 +++++++++++++-- .../KtDefaultErrorMessagesSerialization.kt | 4 ++++ .../testData/diagnostics/GenericArrays.kt | 13 +++++++++++++ ...SerializationFirDiagnosticTestGenerated.java | 6 ++++++ ...ializationPluginDiagnosticTestGenerated.java | 6 ++++++ 9 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 plugins/kotlinx-serialization/testData/diagnostics/GenericArrays.kt diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationErrors.java b/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationErrors.java index 6e777b20eb5..ccb12fa42fb 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationErrors.java +++ b/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationErrors.java @@ -32,6 +32,8 @@ public interface SerializationErrors { DiagnosticFactory1 LOCAL_SERIALIZER_USAGE = DiagnosticFactory1.create(ERROR); DiagnosticFactory0 TRANSIENT_MISSING_INITIALIZER = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 GENERIC_ARRAY_ELEMENT_NOT_SUPPORTED = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 TRANSIENT_IS_REDUNDANT = DiagnosticFactory0.create(WARNING); DiagnosticFactory0 JSON_FORMAT_REDUNDANT_DEFAULT = DiagnosticFactory0.create(WARNING); DiagnosticFactory0 JSON_FORMAT_REDUNDANT = DiagnosticFactory0.create(WARNING); diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginDeclarationChecker.kt b/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginDeclarationChecker.kt index 0e1dabf1745..9d23744ae35 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginDeclarationChecker.kt +++ b/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginDeclarationChecker.kt @@ -69,7 +69,8 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker { val entry = classDescriptor.findAnnotationDeclaration(SerializationAnnotations.serializerAnnotationFqName) val inSameModule = trace.bindingContext[BindingContext.FQNAME_TO_CLASS_DESCRIPTOR, serializableDescriptor.fqNameUnsafe] != null - val diagnostic = if (inSameModule) SerializationErrors.EXTERNAL_CLASS_NOT_SERIALIZABLE else SerializationErrors.EXTERNAL_CLASS_IN_ANOTHER_MODULE + val diagnostic = + if (inSameModule) SerializationErrors.EXTERNAL_CLASS_NOT_SERIALIZABLE else SerializationErrors.EXTERNAL_CLASS_IN_ANOTHER_MODULE trace.report(diagnostic.on(entry ?: declaration, classDescriptor.defaultType, serializableKType)) } @@ -263,6 +264,7 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker { } } + private fun ClassDescriptor.isSerializableEnumWithMissingSerializer(): Boolean { if (kind != ClassKind.ENUM_CLASS) return false if (hasSerializableOrMetaAnnotation) return false @@ -344,10 +346,23 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker { generatorContextForAnalysis.checkTypeArguments(it.module, it.type, element, trace, propertyPsi) } else { generatorContextForAnalysis.checkType(it.module, it.type, ktType, trace, propertyPsi) + checkGenericArrayType(it.type, ktType, trace, propertyPsi) } } } + private fun checkGenericArrayType( + type: KotlinType, + ktType: KtTypeReference?, + trace: BindingTrace, + fallbackElement: PsiElement + ) { + if (KotlinBuiltIns.isArray(type) && type.arguments.first().type.genericIndex != null) { + // Array is unsupported, since we can't get T::class from KSerializer + trace.report(SerializationErrors.GENERIC_ARRAY_ELEMENT_NOT_SUPPORTED.on(ktType ?: fallbackElement)) + } + } + private fun AbstractSerialGenerator.checkTypeArguments( module: ModuleDescriptor, type: KotlinType, diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginErrorsRendering.kt b/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginErrorsRendering.kt index cab7951b6b0..19be51182a7 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginErrorsRendering.kt +++ b/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginErrorsRendering.kt @@ -95,6 +95,10 @@ object SerializationPluginErrorsRendering : DefaultErrorMessages.Extension { SerializationErrors.TRANSIENT_IS_REDUNDANT, "Property does not have backing field which makes it non-serializable and therefore @Transient is redundant" ) + MAP.put( + SerializationErrors.GENERIC_ARRAY_ELEMENT_NOT_SUPPORTED, + "Serialization of Arrays with generic type arguments is impossible because of unknown compile-time type." + ) MAP.put( SerializationErrors.JSON_FORMAT_REDUNDANT_DEFAULT, "Redundant creation of Json default format. Creating instances for each usage can be slow." diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/FirSerializationErrors.kt b/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/FirSerializationErrors.kt index ca35caec0b9..3518152e81a 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/FirSerializationErrors.kt +++ b/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/FirSerializationErrors.kt @@ -29,10 +29,10 @@ object FirSerializationErrors { val SERIALIZER_NULLABILITY_INCOMPATIBLE by error2() val SERIALIZER_TYPE_INCOMPATIBLE by warning3() val LOCAL_SERIALIZER_USAGE by error1() + val GENERIC_ARRAY_ELEMENT_NOT_SUPPORTED by error0() val TRANSIENT_MISSING_INITIALIZER by error0() val TRANSIENT_IS_REDUNDANT by warning0() - val INCORRECT_TRANSIENT by warning0() val REQUIRED_KOTLIN_TOO_HIGH by error3() diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/FirSerializationPluginClassChecker.kt b/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/FirSerializationPluginClassChecker.kt index 7385789b1b6..d1bb7ce5c7e 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/FirSerializationPluginClassChecker.kt +++ b/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/FirSerializationPluginClassChecker.kt @@ -204,9 +204,9 @@ object FirSerializationPluginClassChecker : FirClassChecker() { ) return false } - + if (!classSymbol.hasSerializableOrMetaAnnotation) return false - + if (classSymbol.isAnonymousObjectOrInsideIt) { reporter.reportOn(classSymbol.serializableOrMetaAnnotationSource, FirSerializationErrors.ANONYMOUS_OBJECTS_NOT_SUPPORTED) return false @@ -370,10 +370,21 @@ object FirSerializationPluginClassChecker : FirClassChecker() { checkSerializerNullability(propertyType, serializerType, source, reporter) } else { checkType(propertyType, source, reporter) + checkGenericArrayType(propertyType, source, reporter) } } } + context(CheckerContext) + private fun checkGenericArrayType(propertyType: ConeKotlinType, source: KtSourceElement?, reporter: DiagnosticReporter) { + if (propertyType.isNonPrimitiveArray && propertyType.typeArguments.first().type?.isTypeParameter == true) { + reporter.reportOn( + source, + FirSerializationErrors.GENERIC_ARRAY_ELEMENT_NOT_SUPPORTED, + ) + } + } + context(CheckerContext) @Suppress("IncorrectFormatting") // KTIJ-22227 private fun checkTypeArguments(type: ConeKotlinType, source: KtSourceElement?, reporter: DiagnosticReporter) { diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/KtDefaultErrorMessagesSerialization.kt b/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/KtDefaultErrorMessagesSerialization.kt index 910785e7726..9a566e962cf 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/KtDefaultErrorMessagesSerialization.kt +++ b/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/KtDefaultErrorMessagesSerialization.kt @@ -102,6 +102,10 @@ object KtDefaultErrorMessagesSerialization { FirSerializationErrors.INCORRECT_TRANSIENT, "@kotlin.jvm.Transient does not affect @Serializable classes. Please use @kotlinx.serialization.Transient instead." ) + put( + FirSerializationErrors.GENERIC_ARRAY_ELEMENT_NOT_SUPPORTED, + "Serialization of Arrays with generic type arguments is impossible because of unknown compile-time type." + ) put( FirSerializationErrors.REQUIRED_KOTLIN_TOO_HIGH, "Your current Kotlin version is {0}, while kotlinx.serialization core runtime {1} requires at least Kotlin {2}. " + diff --git a/plugins/kotlinx-serialization/testData/diagnostics/GenericArrays.kt b/plugins/kotlinx-serialization/testData/diagnostics/GenericArrays.kt new file mode 100644 index 00000000000..3f515ad9408 --- /dev/null +++ b/plugins/kotlinx-serialization/testData/diagnostics/GenericArrays.kt @@ -0,0 +1,13 @@ +// FIR_IDENTICAL +// WITH_STDLIB +// SKIP_TXT +import kotlinx.serialization.* + +@Serializable +class C(val values: IntArray) // OK + +@Serializable +class B(val values: Array) // OK + +@Serializable +class A(val values: Array) diff --git a/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirDiagnosticTestGenerated.java b/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirDiagnosticTestGenerated.java index 736babbe2fe..c6713fbc920 100644 --- a/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirDiagnosticTestGenerated.java +++ b/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirDiagnosticTestGenerated.java @@ -44,6 +44,12 @@ public class SerializationFirDiagnosticTestGenerated extends AbstractSerializati runTest("plugins/kotlinx-serialization/testData/diagnostics/ExternalSerializers.kt"); } + @Test + @TestMetadata("GenericArrays.kt") + public void testGenericArrays() throws Exception { + runTest("plugins/kotlinx-serialization/testData/diagnostics/GenericArrays.kt"); + } + @Test @TestMetadata("IncorrectTransient.kt") public void testIncorrectTransient() throws Exception { diff --git a/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationPluginDiagnosticTestGenerated.java b/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationPluginDiagnosticTestGenerated.java index a639a03a124..53ae0c9da2f 100644 --- a/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationPluginDiagnosticTestGenerated.java +++ b/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationPluginDiagnosticTestGenerated.java @@ -42,6 +42,12 @@ public class SerializationPluginDiagnosticTestGenerated extends AbstractSerializ runTest("plugins/kotlinx-serialization/testData/diagnostics/ExternalSerializers.kt"); } + @Test + @TestMetadata("GenericArrays.kt") + public void testGenericArrays() throws Exception { + runTest("plugins/kotlinx-serialization/testData/diagnostics/GenericArrays.kt"); + } + @Test @TestMetadata("IncorrectTransient.kt") public void testIncorrectTransient() throws Exception {