diff --git a/kotlin-native/backend.native/tests/build.gradle b/kotlin-native/backend.native/tests/build.gradle index 324022c3edc..8c3d6d1a82e 100644 --- a/kotlin-native/backend.native/tests/build.gradle +++ b/kotlin-native/backend.native/tests/build.gradle @@ -5960,6 +5960,23 @@ if (isAppleTarget(project)) { } } } + + frameworkTest("testKt56233Framework") { + enabled = isExperimentalMM // Fails with legacy MM. It's deprecated, so won't fix. + framework("Kt56233") { + sources = ['framework/kt56233'] + } + swiftSources = ['framework/kt56233'] + } + + frameworkTest("testPermanentObjectsFramework") { + enabled = isExperimentalMM + framework("PermanentObjects") { + sources = ['framework/permanentObjects'] + opts = ['-opt-in=kotlin.native.internal.InternalForKotlinNative'] + } + swiftSources = ['framework/permanentObjects'] + } } /** diff --git a/kotlin-native/backend.native/tests/framework/kt56233/knlibrary.kt b/kotlin-native/backend.native/tests/framework/kt56233/knlibrary.kt new file mode 100644 index 00000000000..9e65412eb1e --- /dev/null +++ b/kotlin-native/backend.native/tests/framework/kt56233/knlibrary.kt @@ -0,0 +1,4 @@ +enum class SimpleEnum { + ONE, + TWO +} diff --git a/kotlin-native/backend.native/tests/framework/kt56233/test.swift b/kotlin-native/backend.native/tests/framework/kt56233/test.swift new file mode 100644 index 00000000000..7e201377e6b --- /dev/null +++ b/kotlin-native/backend.native/tests/framework/kt56233/test.swift @@ -0,0 +1,44 @@ +import Kt56233 + +func threadRoutine(pointer: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer? { + autoreleasepool { + let f = pointer.bindMemory(to: (() -> ()).self, capacity: 1).pointee + f() + } + return nil +} + +func launchThreads( + _ f: @convention(c) () -> (), + threadCount: Int = 4 +) throws { + var threads: [pthread_t] = [] + for _ in 0.. ()>.allocate(capacity: 1) + fPtr.initialize(to: f) + var thread: pthread_t? = nil + let result = pthread_create(&thread, nil, threadRoutine, fPtr) + try assertEquals(actual: result, expected: 0) + threads.append(thread!) + } + for thread in threads { + pthread_join(thread, nil) + } +} + +func kt56233() { + // Stress testing for race conditions. + for _ in 0..<50000000 { + _ = Kt56233.SimpleEnum.two.ordinal + } +} + +// -------- Execution of the test -------- + +class TestTests : SimpleTestProvider { + override init() { + super.init() + + test("Kt56233", { try launchThreads(kt56233) }) + } +} diff --git a/kotlin-native/backend.native/tests/framework/permanentObjects/knlibrary.kt b/kotlin-native/backend.native/tests/framework/permanentObjects/knlibrary.kt new file mode 100644 index 00000000000..e6d13726290 --- /dev/null +++ b/kotlin-native/backend.native/tests/framework/permanentObjects/knlibrary.kt @@ -0,0 +1,25 @@ +@file:OptIn(kotlin.ExperimentalStdlibApi::class) + +import kotlin.native.internal.GC +import kotlin.native.internal.gc.GCInfo +import kotlin.native.internal.isPermanent +import kotlin.test.* + +private var _counter = 0 + +object Permanent { + var counter + get() = _counter + set(value) { + _counter = value + } +} + +fun assertIsPermanent() { + assertTrue(Permanent.isPermanent()) +} + +fun stableRefsCount(): Long { + GC.collect() + return GC.lastGCInfo!!.rootSet.stableReferences +} diff --git a/kotlin-native/backend.native/tests/framework/permanentObjects/test.swift b/kotlin-native/backend.native/tests/framework/permanentObjects/test.swift new file mode 100644 index 00000000000..5fa78623d93 --- /dev/null +++ b/kotlin-native/backend.native/tests/framework/permanentObjects/test.swift @@ -0,0 +1,24 @@ +import PermanentObjects + +func testPermanentObjects() throws { + PermanentObjects.KnlibraryKt.assertIsPermanent() + let stableRefsBefore = PermanentObjects.KnlibraryKt.stableRefsCount() + autoreleasepool { + for i in 0..<1000 { + PermanentObjects.Permanent().counter += 1 + } + } + let stableRefsAfter = PermanentObjects.KnlibraryKt.stableRefsCount() + try assertEquals(actual: PermanentObjects.Permanent().counter, expected: 1000) + try assertEquals(actual: stableRefsAfter, expected: stableRefsBefore) +} + +// -------- Execution of the test -------- + +class TestTests : SimpleTestProvider { + override init() { + super.init() + + test("permanentObjects", testPermanentObjects) + } +} diff --git a/kotlin-native/backend.native/tests/interop/objc/tests/kt41811.h b/kotlin-native/backend.native/tests/interop/objc/tests/kt41811.h index ed41355e86f..d7c1436ded6 100644 --- a/kotlin-native/backend.native/tests/interop/objc/tests/kt41811.h +++ b/kotlin-native/backend.native/tests/interop/objc/tests/kt41811.h @@ -22,3 +22,9 @@ extern BOOL deallocLoadWeakDeallocated; @interface DeallocLoadWeak : NSObject -(void)checkWeak; @end + +extern BOOL deallocRetainAndAccessDeallocated; + +@interface DeallocRetainAndAccess : NSObject +@property void (^onDealloc)(id); +@end diff --git a/kotlin-native/backend.native/tests/interop/objc/tests/kt41811.kt b/kotlin-native/backend.native/tests/interop/objc/tests/kt41811.kt index ae0c2dd50d6..cc7b7b6878c 100644 --- a/kotlin-native/backend.native/tests/interop/objc/tests/kt41811.kt +++ b/kotlin-native/backend.native/tests/interop/objc/tests/kt41811.kt @@ -116,3 +116,44 @@ fun testKT41811WithGlobal() { assertTrue(deallocRetainReleaseDeallocated) } + +var localDeallocRetainAndAccessDeallocated = false + +@Test +fun testKT41811WithAccess() { + // Legacy MM crashes with an assertion failure. + @OptIn(kotlin.ExperimentalStdlibApi::class) + if (!isExperimentalMM()) + return + + // Attempt to make the state predictable: + kotlin.native.internal.GC.collect() + + deallocRetainAndAccessDeallocated = false + localDeallocRetainAndAccessDeallocated = false + + createGarbageDeallocRetainAndAccess() + + // Runs [DeallocRetainAndAccess dealloc]: + kotlin.native.internal.GC.collect() + + assertTrue(deallocRetainAndAccessDeallocated) + assertTrue(localDeallocRetainAndAccessDeallocated) + + // Might crash due to double-dispose if the dealloc applied addRef/releaseRef to reclaimed Kotlin object: + kotlin.native.internal.GC.collect() +} + +private fun createGarbageDeallocRetainAndAccess() { + autoreleasepool { + object : DeallocRetainAndAccess() { + init { + onDealloc = { + assertNull(it) + assertFalse(localDeallocRetainAndAccessDeallocated) + localDeallocRetainAndAccessDeallocated = true + } + } + } + } +} diff --git a/kotlin-native/backend.native/tests/interop/objc/tests/kt41811.m b/kotlin-native/backend.native/tests/interop/objc/tests/kt41811.m index bd65e9677d0..58e2ee7b977 100644 --- a/kotlin-native/backend.native/tests/interop/objc/tests/kt41811.m +++ b/kotlin-native/backend.native/tests/interop/objc/tests/kt41811.m @@ -37,3 +37,20 @@ BOOL deallocLoadWeakDeallocated = NO; deallocLoadWeakDeallocated = YES; } @end + +id retainObject2 = nil; +BOOL deallocRetainAndAccessDeallocated = NO; + +@implementation DeallocRetainAndAccess + +- (void)dealloc { + retainObject2 = self; + assert(_onDealloc != nil); + _onDealloc(retainObject2); + retainObject2 = nil; + + assert(!deallocRetainAndAccessDeallocated); + deallocRetainAndAccessDeallocated = YES; +} + +@end diff --git a/kotlin-native/backend.native/tests/objcexport/deallocRetain.kt b/kotlin-native/backend.native/tests/objcexport/deallocRetain.kt index 540e3776b62..5adf944df88 100644 --- a/kotlin-native/backend.native/tests/objcexport/deallocRetain.kt +++ b/kotlin-native/backend.native/tests/objcexport/deallocRetain.kt @@ -10,3 +10,10 @@ open class DeallocRetainBase fun garbageCollect() = kotlin.native.internal.GC.collect() fun createWeakReference(value: Any) = kotlin.native.ref.WeakReference(value) + +fun assertNull(value: Any?) { + kotlin.test.assertNull(value) +} + +@OptIn(kotlin.ExperimentalStdlibApi::class) +fun isExperimentalMM() = kotlin.native.isExperimentalMM() diff --git a/kotlin-native/backend.native/tests/objcexport/deallocRetain.swift b/kotlin-native/backend.native/tests/objcexport/deallocRetain.swift index 97186c66bd0..7754b871949 100644 --- a/kotlin-native/backend.native/tests/objcexport/deallocRetain.swift +++ b/kotlin-native/backend.native/tests/objcexport/deallocRetain.swift @@ -62,12 +62,48 @@ private class DeallocRetain : DeallocRetainBase { } } +private class DeallocRetainAndAccess : DeallocRetainBase { + static var deallocated = false + static var retainObject: DeallocRetainAndAccess? = nil + + deinit { + DeallocRetainAndAccess.retainObject = self + DeallocRetainKt.assertNull(value: DeallocRetainAndAccess.retainObject) + DeallocRetainAndAccess.retainObject = nil + + try! assertFalse(DeallocRetainAndAccess.deallocated) + DeallocRetainAndAccess.deallocated = true + } +} + +private func test2() throws { + // Attempt to make the state predictable: + DeallocRetainKt.garbageCollect() + + DeallocRetainAndAccess.deallocated = false + + autoreleasepool { + DeallocRetainAndAccess() + } + + // Runs DeallocRetainAndAccess.deinit: + DeallocRetainKt.garbageCollect() + + try assertTrue(DeallocRetain.deallocated) + + // Might crash due to double-dispose if the dealloc applied addRef/releaseRef to reclaimed Kotlin object: + DeallocRetainKt.garbageCollect() +} + class DeallocRetainTests : SimpleTestProvider { override init() { super.init() #if !NOOP_GC test("Test1", test1) + if (DeallocRetainKt.isExperimentalMM()) { + test("Test2", test2) + } #endif } } diff --git a/kotlin-native/backend.native/tests/objcexport/expectedLazy.h b/kotlin-native/backend.native/tests/objcexport/expectedLazy.h index 02b115dc4b4..ebd0de1db99 100644 --- a/kotlin-native/backend.native/tests/objcexport/expectedLazy.h +++ b/kotlin-native/backend.native/tests/objcexport/expectedLazy.h @@ -481,6 +481,8 @@ __attribute__((swift_name("DeallocRetainKt"))) @interface KtDeallocRetainKt : KtBase + (void)garbageCollect __attribute__((swift_name("garbageCollect()"))); + (KtKotlinWeakReference *)createWeakReferenceValue:(id)value __attribute__((swift_name("createWeakReference(value:)"))); ++ (void)assertNullValue:(id _Nullable)value __attribute__((swift_name("assertNull(value:)"))); ++ (BOOL)isExperimentalMM __attribute__((swift_name("isExperimentalMM()"))); @end __attribute__((objc_subclassing_restricted)) diff --git a/kotlin-native/backend.native/tests/objcexport/expectedLazyLegacySuspendUnit.h b/kotlin-native/backend.native/tests/objcexport/expectedLazyLegacySuspendUnit.h index 687cdd67740..093460d9588 100644 --- a/kotlin-native/backend.native/tests/objcexport/expectedLazyLegacySuspendUnit.h +++ b/kotlin-native/backend.native/tests/objcexport/expectedLazyLegacySuspendUnit.h @@ -481,6 +481,8 @@ __attribute__((swift_name("DeallocRetainKt"))) @interface KtDeallocRetainKt : KtBase + (void)garbageCollect __attribute__((swift_name("garbageCollect()"))); + (KtKotlinWeakReference *)createWeakReferenceValue:(id)value __attribute__((swift_name("createWeakReference(value:)"))); ++ (void)assertNullValue:(id _Nullable)value __attribute__((swift_name("assertNull(value:)"))); ++ (BOOL)isExperimentalMM __attribute__((swift_name("isExperimentalMM()"))); @end __attribute__((objc_subclassing_restricted)) diff --git a/kotlin-native/backend.native/tests/objcexport/expectedLazyNoGenerics.h b/kotlin-native/backend.native/tests/objcexport/expectedLazyNoGenerics.h index 8c8e9ad7f39..302aba0a790 100644 --- a/kotlin-native/backend.native/tests/objcexport/expectedLazyNoGenerics.h +++ b/kotlin-native/backend.native/tests/objcexport/expectedLazyNoGenerics.h @@ -481,6 +481,8 @@ __attribute__((swift_name("DeallocRetainKt"))) @interface KtDeallocRetainKt : KtBase + (void)garbageCollect __attribute__((swift_name("garbageCollect()"))); + (KtKotlinWeakReference *)createWeakReferenceValue:(id)value __attribute__((swift_name("createWeakReference(value:)"))); ++ (void)assertNullValue:(id _Nullable)value __attribute__((swift_name("assertNull(value:)"))); ++ (BOOL)isExperimentalMM __attribute__((swift_name("isExperimentalMM()"))); @end __attribute__((objc_subclassing_restricted))