Skip to content

Commit

Permalink
* fixed: ObjCClassNotFoundException when marshaling protocols impleme…
Browse files Browse the repository at this point in the history
…nted 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
  • Loading branch information
dkimitsa authored May 7, 2024
1 parent 1206cb6 commit 1d11deb
Show file tree
Hide file tree
Showing 2 changed files with 21 additions and 5 deletions.
14 changes: 11 additions & 3 deletions compiler/objc/src/main/java/org/robovm/objc/ObjCClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<? extends ObjCObject> type) {
Expand Down Expand Up @@ -328,6 +332,10 @@ private static List<String> 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) {
Expand Down Expand Up @@ -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);
}
Expand Down
12 changes: 10 additions & 2 deletions compiler/objc/src/main/java/org/robovm/objc/ObjCObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -328,8 +328,16 @@ public static <T extends ObjCObject> T toObjCObject(Class<T> 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
Expand Down

0 comments on commit 1d11deb

Please sign in to comment.