Fix isa swizzling for Kotlin subclasses of Obj-C classes
Don't rely on indexed ivars of instance's isa since isa swizzling replaces it. Use method instead, it works on the replacement too (if it is a subclass of the original isa). #KT-42482 Fixed.
This commit is contained in:
committed by
Stanislav Erokhin
parent
e1d9a36de3
commit
62272f54c1
@@ -46,26 +46,33 @@ const char* Kotlin_ObjCInterop_getUniquePrefix() {
|
||||
|
||||
extern "C" id objc_msgSendSuper2(struct objc_super *super, SEL op, ...);
|
||||
|
||||
struct KotlinClassData {
|
||||
struct KotlinObjCClassData {
|
||||
const TypeInfo* typeInfo;
|
||||
Class objcClass;
|
||||
int32_t bodyOffset;
|
||||
};
|
||||
|
||||
static inline struct KotlinClassData* GetKotlinClassData(Class clazz) {
|
||||
void* ivars = object_getIndexedIvars(reinterpret_cast<id>(clazz));
|
||||
return static_cast<struct KotlinClassData*>(ivars);
|
||||
// Acts only as container for the method, not actually applied to any class.
|
||||
@protocol HasKotlinObjCClassData
|
||||
@required
|
||||
-(void*)_kotlinObjCClassData;
|
||||
@end;
|
||||
|
||||
static inline struct KotlinObjCClassData* GetKotlinClassData(id objOrClass) {
|
||||
void* ptr = [(id<HasKotlinObjCClassData>)objOrClass _kotlinObjCClassData];
|
||||
return static_cast<struct KotlinObjCClassData*>(ptr);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
BackRefFromAssociatedObject* getBackRef(id obj, KotlinClassData* classData) {
|
||||
BackRefFromAssociatedObject* getBackRef(id obj, KotlinObjCClassData* classData) {
|
||||
void* body = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(obj) + classData->bodyOffset);
|
||||
return reinterpret_cast<BackRefFromAssociatedObject*>(body);
|
||||
}
|
||||
|
||||
BackRefFromAssociatedObject* getBackRef(id obj) {
|
||||
// TODO: suboptimal; consider specializing methods for each class.
|
||||
auto* classData = GetKotlinClassData(object_getClass(obj));
|
||||
auto* classData = GetKotlinClassData(obj);
|
||||
return getBackRef(obj, classData);
|
||||
}
|
||||
|
||||
@@ -75,11 +82,11 @@ OBJ_GETTER(toKotlinImp, id self, SEL _cmd) {
|
||||
|
||||
id allocWithZoneImp(Class self, SEL _cmd, void* zone) {
|
||||
// [super allocWithZone:zone]
|
||||
struct objc_super s = {(id)self, object_getClass((id)self)};
|
||||
auto* classData = GetKotlinClassData(self); // TODO: suboptimal; consider specializing.
|
||||
struct objc_super s = {(id)self, object_getClass(classData->objcClass)};
|
||||
auto messenger = reinterpret_cast<id (*) (struct objc_super*, SEL _cmd, void* zone)>(objc_msgSendSuper2);
|
||||
id result = messenger(&s, _cmd, zone);
|
||||
|
||||
auto* classData = GetKotlinClassData(self); // TODO: suboptimal; consider specializing.
|
||||
auto* typeInfo = classData->typeInfo;
|
||||
ObjHolder holder;
|
||||
auto kotlinObj = AllocInstanceWithAssociatedObject(typeInfo, result, holder.slot());
|
||||
@@ -119,6 +126,8 @@ void releaseImp(id self, SEL _cmd) {
|
||||
}
|
||||
|
||||
void releaseAsAssociatedObjectImp(id self, SEL _cmd) {
|
||||
auto* classData = GetKotlinClassData(self);
|
||||
|
||||
// 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])
|
||||
// using the [super release] call below.
|
||||
@@ -131,14 +140,14 @@ void releaseAsAssociatedObjectImp(id self, SEL _cmd) {
|
||||
// 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:
|
||||
getBackRef(self)->detach();
|
||||
getBackRef(self, classData)->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.
|
||||
|
||||
// [super release]
|
||||
Class clazz = object_getClass(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(release));
|
||||
@@ -150,17 +159,12 @@ extern "C" {
|
||||
|
||||
Class Kotlin_Interop_getObjCClass(const char* name);
|
||||
|
||||
static inline void SetKotlinTypeInfo(Class clazz, const TypeInfo* typeInfo) {
|
||||
GetKotlinClassData(clazz)->typeInfo = typeInfo;
|
||||
}
|
||||
|
||||
const TypeInfo* GetObjCKotlinTypeInfo(ObjHeader* obj) RUNTIME_NOTHROW;
|
||||
|
||||
RUNTIME_NOTHROW const TypeInfo* GetObjCKotlinTypeInfo(ObjHeader* obj) {
|
||||
void* objcPtr = obj->GetAssociatedObject();
|
||||
RuntimeAssert(objcPtr != nullptr, "");
|
||||
Class clazz = object_getClass(reinterpret_cast<id>(objcPtr));
|
||||
return GetKotlinClassData(clazz)->typeInfo;
|
||||
return GetKotlinClassData(reinterpret_cast<id>(objcPtr))->typeInfo;
|
||||
}
|
||||
|
||||
|
||||
@@ -181,6 +185,24 @@ static void AddNSObjectOverride(bool isClassMethod, Class clazz, SEL selector, v
|
||||
RuntimeCheck(added, "Unable to add method to Objective-C class");
|
||||
}
|
||||
|
||||
static void AddKotlinClassData(bool isClassMethod, Class clazz, void* imp) {
|
||||
SEL selector = @selector(_kotlinObjCClassData);
|
||||
|
||||
auto methodDescription = protocol_getMethodDescription(
|
||||
@protocol(HasKotlinObjCClassData),
|
||||
selector,
|
||||
YES, YES
|
||||
);
|
||||
|
||||
const char* typeEncoding = methodDescription.types;
|
||||
|
||||
RuntimeCheck(typeEncoding != nullptr, "unable to find method in Objective-C protocol");
|
||||
|
||||
BOOL added = class_addMethod(
|
||||
isClassMethod ? object_getClass((id)clazz) : clazz, selector, (IMP)imp, typeEncoding);
|
||||
RuntimeCheck(added, "Unable to add method to Objective-C class");
|
||||
}
|
||||
|
||||
struct ObjCMethodDescription {
|
||||
void* (*imp)(void*, void*, ...);
|
||||
const char* selector;
|
||||
@@ -206,6 +228,8 @@ struct KotlinObjCClassInfo {
|
||||
const TypeInfo* metaTypeInfo;
|
||||
|
||||
void** createdClass;
|
||||
|
||||
KotlinObjCClassData* (*classDataImp)(void*, void*);
|
||||
};
|
||||
|
||||
static void AddMethods(Class clazz, const struct ObjCMethodDescription* methods, int32_t methodsNum) {
|
||||
@@ -221,11 +245,10 @@ static int anonymousClassNextId = 0;
|
||||
|
||||
static Class allocateClass(const KotlinObjCClassInfo* info) {
|
||||
Class superclass = Kotlin_Interop_getObjCClass(info->superclassName);
|
||||
size_t extraBytes = sizeof(struct KotlinClassData);
|
||||
|
||||
if (info->exported) {
|
||||
RuntimeCheck(info->name != nullptr, "exported Objective-C class must have a name");
|
||||
Class result = objc_allocateClassPair(superclass, info->name, extraBytes);
|
||||
Class result = objc_allocateClassPair(superclass, info->name, 0);
|
||||
if (result != nullptr) return result;
|
||||
// Similar to how Objective-C runtime handles this:
|
||||
fprintf(stderr, "Class %s has multiple implementations. Which one will be used is undefined.\n", info->name);
|
||||
@@ -242,7 +265,7 @@ static Class allocateClass(const KotlinObjCClassInfo* info) {
|
||||
int classId = anonymousClassNextId++;
|
||||
className += std::to_string(classId);
|
||||
|
||||
Class result = objc_allocateClassPair(superclass, className.c_str(), extraBytes);
|
||||
Class result = objc_allocateClassPair(superclass, className.c_str(), 0);
|
||||
RuntimeCheck(result != nullptr, "Failed to allocate Objective-C class");
|
||||
return result;
|
||||
}
|
||||
@@ -284,7 +307,12 @@ void* CreateKotlinObjCClass(const KotlinObjCClassInfo* info) {
|
||||
AddMethods(newClass, info->instanceMethods, info->instanceMethodsNum);
|
||||
AddMethods(newMetaclass, info->classMethods, info->classMethodsNum);
|
||||
|
||||
SetKotlinTypeInfo(newClass, Kotlin_ObjCExport_createTypeInfoWithKotlinFieldsFrom(newClass, info->typeInfo));
|
||||
// Adding both instance and class methods to make [GetKotlinClassData] work
|
||||
// for instances as well as the class itself.
|
||||
AddKotlinClassData(false, newClass, (void*)info->classDataImp);
|
||||
AddKotlinClassData(true, newClass, (void*)info->classDataImp);
|
||||
|
||||
const TypeInfo* actualTypeInfo = Kotlin_ObjCExport_createTypeInfoWithKotlinFieldsFrom(newClass, info->typeInfo);
|
||||
|
||||
int bodySize = sizeof(BackRefFromAssociatedObject);
|
||||
char bodyTypeEncoding[16];
|
||||
@@ -297,9 +325,15 @@ void* CreateKotlinObjCClass(const KotlinObjCClassInfo* info) {
|
||||
Ivar body = class_getInstanceVariable(newClass, "kotlinBody");
|
||||
RuntimeAssert(body != nullptr, "Unable to get ivar added to Objective-C class");
|
||||
int32_t offset = (int32_t)ivar_getOffset(body);
|
||||
GetKotlinClassData(newClass)->bodyOffset = offset;
|
||||
*info->bodyOffset = offset;
|
||||
|
||||
// Doing this after objc_registerClassPair because it is not clear whether calling class methods
|
||||
// is safe before that.
|
||||
auto* classData = GetKotlinClassData(newClass);
|
||||
classData->typeInfo = actualTypeInfo;
|
||||
classData->objcClass = newClass;
|
||||
classData->bodyOffset = offset;
|
||||
|
||||
*info->createdClass = newClass;
|
||||
return newClass;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user