Few cents about my commits

ios12: class_copyProtocolList crash

|

Issue was reported on gitter and caused crash inside ObjC library of RoboVM with following stacktrace:

0 _mapStrHash(_NXMapTable*, void const*) + 4
1 _NXMapMember(_NXMapTable*, void const*, void**) + 52
2 NXMapGet + 20
3 getProtocol(char const*) + 28
4 class_copyProtocolList + 328
5 [J]org.robovm.objc.ObjCRuntime.class_copyProtocolList(JJ)J + 4358277632

Issue was reproduced on iOS12 simulator. Sample was simplified to following RoboVM/Java snippet:

@CustomClass("Test")
public class Test extends NSObject{
    @Method
    public void foo(NSDictionary<NSString, NSString> p) {
        System.out.println("Hello: " + p);
    }
}

That to be called from Swift code like bellow:

  Test().foo([:])

As there is a lot of work happening in marshaller code of ObjCObject.toObjCObject sample was modified to eliminate marshaller code and do it in minimal code:

    @Method
    public void foo(@Pointer long handle){
        long classPtr=ObjCRuntime.object_getClass(handle);
        long protocols=ObjCRuntime.class_copyProtocolList(classPtr,0);
    }

It produces same crash as in top of post. Strange thing if similar code is implemented in Objective-C and called from swift WORKS. Both RoboVM and ObjectiveC cases were debugged with XCode.
In case of RoboVM structures of cls->data()->protocols were not initialized and code picked garbage string. Thanks, Apple, for opensourcing this code.

Search for similar cases bring me to Xamarin and root case is bug in ObjectiveC runtime.
The fix is to call cls = [obj class] instead of cls = ObjCRuntime.object_getClass(obj).
In case of RoboVM things are bit complicated as code works on ObjectiveC level and there is no NSObject protocol known. So the way is to use selectors:

    public static ObjCClass getFromObject(long handle) {
        long classPtr = ObjCRuntime.object_getClass(handle);
        // dkimitsa. There is a bug observed in iOS12 that causes not all Objective-C class fields properly initialized
        // in Class instance of Swift classes. This causes a crash in APIs like class_copyProtocolList to crash
        // as it faces not initialized data. Example of such class is `Swift.__EmptyDictionarySingleton`.
        // Workaround for this case is to call [NSObject class] selector that initializes all structs.
        // ObjCClass is not always NSObject check for responds to selector is required here.
        // similar case: https://github.com/xamarin/xamarin-macios/pull/6293
        if (classPtr != 0 && ObjCRuntime.class_respondsToSelector(classPtr, SELECTOR_NSOBJECT_CLASS.getHandle())) {
            classPtr = ObjCRuntime.ptr_objc_msgSend(handle, SELECTOR_NSOBJECT_CLASS.getHandle());
        }
        return toObjCClass(classPtr);
    }

The fix was delivered as PR588

Comments