From 1d11deb22a31c90a296b3ade5df2f4f265750b1c Mon Sep 17 00:00:00 2001 From: Demyan Kimitsa Date: Tue, 7 May 2024 11:55:50 +0300 Subject: [PATCH] * fixed: ObjCClassNotFoundException when marshaling protocols implemented in pure Swift classes (#784) reported over gitter, in scope of CleverAds: ``` "Terminating app due to uncaught exception 'org.robovm.objc.ObjCClassNotFoundException', reason: 'org.robovm.objc.ObjCClassNotFoundException: Could not find Java class corresponding to Objective-C class: CleverAdsSolutions.ImpressionWrapper". ``` ## Root case Then trying to marshal pointer to Java world and turn it into Interface instance ObjC runtime tries find as much complete as possible class instance representation from this pointer to allow to have not just as $ObjCProxy of this interface/protocol but a proper class instance. In case of reported issue: there was expected `CASStatusHandler` protocol in callback. CAS provided pure Swift class `CleverAdsSolutions.ImpressionWrapper` that implemented it. This class is extended from `Swift._SwiftObject` and this class is not known to RoboVM as well. As result everything was terminated with: ObjCClassNotFoundException What is wrong here: - if pointer is not resolved to any class -- $ObjCProxy should be used to marshal into interface implementation; - even if pointer was resolved to best available class in hierarchy (lets say NSObject), it might be not top one that implement interface itself. As result $ObjCProxy will be used as target class for marshalling. ## The fix: consider `ObjCClassNotFoundException` case similar to `not isAssignableFrom` and use $ObjCProxy in both case. changes were done to not throw ObjCClassNotFoundException in case class being resolved on behalf of $ObjCProxy --- .../src/main/java/org/robovm/objc/ObjCClass.java | 14 +++++++++++--- .../src/main/java/org/robovm/objc/ObjCObject.java | 12 ++++++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/compiler/objc/src/main/java/org/robovm/objc/ObjCClass.java b/compiler/objc/src/main/java/org/robovm/objc/ObjCClass.java index fd3009c02..4d3b066f9 100755 --- a/compiler/objc/src/main/java/org/robovm/objc/ObjCClass.java +++ b/compiler/objc/src/main/java/org/robovm/objc/ObjCClass.java @@ -248,8 +248,12 @@ public static ObjCClass getFromObject(ObjCObject id) { } return getByType(id.getClass()); } - + public static ObjCClass getFromObject(long handle) { + return getFromObject(handle, false); + } + + public static ObjCClass getFromObject(long handle, boolean optional) { 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 @@ -260,7 +264,7 @@ public static ObjCClass getFromObject(long handle) { if (classPtr != 0 && ObjCRuntime.class_respondsToSelector(classPtr, SELECTOR_NSOBJECT_CLASS.getHandle())) { classPtr = ObjCRuntime.ptr_objc_msgSend(handle, SELECTOR_NSOBJECT_CLASS.getHandle()); } - return toObjCClass(classPtr); + return toObjCClass(classPtr, optional); } public static ObjCClass getByType(Class type) { @@ -328,6 +332,10 @@ private static List getProtocols(long handle, boolean isProtocol) { } public static ObjCClass toObjCClass(final long handle) { + return toObjCClass(handle, false); + } + + public static ObjCClass toObjCClass(final long handle, final boolean optional) { long classPtr = handle; ObjCClass c = ObjCObject.getPeerObject(classPtr); if (c == null) { @@ -358,7 +366,7 @@ public static ObjCClass toObjCClass(final long handle) { } } } - if (c == null) { + if (c == null && !optional) { String name = VM.newStringUTF(ObjCRuntime.class_getName(handle)); throw new ObjCClassNotFoundException("Could not find Java class corresponding to Objective-C class: " + name); } diff --git a/compiler/objc/src/main/java/org/robovm/objc/ObjCObject.java b/compiler/objc/src/main/java/org/robovm/objc/ObjCObject.java index 082e33cb8..215263129 100755 --- a/compiler/objc/src/main/java/org/robovm/objc/ObjCObject.java +++ b/compiler/objc/src/main/java/org/robovm/objc/ObjCObject.java @@ -328,8 +328,16 @@ public static T toObjCObject(Class cls, long handle, i } } - ObjCClass objCClass = ObjCClass.getFromObject(handle); - if (!expectedType.isAssignableFrom(objCClass.getType())) { + // dkimitsa: when ObjCProxy is a target at java level it expected to return Interface/Protocol implementation + // But not always is possible to recognizable ObjC object implementing the protocol behind the handle. + // For example in case protocol is implemented by pure Swift object. + // In this cause case ObjCClass might not be resolved (cause ObjCClassNotFoundException) + // or not resolve to one that implement the protocol (not isAssignableFrom). + // To workaround -- allow getFromObject to be optional and return Null. + // in this case objCClass will be resolved using getByType() from provided $ObjCProxy + // it's the case when proper ObjC object that implement the protocol can't be identified + ObjCClass objCClass = ObjCClass.getFromObject(handle, expectedType != cls); + if (objCClass == null || !expectedType.isAssignableFrom(objCClass.getType())) { /* * If the expected return type is incompatible with the type of * the native instance we have to make sure we return an