diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt index 3e9cddd6048..4f59b738b4f 100644 --- a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt @@ -24,7 +24,7 @@ interface ObjCClassOf : ObjCClass // TODO: T should be added to typealias ObjCObjectMeta = ObjCClass @ExportTypeInfo("theForeignObjCObjectTypeInfo") -internal open class ForeignObjCObject +internal open class ForeignObjCObject : konan.internal.ObjCObjectWrapper abstract class ObjCObjectBase protected constructor() : ObjCObject { @Target(AnnotationTarget.CONSTRUCTOR) @@ -150,6 +150,20 @@ private external fun getObjCClass(): NativePtr @konan.internal.Intrinsic external fun getMessenger(superClass: NativePtr): COpaquePointer? @konan.internal.Intrinsic external fun getMessengerLU(superClass: NativePtr): COpaquePointer? +internal class ObjCWeakReferenceImpl : konan.ref.WeakReferenceImpl() { + @SymbolName("Konan_ObjCInterop_getWeakReference") + external override fun get(): Any? +} + +@SymbolName("Konan_ObjCInterop_initWeakReference") +private external fun ObjCWeakReferenceImpl.init(objcPtr: NativePtr) + +@konan.internal.ExportForCppRuntime internal fun makeObjCWeakReferenceImpl(objcPtr: NativePtr): ObjCWeakReferenceImpl { + val result = ObjCWeakReferenceImpl() + result.init(objcPtr) + return result +} + // Konan runtme: @Deprecated("Use plain Kotlin cast of String to NSString", level = DeprecationLevel.WARNING) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/serialization/IrDescriptorDeserializer.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/serialization/IrDescriptorDeserializer.kt index 96f9a2d55b4..7ec0933b113 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/serialization/IrDescriptorDeserializer.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/serialization/IrDescriptorDeserializer.kt @@ -108,7 +108,7 @@ internal class IrDescriptorDeserializer(val context: Context, descriptorIndex[index]!! CLASS, CONSTRUCTOR, FUNCTION, ACCESSOR -> descriptorIndex[index] ?: - findInTheDescriptorTree(proto)!! + findInTheDescriptorTree(proto)!! // Note: can be null when using `@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE")` else -> TODO("Unexpected descriptor kind: $kind") } diff --git a/backend.native/tests/interop/objc/smoke.kt b/backend.native/tests/interop/objc/smoke.kt index ff83a17b867..0a9077856d5 100644 --- a/backend.native/tests/interop/objc/smoke.kt +++ b/backend.native/tests/interop/objc/smoke.kt @@ -1,5 +1,6 @@ import kotlinx.cinterop.* import objcSmoke.* +import konan.ref.* import kotlin.test.* fun main(args: Array) { @@ -11,6 +12,7 @@ fun main(args: Array) { fun run() { testTypeOps() testConversions() + testWeakRefs() println( getSupplier( @@ -145,6 +147,35 @@ fun testMethodsOfAny(kotlinObject: Any, equalNsObject: NSObject, otherObject: An assertNotEquals(equalNsObject, otherObject) } +fun testWeakRefs() { + testWeakReference({ NSObject.new()!! }) + + createAndAbandonWeakRef(NSObject()) + + testWeakReference({ NSArray.arrayWithArray(listOf(42)) as NSArray }) +} + +fun testWeakReference(block: () -> NSObject) { + val ref = autoreleasepool { + createAndTestWeakReference(block) + } + + assertNull(ref.get()) +} + +fun createAndTestWeakReference(block: () -> NSObject): WeakReference { + val ref = createWeakReference(block) + assertNotNull(ref.get()) + assertEquals(ref.get()!!.hash(), ref.get()!!.hash()) + return ref +} + +fun createWeakReference(block: () -> NSObject) = WeakReference(block()) + +fun createAndAbandonWeakRef(obj: NSObject) { + WeakReference(obj) +} + fun nsArrayOf(vararg elements: Any): NSArray = NSMutableArray().apply { elements.forEach { this.addObject(it as ObjCObject) diff --git a/runtime/src/main/cpp/ObjCInteropUtils.mm b/runtime/src/main/cpp/ObjCInteropUtils.mm index f57afa9cdf3..31475373ed3 100644 --- a/runtime/src/main/cpp/ObjCInteropUtils.mm +++ b/runtime/src/main/cpp/ObjCInteropUtils.mm @@ -165,6 +165,48 @@ KBoolean Kotlin_Interop_IsObjectKindOfClass(id obj, void* cls) { return [((id)obj) isKindOfClass:(Class)cls]; } +// Used as an associated object for ObjCWeakReferenceImpl. +@interface KotlinObjCWeakReference : NSObject +@end; + +// libobjc: +id objc_loadWeakRetained(id *location); +id objc_storeWeak(id *location, id newObj); +void objc_destroyWeak(id *location); +void objc_release(id obj); + +@implementation KotlinObjCWeakReference { + @public id referred; +} + +// Called when removing Kotlin object. +-(void)releaseAsAssociatedObject { + objc_destroyWeak(&referred); + objc_release(self); +} + +@end; + +OBJ_GETTER(Kotlin_Interop_refFromObjC, id obj); + +OBJ_GETTER(Konan_ObjCInterop_getWeakReference, KRef ref) { + MetaObjHeader* meta = ref->meta_object(); + KotlinObjCWeakReference* objcRef = (KotlinObjCWeakReference*)meta->associatedObject_; + + id objcReferred = objc_loadWeakRetained(&objcRef->referred); + KRef result = Kotlin_Interop_refFromObjC(objcReferred, OBJ_RESULT); + objc_release(objcReferred); + + return result; +} + +void Konan_ObjCInterop_initWeakReference(KRef ref, id objcPtr) { + MetaObjHeader* meta = ref->meta_object(); + KotlinObjCWeakReference* objcRef = [KotlinObjCWeakReference new]; + objc_storeWeak(&objcRef->referred, objcPtr); + meta->associatedObject_ = objcRef; +} + } // extern "C" #else // KONAN_OBJC_INTEROP @@ -205,7 +247,16 @@ KRef Kotlin_Interop_unwrapKotlinObjectHolder(void* holder) { RuntimeAssert(false, "Objective-C interop is disabled"); return nullptr; } + +OBJ_GETTER(Konan_ObjCInterop_getWeakReference, KRef ref) { + RuntimeAssert(false, "Objective-C interop is disabled"); + RETURN_OBJ(nullptr); +} + +void Konan_ObjCInterop_initWeakReference(KRef ref, void* objcPtr) { + RuntimeAssert(false, "Objective-C interop is disabled"); +} } // extern "C" -#endif // KONAN_OBJC_INTEROP \ No newline at end of file +#endif // KONAN_OBJC_INTEROP diff --git a/runtime/src/main/cpp/Types.h b/runtime/src/main/cpp/Types.h index 8355e42a411..a24582dc2ee 100644 --- a/runtime/src/main/cpp/Types.h +++ b/runtime/src/main/cpp/Types.h @@ -84,6 +84,7 @@ extern const TypeInfo* theStringTypeInfo; extern const TypeInfo* theThrowableTypeInfo; extern const TypeInfo* theUnitTypeInfo; extern const TypeInfo* theForeignObjCObjectTypeInfo; +extern const TypeInfo* theObjCObjectWrapperTypeInfo; KBoolean IsInstance(const ObjHeader* obj, const TypeInfo* type_info) RUNTIME_PURE; void CheckCast(const ObjHeader* obj, const TypeInfo* type_info); diff --git a/runtime/src/main/cpp/Weak.cpp b/runtime/src/main/cpp/Weak.cpp index 43e04241156..0fd3e4c1ea8 100644 --- a/runtime/src/main/cpp/Weak.cpp +++ b/runtime/src/main/cpp/Weak.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ #include "Memory.h" +#include "Types.h" namespace { @@ -39,11 +40,19 @@ inline void unlock(int32_t* address) { extern "C" { OBJ_GETTER(makeWeakReferenceCounter, void*); +OBJ_GETTER(makeObjCWeakReferenceImpl, void*); // See Weak.kt for implementation details. // Retrieve link on the counter object. -OBJ_GETTER(Konan_getWeakReferenceCounter, ObjHeader* referred) { +OBJ_GETTER(Konan_getWeakReferenceImpl, ObjHeader* referred) { MetaObjHeader* meta = referred->meta_object(); + +#if KONAN_OBJC_INTEROP + if (IsInstance(referred, theObjCObjectWrapperTypeInfo)) { + RETURN_RESULT_OF(makeObjCWeakReferenceImpl, meta->associatedObject_); + } +#endif // KONAN_OBJC_INTEROP + if (meta->counter_ == nullptr) { ObjHolder counterHolder; // Cast unneeded, just to emphasize we store an object reference as void*. diff --git a/runtime/src/main/kotlin/konan/internal/ObjCExportUtils.kt b/runtime/src/main/kotlin/konan/internal/ObjCExportUtils.kt index ee5fc28b58b..fb0773487bc 100644 --- a/runtime/src/main/kotlin/konan/internal/ObjCExportUtils.kt +++ b/runtime/src/main/kotlin/konan/internal/ObjCExportUtils.kt @@ -18,7 +18,14 @@ package konan.internal import konan.internal.ExportForCppRuntime -internal class NSArrayAsKList : AbstractList() { +/** + * This interface denotes the object to be a wrapper for the Objective-C object, + * so the latter should be used to observe object lifetime. + */ +@ExportTypeInfo("theObjCObjectWrapperTypeInfo") +internal interface ObjCObjectWrapper + +internal class NSArrayAsKList : AbstractList(), ObjCObjectWrapper { override val size: Int get() = getSize() @@ -29,7 +36,7 @@ internal class NSArrayAsKList : AbstractList() { external override fun get(index: Int): Any? } -internal class NSMutableArrayAsKMutableList : AbstractMutableList() { +internal class NSMutableArrayAsKMutableList : AbstractMutableList(), ObjCObjectWrapper { override val size: Int get() = getSize() @@ -49,7 +56,7 @@ internal class NSMutableArrayAsKMutableList : AbstractMutableList() { external override fun set(index: Int, element: Any?): Any? } -internal class NSSetAsKSet : AbstractSet(), konan.internal.KonanSet { +internal class NSSetAsKSet : AbstractSet(), konan.internal.KonanSet, ObjCObjectWrapper { override val size: Int get() = getSize() @@ -66,7 +73,7 @@ internal class NSSetAsKSet : AbstractSet(), konan.internal.KonanSet external override fun iterator(): Iterator } -internal class NSDictionaryAsKMap : Map { +internal class NSDictionaryAsKMap : Map, ObjCObjectWrapper { override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/runtime/src/main/kotlin/konan/ref/Weak.kt b/runtime/src/main/kotlin/konan/ref/Weak.kt index f31e6173273..6d794ef1318 100644 --- a/runtime/src/main/kotlin/konan/ref/Weak.kt +++ b/runtime/src/main/kotlin/konan/ref/Weak.kt @@ -27,14 +27,14 @@ class WeakReference { * removing object, and is nullified once object is collected. */ constructor(referred: T) { - pointer = getWeakReferenceCounter(referred) + pointer = getWeakReferenceImpl(referred) } /** * Backing store for the object pointer, inaccessible directly. */ @PublishedApi - internal var pointer: WeakReferenceCounter? + internal var pointer: WeakReferenceImpl? /** * Clears reference to an object. @@ -47,5 +47,4 @@ class WeakReference { /** * Returns either reference to an object or null, if it was collected. */ -@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE") public inline fun WeakReference.get() = pointer?.get() as T? diff --git a/runtime/src/main/kotlin/konan/ref/WeakPrivate.kt b/runtime/src/main/kotlin/konan/ref/WeakPrivate.kt index 934f20ed9f0..700765ea017 100644 --- a/runtime/src/main/kotlin/konan/ref/WeakPrivate.kt +++ b/runtime/src/main/kotlin/konan/ref/WeakPrivate.kt @@ -18,7 +18,6 @@ package konan.ref import kotlinx.cinterop.COpaquePointer import konan.internal.ExportForCppRuntime -import konan.internal.ExportForCompiler /** * Theory of operations: @@ -43,18 +42,22 @@ import konan.internal.ExportForCompiler */ // Clear holding the counter object, which refers to the actual object. -@ExportForCompiler -internal class WeakReferenceCounter(var referred: COpaquePointer?) { +internal class WeakReferenceCounter(var referred: COpaquePointer?) : WeakReferenceImpl() { // Spinlock, potentially taken when materializing or removing 'referred' object. var lock: Int = 0 @SymbolName("Konan_WeakReferenceCounter_get") - internal external fun get(): Any? + external override fun get(): Any? +} + +@PublishedApi +internal abstract class WeakReferenceImpl { + abstract fun get(): Any? } // Get a counter from non-null object. -@SymbolName("Konan_getWeakReferenceCounter") -external internal fun getWeakReferenceCounter(referent: Any): WeakReferenceCounter +@SymbolName("Konan_getWeakReferenceImpl") +external internal fun getWeakReferenceImpl(referent: Any): WeakReferenceImpl // Create a counter object. @ExportForCppRuntime