[K/N] Fix a race in MemorySharedRefs ^KT-56233

This commit is contained in:
Alexander Shabalin
2023-03-31 16:24:56 +02:00
committed by Space Team
parent 8a8aeed998
commit 4cd1f2ff82
55 changed files with 1693 additions and 464 deletions
@@ -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::*;