[K/N] Fix a race in MemorySharedRefs ^KT-56233
This commit is contained in:
committed by
Space Team
parent
8a8aeed998
commit
4cd1f2ff82
@@ -86,6 +86,10 @@ ALWAYS_INLINE inline RuntimeAssertsMode runtimeAssertsMode() noexcept {
|
||||
return static_cast<RuntimeAssertsMode>(Kotlin_runtimeAssertsMode);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE inline bool runtimeAssertsEnabled() noexcept {
|
||||
return runtimeAssertsMode() != RuntimeAssertsMode::kIgnore;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE inline std::string_view runtimeLogs() noexcept {
|
||||
return Kotlin_runtimeLogs == nullptr ? std::string_view() : std::string_view(Kotlin_runtimeLogs);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ NO_INLINE void RunFinalizerHooksImpl(ObjHeader* object, const TypeInfo* type) no
|
||||
DisposeCleaner(object);
|
||||
} else if (type == theWorkerBoundReferenceTypeInfo) {
|
||||
DisposeWorkerBoundReference(object);
|
||||
} else if (type == theRegularWeakReferenceImplTypeInfo) {
|
||||
DisposeRegularWeakReferenceImpl(object);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -355,6 +355,8 @@ CODEGEN_INLINE_POLICY RUNTIME_NOTHROW void Kotlin_mm_switchThreadStateRunnable()
|
||||
CODEGEN_INLINE_POLICY void Kotlin_mm_safePointFunctionPrologue() RUNTIME_NOTHROW;
|
||||
CODEGEN_INLINE_POLICY void Kotlin_mm_safePointWhileLoopBody() RUNTIME_NOTHROW;
|
||||
|
||||
RUNTIME_NOTHROW void DisposeRegularWeakReferenceImpl(ObjHeader* counter);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "ManuallyScoped.hpp"
|
||||
#include "Memory.h"
|
||||
#include "Mutex.hpp"
|
||||
|
||||
// TODO: Generalize for uses outside this file.
|
||||
enum class ErrorPolicy {
|
||||
@@ -30,16 +32,14 @@ class KRefSharedHolder {
|
||||
|
||||
void dispose();
|
||||
|
||||
void disposeFromNative() {
|
||||
kotlin::CalledFromNativeGuard guard;
|
||||
dispose();
|
||||
}
|
||||
|
||||
OBJ_GETTER0(describe) const;
|
||||
|
||||
private:
|
||||
ObjHeader* obj_;
|
||||
ForeignRefContext context_;
|
||||
union {
|
||||
ForeignRefContext context_; // Legacy MM.
|
||||
kotlin::mm::RawSpecialRef* ref_; // New MM.
|
||||
};
|
||||
};
|
||||
|
||||
static_assert(std::is_trivially_destructible_v<KRefSharedHolder>, "KRefSharedHolder destructor is not guaranteed to be called.");
|
||||
@@ -47,6 +47,7 @@ static_assert(std::is_trivially_destructible_v<KRefSharedHolder>, "KRefSharedHol
|
||||
class BackRefFromAssociatedObject {
|
||||
public:
|
||||
void initForPermanentObject(ObjHeader* obj);
|
||||
|
||||
void initAndAddRef(ObjHeader* obj);
|
||||
|
||||
// Error if refCount is zero and it's called from the wrong worker with non-frozen obj_.
|
||||
@@ -59,8 +60,11 @@ class BackRefFromAssociatedObject {
|
||||
|
||||
void releaseRef();
|
||||
|
||||
// This does nothing with the new MM.
|
||||
void detach();
|
||||
void assertDetached();
|
||||
|
||||
// This does nothing with legacy MM.
|
||||
void dealloc();
|
||||
|
||||
// Error if called from the wrong worker with non-frozen obj_.
|
||||
template <ErrorPolicy errorPolicy>
|
||||
@@ -69,9 +73,18 @@ class BackRefFromAssociatedObject {
|
||||
ObjHeader* refPermanent() const;
|
||||
|
||||
private:
|
||||
ObjHeader* obj_; // May be null before [initAndAddRef] or after [detach].
|
||||
ForeignRefContext context_;
|
||||
volatile int refCount;
|
||||
union {
|
||||
struct {
|
||||
ObjHeader* obj_; // May be null before [initAndAddRef] or after [detach].
|
||||
ForeignRefContext context_;
|
||||
volatile int refCount;
|
||||
}; // Legacy MM
|
||||
struct {
|
||||
kotlin::mm::RawSpecialRef* ref_;
|
||||
kotlin::ManuallyScoped<kotlin::RWSpinLock<kotlin::MutexThreadStateHandling::kIgnore>> deallocMutex_;
|
||||
}; // New MM. Regular object.
|
||||
ObjHeader* permanentObj_; // New MM. Permanent object.
|
||||
};
|
||||
};
|
||||
|
||||
static_assert(
|
||||
|
||||
@@ -99,9 +99,9 @@ static Class getOrCreateClass(const TypeInfo* typeInfo);
|
||||
|
||||
namespace {
|
||||
|
||||
ALWAYS_INLINE void send_releaseAsAssociatedObject(void* associatedObject, ReleaseMode mode) {
|
||||
auto msgSend = reinterpret_cast<void (*)(void* self, SEL cmd, ReleaseMode mode)>(&objc_msgSend);
|
||||
msgSend(associatedObject, Kotlin_ObjCExport_releaseAsAssociatedObjectSelector, mode);
|
||||
ALWAYS_INLINE void send_releaseAsAssociatedObject(void* associatedObject) {
|
||||
auto msgSend = reinterpret_cast<void (*)(void* self, SEL cmd)>(&objc_msgSend);
|
||||
msgSend(associatedObject, Kotlin_ObjCExport_releaseAsAssociatedObjectSelector);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -109,22 +109,7 @@ ALWAYS_INLINE void send_releaseAsAssociatedObject(void* associatedObject, Releas
|
||||
extern "C" ALWAYS_INLINE void Kotlin_ObjCExport_releaseAssociatedObject(void* associatedObject) {
|
||||
if (associatedObject != nullptr) {
|
||||
kotlin::ThreadStateGuard guard(kotlin::ThreadState::kNative);
|
||||
send_releaseAsAssociatedObject(associatedObject, ReleaseMode::kRelease);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" ALWAYS_INLINE void Kotlin_ObjCExport_detachAndReleaseAssociatedObject(void* associatedObject) {
|
||||
if (associatedObject != nullptr) {
|
||||
kotlin::ThreadStateGuard guard(kotlin::ThreadState::kNative);
|
||||
send_releaseAsAssociatedObject(associatedObject, ReleaseMode::kDetachAndRelease);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" ALWAYS_INLINE void Kotlin_ObjCExport_detachAssociatedObject(void* associatedObject) {
|
||||
if (associatedObject != nullptr) {
|
||||
// Switching to Native state is not required, because detach is fast and can't call user code.
|
||||
// Also switching is not possible, because this is called from GC.
|
||||
send_releaseAsAssociatedObject(associatedObject, ReleaseMode::kDetach);
|
||||
send_releaseAsAssociatedObject(associatedObject);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +285,7 @@ static OBJ_GETTER(blockToKotlinImp, id self, SEL cmd);
|
||||
static OBJ_GETTER(boxedBooleanToKotlinImp, NSNumber* self, SEL cmd);
|
||||
|
||||
static OBJ_GETTER(SwiftObject_toKotlinImp, id self, SEL cmd);
|
||||
static void SwiftObject_releaseAsAssociatedObjectImp(id self, SEL cmd, ReleaseMode mode);
|
||||
static void SwiftObject_releaseAsAssociatedObjectImp(id self, SEL cmd);
|
||||
|
||||
static void initTypeAdaptersFrom(const ObjCTypeAdapter** adapters, int count) {
|
||||
for (int index = 0; index < count; ++index) {
|
||||
@@ -361,7 +346,7 @@ static void Kotlin_ObjCExport_initializeImpl() {
|
||||
swiftRootClass, releaseAsAssociatedObjectSelector,
|
||||
(IMP)SwiftObject_releaseAsAssociatedObjectImp, releaseAsAssociatedObjectTypeEncoding
|
||||
);
|
||||
RuntimeAssert(added, "Unable to add 'releaseAsAssociatedObject:' method to SwiftObject class");
|
||||
RuntimeAssert(added, "Unable to add 'releaseAsAssociatedObject' method to SwiftObject class");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -381,9 +366,7 @@ static OBJ_GETTER(SwiftObject_toKotlinImp, id self, SEL cmd) {
|
||||
RETURN_RESULT_OF(Kotlin_ObjCExport_convertUnmappedObjCObject, self);
|
||||
}
|
||||
|
||||
static void SwiftObject_releaseAsAssociatedObjectImp(id self, SEL cmd, ReleaseMode mode) {
|
||||
if (!ReleaseModeHasRelease(mode))
|
||||
return;
|
||||
static void SwiftObject_releaseAsAssociatedObjectImp(id self, SEL cmd) {
|
||||
objc_release(self);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,32 +18,6 @@
|
||||
+(instancetype)createRetainedWrapper:(ObjHeader*)obj;
|
||||
@end
|
||||
|
||||
enum class ReleaseMode {
|
||||
kRelease,
|
||||
kDetachAndRelease,
|
||||
kDetach,
|
||||
};
|
||||
|
||||
inline bool ReleaseModeHasDetach(ReleaseMode mode) {
|
||||
switch (mode) {
|
||||
case ReleaseMode::kRelease:
|
||||
return false;
|
||||
case ReleaseMode::kDetachAndRelease:
|
||||
case ReleaseMode::kDetach:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool ReleaseModeHasRelease(ReleaseMode mode) {
|
||||
switch (mode) {
|
||||
case ReleaseMode::kRelease:
|
||||
case ReleaseMode::kDetachAndRelease:
|
||||
return true;
|
||||
case ReleaseMode::kDetach:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void Kotlin_ObjCExport_initializeClass(Class clazz);
|
||||
extern "C" const TypeInfo* Kotlin_ObjCExport_getAssociatedTypeInfo(Class clazz);
|
||||
extern "C" OBJ_GETTER(Kotlin_ObjCExport_convertUnmappedObjCObject, id obj);
|
||||
|
||||
@@ -115,8 +115,18 @@ void releaseImp(id self, SEL _cmd) {
|
||||
getBackRef(self)->releaseRef();
|
||||
}
|
||||
|
||||
void releaseAsAssociatedObjectImp(id self, SEL _cmd, ReleaseMode mode) {
|
||||
void releaseAsAssociatedObjectImp(id self, SEL _cmd) {
|
||||
auto* classData = GetKotlinClassData(self);
|
||||
if (CurrentMemoryModel == MemoryModel::kExperimental) {
|
||||
// No need for any special handling. Weak reference handling machinery
|
||||
// has already cleaned up the reference to Kotlin object.
|
||||
// [super release]
|
||||
Class clazz = classData->objcClass;
|
||||
struct objc_super s = {self, clazz};
|
||||
auto messenger = reinterpret_cast<void (*) (struct objc_super*, SEL _cmd)>(objc_msgSendSuper2);
|
||||
messenger(&s, @selector(release));
|
||||
return;
|
||||
}
|
||||
|
||||
// This function is called by the GC. It made a decision to reclaim Kotlin object, and runs
|
||||
// deallocation hooks at the moment, including deallocation of the "associated object" ([self])
|
||||
@@ -132,24 +142,28 @@ void releaseAsAssociatedObjectImp(id self, SEL _cmd, ReleaseMode mode) {
|
||||
// Generally retaining and releasing Kotlin object that is being deallocated would lead to
|
||||
// use-after-dispose and double-dispose problems (with unpredictable consequences) or to an assertion failure.
|
||||
// To workaround this, detach the back ref from the Kotlin object:
|
||||
if (ReleaseModeHasDetach(mode)) {
|
||||
backRef->detach();
|
||||
} else {
|
||||
// With Mark&Sweep this object should already have been detached earlier.
|
||||
backRef->assertDetached();
|
||||
}
|
||||
backRef->detach();
|
||||
|
||||
// So retain/release/etc. on [self] won't affect the Kotlin object, and an attempt to get
|
||||
// the reference to it (e.g. when calling Kotlin method on [self]) would crash.
|
||||
// The latter is generally ok, because by the time superclass dealloc gets launched, subclass state
|
||||
// should already be deinitialized, and Kotlin methods operate on the subclass.
|
||||
if (ReleaseModeHasRelease(mode)) {
|
||||
// [super release]
|
||||
Class clazz = classData->objcClass;
|
||||
struct objc_super s = {self, clazz};
|
||||
auto messenger = reinterpret_cast<void (*) (struct objc_super*, SEL _cmd)>(objc_msgSendSuper2);
|
||||
messenger(&s, @selector(release));
|
||||
}
|
||||
// [super release]
|
||||
Class clazz = classData->objcClass;
|
||||
struct objc_super s = {self, clazz};
|
||||
auto messenger = reinterpret_cast<void (*) (struct objc_super*, SEL _cmd)>(objc_msgSendSuper2);
|
||||
messenger(&s, @selector(release));
|
||||
}
|
||||
|
||||
void deallocImp(id self, SEL _cmd) {
|
||||
getBackRef(self)->dealloc();
|
||||
|
||||
// [super dealloc]
|
||||
auto* classData = GetKotlinClassData(self);
|
||||
Class clazz = classData->objcClass;
|
||||
struct objc_super s = {self, clazz};
|
||||
auto messenger = reinterpret_cast<void (*) (struct objc_super*, SEL _cmd)>(objc_msgSendSuper2);
|
||||
messenger(&s, @selector(dealloc));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -277,6 +291,9 @@ void* CreateKotlinObjCClass(const KotlinObjCClassInfo* info) {
|
||||
AddNSObjectOverride(false, newClass, @selector(release), (void*)&releaseImp);
|
||||
AddNSObjectOverride(false, newClass, Kotlin_ObjCExport_releaseAsAssociatedObjectSelector,
|
||||
(void*)&releaseAsAssociatedObjectImp);
|
||||
if (CurrentMemoryModel == MemoryModel::kExperimental) {
|
||||
AddNSObjectOverride(false, newClass, @selector(dealloc), (void*)&deallocImp);
|
||||
}
|
||||
|
||||
AddMethods(newClass, info->instanceMethods, info->instanceMethodsNum);
|
||||
AddMethods(newMetaclass, info->classMethods, info->classMethodsNum);
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
#if KONAN_OBJC_INTEROP
|
||||
|
||||
extern "C" ALWAYS_INLINE void Kotlin_ObjCExport_releaseAssociatedObject(void* associatedObject);
|
||||
extern "C" ALWAYS_INLINE void Kotlin_ObjCExport_detachAndReleaseAssociatedObject(void* associatedObject);
|
||||
extern "C" ALWAYS_INLINE void Kotlin_ObjCExport_detachAssociatedObject(void* associatedObject);
|
||||
|
||||
namespace konan {
|
||||
class AutoreleasePool : private kotlin::Pinned {
|
||||
|
||||
@@ -345,6 +345,7 @@ public:
|
||||
};
|
||||
|
||||
struct RegularWeakReferenceImplPayload {
|
||||
void* weakRef;
|
||||
void* referred;
|
||||
|
||||
using Field = ObjHeader* RegularWeakReferenceImplPayload::*;
|
||||
|
||||
Reference in New Issue
Block a user