diff --git a/backend.native/tests/framework/values/expectedLazy.h b/backend.native/tests/framework/values/expectedLazy.h index c44a87399cf..e4715cf84e0 100644 --- a/backend.native/tests/framework/values/expectedLazy.h +++ b/backend.native/tests/framework/values/expectedLazy.h @@ -951,6 +951,15 @@ __attribute__((swift_name("TopLevelHidden.InnerInner"))) @interface ValuesTopLevelHiddenInnerInner : NSObject @end; +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("TestWeakRefs"))) +@interface ValuesTestWeakRefs : KotlinBase +- (instancetype)initWithFrozen:(BOOL)frozen __attribute__((swift_name("init(frozen:)"))) __attribute__((objc_designated_initializer)); +- (id)getObj __attribute__((swift_name("getObj()"))); +- (void)clearObj __attribute__((swift_name("clearObj()"))); +- (NSArray *)createCycle __attribute__((swift_name("createCycle()"))); +@end; + @interface ValuesEnumeration (ValuesKt) - (ValuesEnumeration *)getAnswer __attribute__((swift_name("getAnswer()"))); @end; @@ -1029,6 +1038,7 @@ __attribute__((swift_name("ValuesKt"))) + (void)takeForwardDeclaredProtocolObj:(id)obj __attribute__((swift_name("takeForwardDeclaredProtocol(obj:)"))); + (void)error __attribute__((swift_name("error()"))) __attribute__((unavailable("error"))); + (void)warning __attribute__((swift_name("warning()"))) __attribute__((deprecated("warning"))); ++ (void)gc __attribute__((swift_name("gc()"))); @property (class, readonly) double dbl __attribute__((swift_name("dbl"))); @property (class, readonly) float flt __attribute__((swift_name("flt"))); @property (class, readonly) int32_t integer __attribute__((swift_name("integer"))); diff --git a/backend.native/tests/framework/values/values.kt b/backend.native/tests/framework/values/values.kt index 0416a623831..f5f1b4179cd 100644 --- a/backend.native/tests/framework/values/values.kt +++ b/backend.native/tests/framework/values/values.kt @@ -710,3 +710,31 @@ open class TestDeprecation() { @Deprecated("warning", level = DeprecationLevel.WARNING) fun warning() {} @Deprecated("warning", level = DeprecationLevel.WARNING) val warningVal: Any? = null @Deprecated("warning", level = DeprecationLevel.WARNING) var warningVar: Any? = null + +fun gc() { + kotlin.native.internal.GC.collect() +} + +class TestWeakRefs(private val frozen: Boolean) { + private var obj: Any? = Any().also { + if (frozen) it.freeze() + } + + fun getObj() = obj!! + + fun clearObj() { + obj = null + } + + fun createCycle(): List { + val node1 = Node(null) + val node2 = Node(node1) + node1.next = node2 + + if (frozen) node1.freeze() + + return listOf(node1, node2) + } + + private class Node(var next: Node?) +} diff --git a/backend.native/tests/framework/values/values.swift b/backend.native/tests/framework/values/values.swift index 05ccbfd0171..1517aa6f6b2 100644 --- a/backend.native/tests/framework/values/values.swift +++ b/backend.native/tests/framework/values/values.swift @@ -667,6 +667,147 @@ func testDeprecation() throws { try assertEquals(actual: test.callEffectivelyHidden(obj: ImplementingHiddenSubclass()), expected: -2) } +func setAssociatedObject(object: AnyObject, value: AnyObject) { + objc_setAssociatedObject( + object, + UnsafeRawPointer(bitPattern: 1)!, + value, + objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN + ) +} + +func testWeakRefs() throws { + try testWeakRefs0(frozen: false) + try testWeakRefs0(frozen: true) +} + +func testWeakRefs0(frozen: Bool) throws { + func getObj(test: TestWeakRefs) -> AnyObject { + return autoreleasepool { test.getObj() as AnyObject } + } + + func test1() throws { + var test = TestWeakRefs(frozen: frozen) + + var obj: AnyObject? = getObj(test: test) + weak var ref = getObj(test: test) + + ValuesKt.gc() + try assertTrue(ref === getObj(test: test)) // There are both Kotlin and Swift references to the object. + + obj = nil + ValuesKt.gc() + try assertTrue(ref === getObj(test: test)) // There are only Kotlin references to the object. + + test.clearObj() + ValuesKt.gc() + try assertTrue(ref === nil) + } + + func test2() throws { + var test = TestWeakRefs(frozen: frozen) + + var obj: AnyObject? = getObj(test: test) + weak var ref = getObj(test: test) + + ValuesKt.gc() + try assertTrue(ref === obj!) // There are both Kotlin and Swift references to the object. + + test.clearObj() + ValuesKt.gc() + try assertTrue(ref === obj!) // There are only Swift references to the object. + + obj = nil + ValuesKt.gc() + try assertTrue(ref === nil) + } + + func test3() throws { + class Holder { + static weak var ref: AnyObject? = nil + static var deinitialized = false + + deinit { + // Access weak ref to Kotlin object during its counterpart dealloc: + try! assertTrue(Holder.ref === nil) + Holder.deinitialized = true + } + } + + Holder.deinitialized = false + Holder.ref = nil + + var test = TestWeakRefs(frozen: frozen) + + Holder.ref = getObj(test: test) + + // Prepare Holder() to get deinitialized along with getObj(test: test): + setAssociatedObject( + object: getObj(test: test), + value: Holder() + ) + + try assertFalse(Holder.ref === nil) + try assertFalse(Holder.deinitialized) + + test.clearObj() + ValuesKt.gc() + + try assertTrue(Holder.ref === nil) + try assertTrue(Holder.deinitialized) + } + + func test4() throws { + class Holder { + static weak var ref1: AnyObject? = nil + static weak var ref2: AnyObject? = nil + static var deinitialized: Int = 0 + + deinit { + // Access weak ref to Kotlin object during its counterpart dealloc: + try! assertTrue(Holder.ref1 === nil) + try! assertTrue(Holder.ref2 === nil) + Holder.deinitialized += 1 + } + } + + Holder.deinitialized = 0 + Holder.ref1 = nil + Holder.ref2 = nil + + var test = TestWeakRefs(frozen: frozen) + + autoreleasepool { + let cycle = test.createCycle() + + let obj1 = cycle[0] as AnyObject + let obj2 = cycle[1] as AnyObject + + // Prepare Holders to get deinitialized along with obj1 and obj2: + setAssociatedObject(object: obj1, value: Holder()) + setAssociatedObject(object: obj2, value: Holder()) + + Holder.ref1 = obj1 + Holder.ref2 = obj2 + } + + try assertFalse(Holder.ref1 === nil) + try assertFalse(Holder.ref2 === nil) + try assertEquals(actual: Holder.deinitialized, expected: 0) + + ValuesKt.gc() + + try assertTrue(Holder.ref1 === nil) + try assertTrue(Holder.ref2 === nil) + try assertEquals(actual: Holder.deinitialized, expected: 2) + } + + try test1() + try test2() + try test3() + // try test4() +} + // See https://github.com/JetBrains/kotlin-native/issues/2931 func testGH2931() throws { for i in 0..<50000 { @@ -735,6 +876,7 @@ class ValuesTests : TestProvider { TestCase(name: "TestClashes", method: withAutorelease(testClashes)), TestCase(name: "TestInvalidIdentifiers", method: withAutorelease(testInvalidIdentifiers)), TestCase(name: "TestDeprecation", method: withAutorelease(testDeprecation)), + TestCase(name: "TestWeakRefs", method: withAutorelease(testWeakRefs)), TestCase(name: "TestGH2931", method: withAutorelease(testGH2931)), ] }