diff --git a/kotlin-native/backend.native/tests/objcexport/expectedLazy.h b/kotlin-native/backend.native/tests/objcexport/expectedLazy.h index 644e3ea91b1..f766b5149ac 100644 --- a/kotlin-native/backend.native/tests/objcexport/expectedLazy.h +++ b/kotlin-native/backend.native/tests/objcexport/expectedLazy.h @@ -923,6 +923,116 @@ __attribute__((swift_name("ArraysInitBlock"))) - (NSString *)log __attribute__((swift_name("log()"))); @end; +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("KotlinLivenessTracker"))) +@interface KtKotlinLivenessTracker : KtBase +- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer)); ++ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead"))); +- (void)addObj:(id _Nullable)obj __attribute__((swift_name("add(obj:)"))); +- (BOOL)isEmpty __attribute__((swift_name("isEmpty()"))); +- (BOOL)objectsAreDead __attribute__((swift_name("objectsAreDead()"))); +@property (readonly) NSMutableArray *> *weakRefs __attribute__((swift_name("weakRefs"))); +@end; + +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("KotlinObject"))) +@interface KtKotlinObject : KtBase +- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer)); ++ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead"))); +@end; + +__attribute__((swift_name("NoAutoreleaseSendHelper"))) +@protocol KtNoAutoreleaseSendHelper +@required +- (void)sendKotlinObjectKotlinObject:(KtKotlinObject *)kotlinObject __attribute__((swift_name("sendKotlinObject(kotlinObject:)"))); +- (void)sendSwiftObjectSwiftObject:(id)swiftObject __attribute__((swift_name("sendSwiftObject(swiftObject:)"))); +- (void)sendListList:(NSArray *)list __attribute__((swift_name("sendList(list:)"))); +- (void)sendStringString:(NSString *)string __attribute__((swift_name("sendString(string:)"))); +- (void)sendNumberNumber:(id)number __attribute__((swift_name("sendNumber(number:)"))); +- (void)sendBlockBlock:(KtKotlinObject *(^)(void))block __attribute__((swift_name("sendBlock(block:)"))); +@end; + +__attribute__((swift_name("NoAutoreleaseReceiveHelper"))) +@protocol KtNoAutoreleaseReceiveHelper +@required +- (KtKotlinObject *)receiveKotlinObject __attribute__((swift_name("receiveKotlinObject()"))); +- (id)receiveSwiftObject __attribute__((swift_name("receiveSwiftObject()"))); +- (NSArray *)receiveList __attribute__((swift_name("receiveList()"))); +- (NSString *)receiveString __attribute__((swift_name("receiveString()"))); +- (id)receiveNumber __attribute__((swift_name("receiveNumber()"))); +- (KtKotlinObject *(^)(void))receiveBlock __attribute__((swift_name("receiveBlock()"))); +@end; + +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("NoAutoreleaseKotlinSendHelper"))) +@interface KtNoAutoreleaseKotlinSendHelper : KtBase +- (instancetype)initWithKotlinLivenessTracker:(KtKotlinLivenessTracker *)kotlinLivenessTracker __attribute__((swift_name("init(kotlinLivenessTracker:)"))) __attribute__((objc_designated_initializer)); +- (void)sendKotlinObjectKotlinObject:(KtKotlinObject *)kotlinObject __attribute__((swift_name("sendKotlinObject(kotlinObject:)"))); +- (void)sendSwiftObjectSwiftObject:(id)swiftObject __attribute__((swift_name("sendSwiftObject(swiftObject:)"))); +- (void)sendListList:(NSArray *)list __attribute__((swift_name("sendList(list:)"))); +- (void)sendStringString:(NSString *)string __attribute__((swift_name("sendString(string:)"))); +- (void)sendNumberNumber:(id)number __attribute__((swift_name("sendNumber(number:)"))); +- (void)sendBlockBlock:(KtKotlinObject *(^)(void))block __attribute__((swift_name("sendBlock(block:)"))); +@property (readonly) KtKotlinLivenessTracker *kotlinLivenessTracker __attribute__((swift_name("kotlinLivenessTracker"))); +@end; + +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("NoAutoreleaseKotlinReceiveHelper"))) +@interface KtNoAutoreleaseKotlinReceiveHelper : KtBase +- (instancetype)initWithKotlinLivenessTracker:(KtKotlinLivenessTracker *)kotlinLivenessTracker __attribute__((swift_name("init(kotlinLivenessTracker:)"))) __attribute__((objc_designated_initializer)); +- (KtKotlinObject *)receiveKotlinObject __attribute__((swift_name("receiveKotlinObject()"))); +- (id)receiveSwiftObject __attribute__((swift_name("receiveSwiftObject()"))); +- (NSArray *)receiveList __attribute__((swift_name("receiveList()"))); +- (NSString *)receiveString __attribute__((swift_name("receiveString()"))); +- (id)receiveNumber __attribute__((swift_name("receiveNumber()"))); +- (KtKotlinObject *(^)(void))receiveBlock __attribute__((swift_name("receiveBlock()"))); +@property id swiftObject __attribute__((swift_name("swiftObject"))); +@property (readonly) KtKotlinLivenessTracker *kotlinLivenessTracker __attribute__((swift_name("kotlinLivenessTracker"))); +@end; + +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("NoAutoreleaseSingleton"))) +@interface KtNoAutoreleaseSingleton : KtBase ++ (instancetype)alloc __attribute__((unavailable)); ++ (instancetype)allocWithZone:(struct _NSZone *)zone __attribute__((unavailable)); ++ (instancetype)noAutoreleaseSingleton __attribute__((swift_name("init()"))); +@property (class, readonly, getter=shared) KtNoAutoreleaseSingleton *shared __attribute__((swift_name("shared"))); +@property (readonly) int32_t x __attribute__((swift_name("x"))); +@end; + +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("NoAutoreleaseEnum"))) +@interface KtNoAutoreleaseEnum : KtKotlinEnum ++ (instancetype)alloc __attribute__((unavailable)); ++ (instancetype)allocWithZone:(struct _NSZone *)zone __attribute__((unavailable)); +- (instancetype)initWithName:(NSString *)name ordinal:(int32_t)ordinal __attribute__((swift_name("init(name:ordinal:)"))) __attribute__((objc_designated_initializer)) __attribute__((unavailable)); +@property (class, readonly) KtNoAutoreleaseEnum *entry __attribute__((swift_name("entry"))); ++ (KtKotlinArray *)values __attribute__((swift_name("values()"))); +@property (readonly) int32_t x __attribute__((swift_name("x"))); +@end; + +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("NoAutoreleaseKt"))) +@interface KtNoAutoreleaseKt : KtBase ++ (void)gc __attribute__((swift_name("gc()"))); ++ (void)callSendKotlinObjectHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendKotlinObject(helper:tracker:)"))); ++ (void)callSendSwiftObjectHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker swiftObject:(id)swiftObject __attribute__((swift_name("callSendSwiftObject(helper:tracker:swiftObject:)"))); ++ (void)callSendListHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendList(helper:tracker:)"))); ++ (void)callSendStringHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendString(helper:tracker:)"))); ++ (void)callSendNumberHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendNumber(helper:tracker:)"))); ++ (void)callSendBlockHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendBlock(helper:tracker:)"))); ++ (void)callReceiveKotlinObjectHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveKotlinObject(helper:tracker:)"))); ++ (void)callReceiveSwiftObjectHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveSwiftObject(helper:tracker:)"))); ++ (void)callReceiveListHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveList(helper:tracker:)"))); ++ (void)callReceiveStringHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveString(helper:tracker:)"))); ++ (void)callReceiveNumberHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveNumber(helper:tracker:)"))); ++ (void)callReceiveBlockHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveBlock(helper:tracker:)"))); ++ (void)callReceiveBlockAndCallHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveBlockAndCall(helper:tracker:)"))); ++ (void * _Nullable)objc_autoreleasePoolPush __attribute__((swift_name("objc_autoreleasePoolPush()"))); ++ (void)objc_autoreleasePoolPopHandle:(void * _Nullable)handle __attribute__((swift_name("objc_autoreleasePoolPop(handle:)"))); ++ (void)useIntArrayArray:(KtKotlinIntArray *)array __attribute__((swift_name("useIntArray(array:)"))); +@end; + __attribute__((swift_name("OverrideKotlinMethods2"))) @protocol KtOverrideKotlinMethods2 @required diff --git a/kotlin-native/backend.native/tests/objcexport/expectedLazyNoGenerics.h b/kotlin-native/backend.native/tests/objcexport/expectedLazyNoGenerics.h index 61baa649b08..5be86035121 100644 --- a/kotlin-native/backend.native/tests/objcexport/expectedLazyNoGenerics.h +++ b/kotlin-native/backend.native/tests/objcexport/expectedLazyNoGenerics.h @@ -865,6 +865,116 @@ __attribute__((swift_name("ArraysInitBlock"))) - (NSString *)log __attribute__((swift_name("log()"))); @end; +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("KotlinLivenessTracker"))) +@interface KtKotlinLivenessTracker : KtBase +- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer)); ++ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead"))); +- (void)addObj:(id _Nullable)obj __attribute__((swift_name("add(obj:)"))); +- (BOOL)isEmpty __attribute__((swift_name("isEmpty()"))); +- (BOOL)objectsAreDead __attribute__((swift_name("objectsAreDead()"))); +@property (readonly) NSMutableArray *weakRefs __attribute__((swift_name("weakRefs"))); +@end; + +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("KotlinObject"))) +@interface KtKotlinObject : KtBase +- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer)); ++ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead"))); +@end; + +__attribute__((swift_name("NoAutoreleaseSendHelper"))) +@protocol KtNoAutoreleaseSendHelper +@required +- (void)sendKotlinObjectKotlinObject:(KtKotlinObject *)kotlinObject __attribute__((swift_name("sendKotlinObject(kotlinObject:)"))); +- (void)sendSwiftObjectSwiftObject:(id)swiftObject __attribute__((swift_name("sendSwiftObject(swiftObject:)"))); +- (void)sendListList:(NSArray *)list __attribute__((swift_name("sendList(list:)"))); +- (void)sendStringString:(NSString *)string __attribute__((swift_name("sendString(string:)"))); +- (void)sendNumberNumber:(id)number __attribute__((swift_name("sendNumber(number:)"))); +- (void)sendBlockBlock:(KtKotlinObject *(^)(void))block __attribute__((swift_name("sendBlock(block:)"))); +@end; + +__attribute__((swift_name("NoAutoreleaseReceiveHelper"))) +@protocol KtNoAutoreleaseReceiveHelper +@required +- (KtKotlinObject *)receiveKotlinObject __attribute__((swift_name("receiveKotlinObject()"))); +- (id)receiveSwiftObject __attribute__((swift_name("receiveSwiftObject()"))); +- (NSArray *)receiveList __attribute__((swift_name("receiveList()"))); +- (NSString *)receiveString __attribute__((swift_name("receiveString()"))); +- (id)receiveNumber __attribute__((swift_name("receiveNumber()"))); +- (KtKotlinObject *(^)(void))receiveBlock __attribute__((swift_name("receiveBlock()"))); +@end; + +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("NoAutoreleaseKotlinSendHelper"))) +@interface KtNoAutoreleaseKotlinSendHelper : KtBase +- (instancetype)initWithKotlinLivenessTracker:(KtKotlinLivenessTracker *)kotlinLivenessTracker __attribute__((swift_name("init(kotlinLivenessTracker:)"))) __attribute__((objc_designated_initializer)); +- (void)sendKotlinObjectKotlinObject:(KtKotlinObject *)kotlinObject __attribute__((swift_name("sendKotlinObject(kotlinObject:)"))); +- (void)sendSwiftObjectSwiftObject:(id)swiftObject __attribute__((swift_name("sendSwiftObject(swiftObject:)"))); +- (void)sendListList:(NSArray *)list __attribute__((swift_name("sendList(list:)"))); +- (void)sendStringString:(NSString *)string __attribute__((swift_name("sendString(string:)"))); +- (void)sendNumberNumber:(id)number __attribute__((swift_name("sendNumber(number:)"))); +- (void)sendBlockBlock:(KtKotlinObject *(^)(void))block __attribute__((swift_name("sendBlock(block:)"))); +@property (readonly) KtKotlinLivenessTracker *kotlinLivenessTracker __attribute__((swift_name("kotlinLivenessTracker"))); +@end; + +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("NoAutoreleaseKotlinReceiveHelper"))) +@interface KtNoAutoreleaseKotlinReceiveHelper : KtBase +- (instancetype)initWithKotlinLivenessTracker:(KtKotlinLivenessTracker *)kotlinLivenessTracker __attribute__((swift_name("init(kotlinLivenessTracker:)"))) __attribute__((objc_designated_initializer)); +- (KtKotlinObject *)receiveKotlinObject __attribute__((swift_name("receiveKotlinObject()"))); +- (id)receiveSwiftObject __attribute__((swift_name("receiveSwiftObject()"))); +- (NSArray *)receiveList __attribute__((swift_name("receiveList()"))); +- (NSString *)receiveString __attribute__((swift_name("receiveString()"))); +- (id)receiveNumber __attribute__((swift_name("receiveNumber()"))); +- (KtKotlinObject *(^)(void))receiveBlock __attribute__((swift_name("receiveBlock()"))); +@property id swiftObject __attribute__((swift_name("swiftObject"))); +@property (readonly) KtKotlinLivenessTracker *kotlinLivenessTracker __attribute__((swift_name("kotlinLivenessTracker"))); +@end; + +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("NoAutoreleaseSingleton"))) +@interface KtNoAutoreleaseSingleton : KtBase ++ (instancetype)alloc __attribute__((unavailable)); ++ (instancetype)allocWithZone:(struct _NSZone *)zone __attribute__((unavailable)); ++ (instancetype)noAutoreleaseSingleton __attribute__((swift_name("init()"))); +@property (class, readonly, getter=shared) KtNoAutoreleaseSingleton *shared __attribute__((swift_name("shared"))); +@property (readonly) int32_t x __attribute__((swift_name("x"))); +@end; + +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("NoAutoreleaseEnum"))) +@interface KtNoAutoreleaseEnum : KtKotlinEnum ++ (instancetype)alloc __attribute__((unavailable)); ++ (instancetype)allocWithZone:(struct _NSZone *)zone __attribute__((unavailable)); +- (instancetype)initWithName:(NSString *)name ordinal:(int32_t)ordinal __attribute__((swift_name("init(name:ordinal:)"))) __attribute__((objc_designated_initializer)) __attribute__((unavailable)); +@property (class, readonly) KtNoAutoreleaseEnum *entry __attribute__((swift_name("entry"))); ++ (KtKotlinArray *)values __attribute__((swift_name("values()"))); +@property (readonly) int32_t x __attribute__((swift_name("x"))); +@end; + +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("NoAutoreleaseKt"))) +@interface KtNoAutoreleaseKt : KtBase ++ (void)gc __attribute__((swift_name("gc()"))); ++ (void)callSendKotlinObjectHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendKotlinObject(helper:tracker:)"))); ++ (void)callSendSwiftObjectHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker swiftObject:(id)swiftObject __attribute__((swift_name("callSendSwiftObject(helper:tracker:swiftObject:)"))); ++ (void)callSendListHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendList(helper:tracker:)"))); ++ (void)callSendStringHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendString(helper:tracker:)"))); ++ (void)callSendNumberHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendNumber(helper:tracker:)"))); ++ (void)callSendBlockHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendBlock(helper:tracker:)"))); ++ (void)callReceiveKotlinObjectHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveKotlinObject(helper:tracker:)"))); ++ (void)callReceiveSwiftObjectHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveSwiftObject(helper:tracker:)"))); ++ (void)callReceiveListHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveList(helper:tracker:)"))); ++ (void)callReceiveStringHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveString(helper:tracker:)"))); ++ (void)callReceiveNumberHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveNumber(helper:tracker:)"))); ++ (void)callReceiveBlockHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveBlock(helper:tracker:)"))); ++ (void)callReceiveBlockAndCallHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveBlockAndCall(helper:tracker:)"))); ++ (void * _Nullable)objc_autoreleasePoolPush __attribute__((swift_name("objc_autoreleasePoolPush()"))); ++ (void)objc_autoreleasePoolPopHandle:(void * _Nullable)handle __attribute__((swift_name("objc_autoreleasePoolPop(handle:)"))); ++ (void)useIntArrayArray:(KtKotlinIntArray *)array __attribute__((swift_name("useIntArray(array:)"))); +@end; + __attribute__((swift_name("OverrideKotlinMethods2"))) @protocol KtOverrideKotlinMethods2 @required diff --git a/kotlin-native/backend.native/tests/objcexport/noAutorelease.kt b/kotlin-native/backend.native/tests/objcexport/noAutorelease.kt new file mode 100644 index 00000000000..20d4899ea40 --- /dev/null +++ b/kotlin-native/backend.native/tests/objcexport/noAutorelease.kt @@ -0,0 +1,161 @@ +package noAutorelease + +import kotlin.native.internal.NativePtr +import kotlin.native.ref.WeakReference +import kotlin.test.* + +class KotlinLivenessTracker { + val weakRefs = mutableListOf>() + + fun add(obj: Any?) { + assertNotNull(obj) + weakRefs += WeakReference(obj) + assertFalse(objectsAreDead()) + } + + fun isEmpty() = weakRefs.isEmpty() + fun objectsAreDead() = weakRefs.all { it.value === null } +} + +fun gc() = kotlin.native.internal.GC.collect() + +class KotlinObject + +interface NoAutoreleaseSendHelper { + fun sendKotlinObject(kotlinObject: KotlinObject) + fun sendSwiftObject(swiftObject: Any) + fun sendList(list: List<*>) + fun sendString(string: String) + fun sendNumber(number: Any) + fun sendBlock(block: () -> KotlinObject) +} + +interface NoAutoreleaseReceiveHelper { + fun receiveKotlinObject(): KotlinObject + fun receiveSwiftObject(): Any + fun receiveList(): List<*> + fun receiveString(): String + fun receiveNumber(): Any + fun receiveBlock(): () -> KotlinObject +} + +class NoAutoreleaseKotlinSendHelper(val kotlinLivenessTracker: KotlinLivenessTracker) : NoAutoreleaseSendHelper { + override fun sendKotlinObject(kotlinObject: KotlinObject) = kotlinLivenessTracker.add(kotlinObject) + override fun sendSwiftObject(swiftObject: Any) = kotlinLivenessTracker.add(swiftObject) + override fun sendList(list: List<*>) = kotlinLivenessTracker.add(list) + override fun sendString(string: String) = kotlinLivenessTracker.add(string) + override fun sendNumber(number: Any) = kotlinLivenessTracker.add(number) + override fun sendBlock(block: () -> KotlinObject) = kotlinLivenessTracker.add(block) +} + +class NoAutoreleaseKotlinReceiveHelper(val kotlinLivenessTracker: KotlinLivenessTracker) : NoAutoreleaseReceiveHelper { + private val kotlinObject = KotlinObject() + lateinit var swiftObject: Any + private val list = listOf(Any()) + private val string = Any().toString() + private val number = createKotlinNumber() + private val block = createLambda() + + override fun receiveKotlinObject(): KotlinObject = kotlinObject.also { kotlinLivenessTracker.add(it) } + override fun receiveSwiftObject(): Any = swiftObject.also { kotlinLivenessTracker.add(it) } + override fun receiveList(): List<*> = list.also { kotlinLivenessTracker.add(it) } + override fun receiveString(): String = string.also { kotlinLivenessTracker.add(it) } + override fun receiveNumber(): Any = number.also { kotlinLivenessTracker.add(it) } + override fun receiveBlock(): () -> KotlinObject = block.also { kotlinLivenessTracker.add(it) } +} + +object NoAutoreleaseSingleton { + val x = 1 +} +enum class NoAutoreleaseEnum { + ENTRY; + + val x = 2 +} + +fun callSendKotlinObject(helper: NoAutoreleaseSendHelper, tracker: KotlinLivenessTracker) { + val kotlinObject = KotlinObject() + + // Repeating twice to cover possible fast paths after caching something for an object. + helper.sendKotlinObject(kotlinObject) + helper.sendKotlinObject(kotlinObject) + tracker.add(kotlinObject) +} + +fun callSendSwiftObject(helper: NoAutoreleaseSendHelper, tracker: KotlinLivenessTracker, swiftObject: Any) { + helper.sendSwiftObject(swiftObject) + helper.sendSwiftObject(swiftObject) + tracker.add(swiftObject) +} + +fun callSendList(helper: NoAutoreleaseSendHelper, tracker: KotlinLivenessTracker) { + val list = listOf(Any()) + + helper.sendList(list) + helper.sendList(list) + tracker.add(list) +} + +fun callSendString(helper: NoAutoreleaseSendHelper, tracker: KotlinLivenessTracker) { + val string = Any().toString() + + helper.sendString(string) + helper.sendString(string) + tracker.add(string) +} + +fun callSendNumber(helper: NoAutoreleaseSendHelper, tracker: KotlinLivenessTracker) { + val number = createKotlinNumber() + + helper.sendNumber(number) + helper.sendNumber(number) + tracker.add(number) +} + +fun callSendBlock(helper: NoAutoreleaseSendHelper, tracker: KotlinLivenessTracker) { + val block = createLambda() + + helper.sendBlock(block) + helper.sendBlock(block) + tracker.add(block) +} + +fun callReceiveKotlinObject(helper: NoAutoreleaseReceiveHelper, tracker: KotlinLivenessTracker) = repeat(2) { + tracker.add(helper.receiveKotlinObject()) +} + +fun callReceiveSwiftObject(helper: NoAutoreleaseReceiveHelper, tracker: KotlinLivenessTracker) = repeat(2) { + tracker.add(helper.receiveSwiftObject()) +} + +fun callReceiveList(helper: NoAutoreleaseReceiveHelper, tracker: KotlinLivenessTracker) = repeat(2) { + tracker.add(helper.receiveList()) +} + +fun callReceiveString(helper: NoAutoreleaseReceiveHelper, tracker: KotlinLivenessTracker) = repeat(2) { + tracker.add(helper.receiveString()) +} + +fun callReceiveNumber(helper: NoAutoreleaseReceiveHelper, tracker: KotlinLivenessTracker) = repeat(2) { + tracker.add(helper.receiveNumber()) +} + +fun callReceiveBlock(helper: NoAutoreleaseReceiveHelper, tracker: KotlinLivenessTracker) = repeat(2) { + tracker.add(helper.receiveBlock()) +} + +fun callReceiveBlockAndCall(helper: NoAutoreleaseReceiveHelper, tracker: KotlinLivenessTracker) = repeat(2) { + tracker.add(helper.receiveBlock()()) +} + +fun objc_autoreleasePoolPush() = kotlinx.cinterop.objc_autoreleasePoolPush() +fun objc_autoreleasePoolPop(handle: NativePtr) = kotlinx.cinterop.objc_autoreleasePoolPop(handle) + +fun useIntArray(array: IntArray) {} // Just to make IntArray available from Swift. + +private fun createLambda(): () -> KotlinObject { + val lambdaResult = KotlinObject() + return { lambdaResult } // make it capturing thus dynamic. +} + +private fun createKotlinNumber(): Any = (0.5 + Any().hashCode().toDouble()) // to make it dynamic. diff --git a/kotlin-native/backend.native/tests/objcexport/noAutorelease.swift b/kotlin-native/backend.native/tests/objcexport/noAutorelease.swift new file mode 100644 index 00000000000..60c512ba21b --- /dev/null +++ b/kotlin-native/backend.native/tests/objcexport/noAutorelease.swift @@ -0,0 +1,458 @@ +import Kt + +private class SwiftLivenessTracker { + class SwiftWeakRef { + weak var value: AnyObject? + } + + var weakRefs: [SwiftWeakRef] = [] + + func add(_ obj: AnyObject?) { + try! assertTrue(obj != nil) + + let weakRef = SwiftWeakRef() + weakRef.value = obj + weakRefs.append(weakRef) + + try! assertFalse(objectsAreDead()) + } + + func isEmpty() -> Bool { + return weakRefs.isEmpty + } + + func objectsAreDead() -> Bool { + for weakRef in weakRefs { + if weakRef.value !== nil { return false } + } + return true + } +} + +private class NoAutoreleaseSwiftHelper : NoAutoreleaseSendHelper, NoAutoreleaseReceiveHelper { + fileprivate var swiftLivenessTracker: SwiftLivenessTracker + + init(swiftLivenessTracker: SwiftLivenessTracker) { + self.swiftLivenessTracker = swiftLivenessTracker + } + + func sendKotlinObject(kotlinObject: KotlinObject) { + swiftLivenessTracker.add(kotlinObject) + } + + func sendSwiftObject(swiftObject: Any) { + swiftLivenessTracker.add(swiftObject as AnyObject) + } + + func sendList(list: [Any]) { + } + + func sendString(string: String) { + } + + func sendNumber(number: Any) { + swiftLivenessTracker.add(number as AnyObject) + } + + func sendBlock(block: @escaping () -> KotlinObject) { + } + + let kotlinObject = KotlinObject() + let swiftObject = SwiftObject2() + let list = createList() + let string = createString() + let number = createNumber() + let block = createBlock() + + func receiveKotlinObject() -> KotlinObject { + let result = kotlinObject + swiftLivenessTracker.add(result) + return result + } + + func receiveSwiftObject() -> Any { + let result = swiftObject + swiftLivenessTracker.add(result) + return result + } + + func receiveList() -> [Any] { + return list + } + + func receiveString() -> String { + return string + } + + func receiveNumber() -> Any { + let result = number + swiftLivenessTracker.add(result) + return number + } + + func receiveBlock() -> (() -> KotlinObject) { + return block + } +} + +private struct TestFlags { + var trackSwiftLifetime: Bool = true + var checkAutorelease: Bool = true +} + +private func testOnce(flags: TestFlags, block: (KotlinLivenessTracker, SwiftLivenessTracker) throws -> Void) throws { + try assertAutoreleasepoolNotUsed(flags: flags) { // Note: this assertion will also fail if something in the "test infra" below uses autoreleasepool. + let kotlinLivenessTracker = KotlinLivenessTracker() + let swiftLivenessTracker = SwiftLivenessTracker() + + try assertTrue(kotlinLivenessTracker.isEmpty()) + try assertTrue(swiftLivenessTracker.isEmpty()) + + try block(kotlinLivenessTracker, swiftLivenessTracker) + + NoAutoreleaseKt.gc() + + try assertFalse(kotlinLivenessTracker.isEmpty()) + if flags.trackSwiftLifetime { + try assertFalse(swiftLivenessTracker.isEmpty()) + } else { + // Note: some of the tests don't actually add objects to the swiftLivenessTracker, + // because e.g. Swift represents certain Obj-C classes as value types. + try assertTrue(swiftLivenessTracker.isEmpty()) + } + +#if !NOOP_GC + // If something has "leaked" to autoreleasepool, one of the assertions below should fail: + try assertTrue(kotlinLivenessTracker.objectsAreDead()) + try assertTrue(swiftLivenessTracker.objectsAreDead()) +#endif + } +} + +private func test(flags: TestFlags, block: (KotlinLivenessTracker, SwiftLivenessTracker) -> Void) throws { + for _ in 1...2 { + // Repeating twice to cover possible fast paths after caching something for a type. + try testOnce(flags: flags, block: block) + } +} + +private func assertAutoreleasepoolNotUsed(flags: TestFlags = TestFlags(), block: () throws -> Void) throws { + let poolBefore = NoAutoreleaseKt.objc_autoreleasePoolPush() + NoAutoreleaseKt.objc_autoreleasePoolPop(handle: poolBefore) + + try block() + + let poolAfter = NoAutoreleaseKt.objc_autoreleasePoolPush() + NoAutoreleaseKt.objc_autoreleasePoolPop(handle: poolAfter) + + // autoreleasepool machinery is implemented as a stack. + // So here we check that stack top after `block()` is the same as before. + // Which means that no objects were added to this stack inbetween. + if flags.checkAutorelease { + try assertEquals(actual: poolAfter, expected: poolBefore) + } else { + // Just to ensure we disable the hack once it is not needed anymore: + try assertFalse(poolAfter == poolBefore) + } +} + +private func testSendToKotlin( + _ createObject: () -> T, + flags: TestFlags = TestFlags(), + sendObject: (NoAutoreleaseKotlinSendHelper, T) -> Void +) throws { + try test(flags: flags) { kotlinLivenessTracker, swiftLivenessTracker in + let helper = NoAutoreleaseKotlinSendHelper(kotlinLivenessTracker: kotlinLivenessTracker) + + let obj = createObject() + if flags.trackSwiftLifetime { + swiftLivenessTracker.add(obj as AnyObject) + } + + // Repeating twice to cover possible fast paths after caching something for an object. + sendObject(helper, obj) + sendObject(helper, obj) + } +} + +private func testReceiveFromKotlin(flags: TestFlags = TestFlags(), receiveObject: (NoAutoreleaseKotlinReceiveHelper) -> T) throws { + try test(flags: flags) { kotlinLivenessTracker, swiftLivenessTracker in + let helper = NoAutoreleaseKotlinReceiveHelper(kotlinLivenessTracker: kotlinLivenessTracker) + + // Repeating twice to cover possible fast paths after caching something for an object. + let obj1 = receiveObject(helper) + let obj2 = receiveObject(helper) + + if flags.trackSwiftLifetime { + swiftLivenessTracker.add(obj1 as AnyObject) + swiftLivenessTracker.add(obj2 as AnyObject) + } + } +} + +private func testCallToSwift(flags: TestFlags = TestFlags(), callKotlin: (NoAutoreleaseSwiftHelper, KotlinLivenessTracker) -> Void) throws { + try test(flags: flags) { kotlinLivenessTracker, swiftLivenessTracker in + let helper = NoAutoreleaseSwiftHelper(swiftLivenessTracker: swiftLivenessTracker) + + callKotlin(helper, kotlinLivenessTracker) + } +} + +private class SwiftObject1 {} +private class SwiftObject2 {} +private class SwiftObject3 {} +private class SwiftObject4 {} + +private func testSendKotlinObjectToKotlin() throws { + try testSendToKotlin({ KotlinObject() }) { + $0.sendKotlinObject(kotlinObject: $1) + } +} + +private func testSendSwiftObjectToKotlin() throws { + try testSendToKotlin({ SwiftObject1() }) { + $0.sendSwiftObject(swiftObject: $1) + } +} + +private func testSendListToKotlin() throws { + try testSendToKotlin({ createList() }, flags: TestFlags(trackSwiftLifetime: false)) { + $0.sendList(list: $1) + } +} + +private func testSendStringToKotlin() throws { + try testSendToKotlin({ createString() }, flags: TestFlags(trackSwiftLifetime: false)) { + $0.sendString(string: $1) + } +} + +private func testSendNumberToKotlin() throws { + try testSendToKotlin({ createNumber() }) { + $0.sendNumber(number: $1) + } +} + +private func testSendBlockToKotlin() throws { + try testSendToKotlin({ createBlock() }, flags: TestFlags(trackSwiftLifetime: false)) { + $0.sendBlock(block: $1) + } +} + +private func testReceiveKotlinObjectFromKotlin() throws { + try testReceiveFromKotlin { + $0.receiveKotlinObject() + } +} + +private func testReceiveSwiftObjectFromKotlin() throws { + try testReceiveFromKotlin { (helper: NoAutoreleaseKotlinReceiveHelper) -> Any in + helper.swiftObject = SwiftObject3() + return helper.receiveSwiftObject() + } +} + +private func testReceiveListFromKotlin() throws { + // Swift conversion from NSArray to Swift Array uses non-optimized autorelease in the implementation, + // So regardless of our efforts, passing List from Kotlin to Swift will use autoreleasepool. + // Use `checkAutorelease: false` flag to disable the relevant check. + try testReceiveFromKotlin(flags: TestFlags(trackSwiftLifetime: false, checkAutorelease: false)) { + $0.receiveList() + } +} + +private func testReceiveStringFromKotlin() throws { + try testReceiveFromKotlin(flags: TestFlags(trackSwiftLifetime: false)) { + $0.receiveString() + } +} + +private func testReceiveNumberFromKotlin() throws { + try testReceiveFromKotlin { + $0.receiveNumber() + } +} + +private func testReceiveBlockFromKotlin() throws { + try testReceiveFromKotlin(flags: TestFlags(trackSwiftLifetime: false)) { + $0.receiveBlock() + } +} + +private func testReceiveBlockFromKotlinAndCall() throws { + try testReceiveFromKotlin { + $0.receiveBlock()() + } +} + +private func testCreateKotlinArray() throws { + try testReceiveFromKotlin { (helper: NoAutoreleaseKotlinReceiveHelper) -> KotlinIntArray in + let array = KotlinIntArray(size: 2) + helper.kotlinLivenessTracker.add(obj: array) + return array + } +} + +private func testGetKotlinUnit() throws { + try assertAutoreleasepoolNotUsed { + try assertSame(actual: KotlinUnit.shared, expected: KotlinUnit.shared) + } +} + +private func testGetKotlinSingleton() throws { + try assertAutoreleasepoolNotUsed { + try assertEquals(actual: NoAutoreleaseSingleton.shared.x, expected: 1) + } +} + +private func testGetKotlinEnumEntry() throws { + try assertAutoreleasepoolNotUsed { + try assertEquals(actual: NoAutoreleaseEnum.entry.x, expected: 2) + } +} + +private func testSendKotlinObjectToSwift() throws { + try testCallToSwift { + NoAutoreleaseKt.callSendKotlinObject(helper: $0, tracker: $1) + } +} + +private func testSendSwiftObjectToSwift() throws { + try testCallToSwift { + NoAutoreleaseKt.callSendSwiftObject(helper: $0, tracker: $1, swiftObject: SwiftObject4()) + } +} + +private func testSendListToSwift() throws { + // Swift conversion from NSArray to Swift Array uses non-optimized autorelease in the implementation, + // So regardless of our efforts, passing List from Kotlin to Swift will use autoreleasepool. + // Use `checkAutorelease: false` flag to disable the relevant check. + try testCallToSwift(flags: TestFlags(trackSwiftLifetime: false, checkAutorelease: false)) { + NoAutoreleaseKt.callSendList(helper: $0, tracker: $1) + } +} + +private func testSendStringToSwift() throws { + try testCallToSwift(flags: TestFlags(trackSwiftLifetime: false)) { + NoAutoreleaseKt.callSendString(helper: $0, tracker: $1) + } +} + +private func testSendNumberToSwift() throws { + try testCallToSwift { + NoAutoreleaseKt.callSendNumber(helper: $0, tracker: $1) + } +} + +private func testSendBlockToSwift() throws { + try testCallToSwift(flags: TestFlags(trackSwiftLifetime: false)) { + NoAutoreleaseKt.callSendBlock(helper: $0, tracker: $1) + } +} + +private func testReceiveKotlinObjectFromSwift() throws { + try testCallToSwift { + NoAutoreleaseKt.callReceiveKotlinObject(helper: $0, tracker: $1) + } +} + +private func testReceiveSwiftObjectFromSwift() throws { + try testCallToSwift { + NoAutoreleaseKt.callReceiveSwiftObject(helper: $0, tracker: $1) + } +} + +private func testReceiveListFromSwift() throws { + try testCallToSwift(flags: TestFlags(trackSwiftLifetime: false)) { + NoAutoreleaseKt.callReceiveList(helper: $0, tracker: $1) + } +} + +private func testReceiveStringFromSwift() throws { + try testCallToSwift(flags: TestFlags(trackSwiftLifetime: false)) { + NoAutoreleaseKt.callReceiveString(helper: $0, tracker: $1) + } +} + +private func testReceiveNumberFromSwift() throws { + try testCallToSwift { + NoAutoreleaseKt.callReceiveNumber(helper: $0, tracker: $1) + } +} + +private func testReceiveBlockFromSwift() throws { + try testCallToSwift(flags: TestFlags(trackSwiftLifetime: false)) { + NoAutoreleaseKt.callReceiveBlock(helper: $0, tracker: $1) + } +} + +private func testReceiveBlockFromSwiftAndCall() throws { + try testCallToSwift { + NoAutoreleaseKt.callReceiveBlockAndCall(helper: $0, tracker: $1) + } +} + +private func createList() -> [Any] { + return [NSObject()] +} + +private func createBlock() -> () -> KotlinObject { + let blockResult = KotlinObject() + return { return blockResult } // Make capturing thus dynamic. +} + +private func createString() -> String { + return NSObject().description // Make it dynamic. +} + +private func createNumber() -> NSNumber { + return (0.5 + Double(NSObject().hash)) as NSNumber +} + +class NoAutoreleaseTests : SimpleTestProvider { + override init() { + super.init() + + test("testSendKotlinObjectToKotlin", testSendKotlinObjectToKotlin) + test("testSendSwiftObjectToKotlin", testSendSwiftObjectToKotlin) + test("testSendListToKotlin", testSendListToKotlin) + test("testSendStringToKotlin", testSendStringToKotlin) + test("testSendNumberToKotlin", testSendNumberToKotlin) + test("testSendBlockToKotlin", testSendBlockToKotlin) + + test("testReceiveKotlinObjectFromKotlin", testReceiveKotlinObjectFromKotlin) + test("testReceiveSwiftObjectFromKotlin", testReceiveSwiftObjectFromKotlin) + test("testReceiveListFromKotlin", testReceiveListFromKotlin) + test("testReceiveStringFromKotlin", testReceiveStringFromKotlin) + test("testReceiveNumberFromKotlin", testReceiveNumberFromKotlin) + test("testReceiveBlockFromKotlin", testReceiveBlockFromKotlin) + test("testReceiveBlockFromKotlinAndCall", testReceiveBlockFromKotlinAndCall) + + test("testCreateKotlinArray", testCreateKotlinArray) + + test("testGetKotlinUnit", testGetKotlinUnit) + test("testGetKotlinSingleton", testGetKotlinSingleton) + test("testGetKotlinEnumEntry", testGetKotlinEnumEntry) + +#if false + test("testSendKotlinObjectToSwift", testSendKotlinObjectToSwift) +#endif + + test("testSendSwiftObjectToSwift", testSendSwiftObjectToSwift) + +#if false + test("testSendListToSwift", testSendListToSwift) + test("testSendStringToSwift", testSendStringToSwift) + test("testSendNumberToSwift", testSendNumberToSwift) + test("testSendBlockToSwift", testSendBlockToSwift) + + test("testReceiveKotlinObjectFromSwift", testReceiveKotlinObjectFromSwift) + test("testReceiveSwiftObjectFromSwift", testReceiveSwiftObjectFromSwift) + test("testReceiveListFromSwift", testReceiveListFromSwift) + test("testReceiveStringFromSwift", testReceiveStringFromSwift) + test("testReceiveNumberFromSwift", testReceiveNumberFromSwift) + test("testReceiveBlockFromSwift", testReceiveBlockFromSwift) + test("testReceiveBlockFromSwiftAndCall", testReceiveBlockFromSwiftAndCall) +#endif + } +}