From a3e396e7e45c3a42f16df8655a2a507658723e48 Mon Sep 17 00:00:00 2001 From: Gleb Lukianets Date: Thu, 8 Dec 2022 14:03:45 +0000 Subject: [PATCH] [K/N] KT-53069: Guard against recursive generic type argument constraints during objc export Merge-request: KT-MR-7872 Merged-by: Gleb Lukianets --- .../objcexport/ObjCExportHeaderGenerator.kt | 68 ++++++---------- .../konan/objcexport/ObjCExportScope.kt | 80 +++++++++++++++++++ .../objcexport/ObjCExportTranslatorMobile.kt | 2 +- .../tests/objcexport/expectedLazy.h | 14 ++++ .../expectedLazyLegacySuspendUnit.h | 14 ++++ .../tests/objcexport/expectedLazyNoGenerics.h | 14 ++++ .../objcexport/recursiveGenericArguments.kt | 10 +++ .../recursiveGenericArguments.swift | 17 ++++ 8 files changed, 173 insertions(+), 46 deletions(-) create mode 100644 kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportScope.kt create mode 100644 kotlin-native/backend.native/tests/objcexport/recursiveGenericArguments.kt create mode 100644 kotlin-native/backend.native/tests/objcexport/recursiveGenericArguments.swift diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt index e4bcafcf35b..6ab6190e279 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt @@ -14,7 +14,6 @@ import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.name.ClassId -import org.jetbrains.kotlin.resolve.DataClassResolver import org.jetbrains.kotlin.resolve.constants.ArrayValue import org.jetbrains.kotlin.resolve.constants.KClassValue import org.jetbrains.kotlin.resolve.deprecation.DeprecationInfo @@ -261,7 +260,7 @@ internal class ObjCExportTranslatorImpl( val name = referenceClass(classDescriptor).objCName val members = buildMembers { - translatePlainMembers(declarations, ObjCNoneExportScope) + translatePlainMembers(declarations, ObjCRootExportScope) } return ObjCInterfaceImpl(name, categoryName = "Extensions", members = members) } @@ -271,7 +270,7 @@ internal class ObjCExportTranslatorImpl( // TODO: stop inheriting KotlinBase. val members = buildMembers { - translatePlainMembers(declarations, ObjCNoneExportScope) + translatePlainMembers(declarations, ObjCRootExportScope) } return objCInterface( name, @@ -342,7 +341,7 @@ internal class ObjCExportTranslatorImpl( ?.forEach { val selector = getSelector(it) if (selector !in presentConstructors) { - add { buildMethod(it, it, ObjCNoneExportScope, unavailable = true) } + add { buildMethod(it, it, ObjCRootExportScope, unavailable = true) } if (selector == "init") { add { ObjCMethod(null, false, ObjCInstanceType, listOf("new"), emptyList(), listOf("unavailable")) } @@ -383,7 +382,7 @@ internal class ObjCExportTranslatorImpl( } } ClassKind.ENUM_CLASS -> { - val type = mapType(descriptor.defaultType, ReferenceBridge, ObjCNoneExportScope) + val type = mapType(descriptor.defaultType, ReferenceBridge, ObjCRootExportScope) descriptor.enumEntries.forEach { val entryName = namer.getEnumEntrySelector(it) @@ -434,9 +433,9 @@ internal class ObjCExportTranslatorImpl( } internal fun createGenericExportScope(descriptor: ClassDescriptor): ObjCExportScope = if (objcGenerics) { - ObjCClassExportScope(descriptor, namer) + ObjCRootExportScope.deriveForClass(descriptor, namer) } else { - ObjCNoneExportScope + ObjCRootExportScope } private fun buildThrowableAsErrorMethod(): ObjCMethod { @@ -560,7 +559,7 @@ internal class ObjCExportTranslatorImpl( } } - translatePlainMembers(methods, properties, ObjCNoneExportScope) + translatePlainMembers(methods, properties, ObjCRootExportScope) } private fun StubBuilder>.translatePlainMembers(members: List, objCExportScope: ObjCExportScope) { @@ -922,12 +921,18 @@ internal class ObjCExportTranslatorImpl( } mostSpecificMatches.firstOrNull()?.let { - return it.mapper.mapType(it.type, this, objCExportScope) + try { + return it.mapper.mapType(it.type, this, objCExportScope.deriveForType(it.type)) + } catch (e: ObjCExportScope.RecursionBreachException) { + return ObjCIdType + } } - if(objcGenerics && kotlinType.isTypeParameter()){ - val genericTypeUsage = objCExportScope.getGenericTypeUsage(TypeUtils.getTypeParameterDescriptorOrNull(kotlinType)) - if(genericTypeUsage != null) + if (objcGenerics && kotlinType.isTypeParameter()) { + val genericTypeUsage = objCExportScope + .nearestScopeOfType() + ?.getGenericTypeUsage(TypeUtils.getTypeParameterDescriptorOrNull(kotlinType)) + if (genericTypeUsage != null) return genericTypeUsage } @@ -1014,7 +1019,9 @@ internal class ObjCExportTranslatorImpl( } else { mapReferenceType(functionType.getReturnTypeFromFunctionType(), objCExportScope) }, - parameterTypes.map { mapReferenceType(it, objCExportScope) } + parameterTypes.map { + mapReferenceType(it, objCExportScope) + } ) } @@ -1369,36 +1376,8 @@ internal fun ObjCExportNamer.ClassOrProtocolName.toNameAttributes(): List() - } - - override fun getGenericTypeUsage(typeParameterDescriptor: TypeParameterDescriptor?): ObjCGenericTypeUsage? { - val localTypeParam = typeNames.firstOrNull { - typeParameterDescriptor != null && - (it == typeParameterDescriptor || (it.isCapturedFromOuterDeclaration && it.original == typeParameterDescriptor)) - } - - return if(localTypeParam == null) { - null - } else { - ObjCGenericTypeParameterUsage(localTypeParam, namer) - } - } -} - -internal object ObjCNoneExportScope: ObjCExportScope{ - override fun getGenericTypeUsage(typeParameterDescriptor: TypeParameterDescriptor?): ObjCGenericTypeUsage? = null -} - -private fun computeSuperClassType(descriptor: ClassDescriptor): KotlinType? = descriptor.typeConstructor.supertypes.filter { !it.isInterface() }.firstOrNull() +private fun computeSuperClassType(descriptor: ClassDescriptor): KotlinType? = + descriptor.typeConstructor.supertypes.firstOrNull { !it.isInterface() } internal const val OBJC_SUBCLASSING_RESTRICTED = "objc_subclassing_restricted" @@ -1415,7 +1394,6 @@ internal fun ClassDescriptor.needCompanionObjectProperty(namer: ObjCExportNamer, return true } - private fun DeprecationInfo.toDeprecationAttribute(): String { val attribute = when (deprecationLevel) { DeprecationLevelValue.WARNING -> "deprecated" @@ -1457,4 +1435,4 @@ private fun quoteAsCStringLiteral(str: String): String = buildString { } } append('"') -} +} \ No newline at end of file diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportScope.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportScope.kt new file mode 100644 index 00000000000..71683e7c8a5 --- /dev/null +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportScope.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.backend.konan.objcexport + +import org.jetbrains.kotlin.backend.konan.descriptors.isInterface +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor +import org.jetbrains.kotlin.types.KotlinType + +interface ObjCExportScope { + class RecursionBreachException : Exception { + constructor(type: KotlinType) : super("$type was already encountered during type mapping process.") + } + + val parent: ObjCExportScope? + get() = null + + fun deriveForType(kotlinType: KotlinType): ObjCTypeExportScope = ObjCTypeExportScopeImpl(kotlinType, this) + fun deriveForClass(container: DeclarationDescriptor, namer: ObjCExportNamer): ObjCClassExportScope = + ObjCClassExportScopeImpl(container, namer, this) +} + +internal inline fun ObjCExportScope.nearestScopeOfType(): T? { + var parent: ObjCExportScope? = this + while (parent != null) { + if (parent is T) { + return parent + } + parent = this.parent + } + return null +} + +internal object ObjCRootExportScope : ObjCExportScope + +interface ObjCClassExportScope : ObjCExportScope { + fun getGenericTypeUsage(typeParameterDescriptor: TypeParameterDescriptor?): ObjCGenericTypeUsage? +} + +private class ObjCClassExportScopeImpl constructor( + container: DeclarationDescriptor, + val namer: ObjCExportNamer, + override val parent: ObjCExportScope?, +) : ObjCClassExportScope { + private val typeParameterNames: List = + if (container is ClassDescriptor && !container.isInterface) { + container.typeConstructor.parameters + } else { + emptyList() + } + + override fun getGenericTypeUsage(typeParameterDescriptor: TypeParameterDescriptor?): ObjCGenericTypeUsage? { + return typeParameterDescriptor?.let { descriptor -> + typeParameterNames.firstOrNull { + it == descriptor || it.isCapturedFromOuterDeclaration && it.original == descriptor + }?.let { + ObjCGenericTypeParameterUsage(it, namer) + } + } + } +} + +interface ObjCTypeExportScope : ObjCExportScope { + val kotlinType: KotlinType +} + +private class ObjCTypeExportScopeImpl(override val kotlinType: KotlinType, override val parent: ObjCExportScope?) : ObjCTypeExportScope { + init { + var parent = this.parent + while (parent != null && parent is ObjCTypeExportScope) { + if (parent.kotlinType == kotlinType) + throw ObjCExportScope.RecursionBreachException(kotlinType) + parent = parent.parent + } + } +} \ No newline at end of file diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslatorMobile.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslatorMobile.kt index fca28c9e9ea..aefb97d1f0a 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslatorMobile.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslatorMobile.kt @@ -18,7 +18,7 @@ class ObjCExportTranslatorMobile internal constructor(private val delegate: ObjC fun translateBaseFunction(descriptor: FunctionDescriptor): ObjCMethod { val classDescriptor = descriptor.containingDeclaration as? ClassDescriptor - val scope = classDescriptor?.let { delegate.createGenericExportScope(it) } ?: ObjCNoneExportScope + val scope = classDescriptor?.let { delegate.createGenericExportScope(it) } ?: ObjCRootExportScope return delegate.buildMethod(descriptor, descriptor, scope) } } \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/objcexport/expectedLazy.h b/kotlin-native/backend.native/tests/objcexport/expectedLazy.h index d56aa25efde..411ec4e6aa2 100644 --- a/kotlin-native/backend.native/tests/objcexport/expectedLazy.h +++ b/kotlin-native/backend.native/tests/objcexport/expectedLazy.h @@ -1462,6 +1462,20 @@ __attribute__((swift_name("OverrideMethodsOfAnyKt"))) + (BOOL)testObj:(id)obj other:(id)other swift:(BOOL)swift error:(NSError * _Nullable * _Nullable)error __attribute__((swift_name("test(obj:other:swift:)"))); @end +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("RecList"))) +@interface KtRecList : KtBase +- (instancetype)initWithValue:(NSArray *)value __attribute__((swift_name("init(value:)"))) __attribute__((objc_designated_initializer)); +@property (readonly) NSArray *value __attribute__((swift_name("value"))); +@end + +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("RecFunc"))) +@interface KtRecFunc : KtBase +- (instancetype)initWithValue:(id (^)(void))value __attribute__((swift_name("init(value:)"))) __attribute__((objc_designated_initializer)); +@property (readonly) id (^value)(void) __attribute__((swift_name("value"))); +@end + __attribute__((swift_name("RefinedClassA"))) @interface KtRefinedClassA : KtBase - (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer)); diff --git a/kotlin-native/backend.native/tests/objcexport/expectedLazyLegacySuspendUnit.h b/kotlin-native/backend.native/tests/objcexport/expectedLazyLegacySuspendUnit.h index b55565eb276..77d79ffee95 100644 --- a/kotlin-native/backend.native/tests/objcexport/expectedLazyLegacySuspendUnit.h +++ b/kotlin-native/backend.native/tests/objcexport/expectedLazyLegacySuspendUnit.h @@ -1397,6 +1397,20 @@ __attribute__((swift_name("OverrideMethodsOfAnyKt"))) + (BOOL)testObj:(id)obj other:(id)other swift:(BOOL)swift error:(NSError * _Nullable * _Nullable)error __attribute__((swift_name("test(obj:other:swift:)"))); @end +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("RecList"))) +@interface KtRecList : KtBase +- (instancetype)initWithValue:(NSArray *)value __attribute__((swift_name("init(value:)"))) __attribute__((objc_designated_initializer)); +@property (readonly) NSArray *value __attribute__((swift_name("value"))); +@end + +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("RecFunc"))) +@interface KtRecFunc : KtBase +- (instancetype)initWithValue:(id (^)(void))value __attribute__((swift_name("init(value:)"))) __attribute__((objc_designated_initializer)); +@property (readonly) id (^value)(void) __attribute__((swift_name("value"))); +@end + __attribute__((swift_name("RefinedClassA"))) @interface KtRefinedClassA : KtBase - (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer)); diff --git a/kotlin-native/backend.native/tests/objcexport/expectedLazyNoGenerics.h b/kotlin-native/backend.native/tests/objcexport/expectedLazyNoGenerics.h index c4366bfc6bf..961a0c18001 100644 --- a/kotlin-native/backend.native/tests/objcexport/expectedLazyNoGenerics.h +++ b/kotlin-native/backend.native/tests/objcexport/expectedLazyNoGenerics.h @@ -1397,6 +1397,20 @@ __attribute__((swift_name("OverrideMethodsOfAnyKt"))) + (BOOL)testObj:(id)obj other:(id)other swift:(BOOL)swift error:(NSError * _Nullable * _Nullable)error __attribute__((swift_name("test(obj:other:swift:)"))); @end +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("RecList"))) +@interface KtRecList : KtBase +- (instancetype)initWithValue:(NSArray *)value __attribute__((swift_name("init(value:)"))) __attribute__((objc_designated_initializer)); +@property (readonly) NSArray *value __attribute__((swift_name("value"))); +@end + +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("RecFunc"))) +@interface KtRecFunc : KtBase +- (instancetype)initWithValue:(id (^)(void))value __attribute__((swift_name("init(value:)"))) __attribute__((objc_designated_initializer)); +@property (readonly) id (^value)(void) __attribute__((swift_name("value"))); +@end + __attribute__((swift_name("RefinedClassA"))) @interface KtRefinedClassA : KtBase - (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer)); diff --git a/kotlin-native/backend.native/tests/objcexport/recursiveGenericArguments.kt b/kotlin-native/backend.native/tests/objcexport/recursiveGenericArguments.kt new file mode 100644 index 00000000000..66b2888ad46 --- /dev/null +++ b/kotlin-native/backend.native/tests/objcexport/recursiveGenericArguments.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package recursiveGenericArguments + +class RecList>(val value: T) + +class RecFunc T>(val value: T) \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/objcexport/recursiveGenericArguments.swift b/kotlin-native/backend.native/tests/objcexport/recursiveGenericArguments.swift new file mode 100644 index 00000000000..a6c38863a37 --- /dev/null +++ b/kotlin-native/backend.native/tests/objcexport/recursiveGenericArguments.swift @@ -0,0 +1,17 @@ +import Kt + +#if !NO_GENERICS +private func testRecursiveGenericArguments() throws { + try assertTrue(type(of: RecList(value: []).value) == [Any].self) +} +#endif + +class RecursiveGenericArgumentsTests : SimpleTestProvider { + override init() { + super.init() + +#if !NO_GENERICS + test("TestRecursiveGenericArguments", testRecursiveGenericArguments) +#endif + } +} \ No newline at end of file