Skip to content

Commit

Permalink
[registrar] Improve error message when failing to create a managed wr…
Browse files Browse the repository at this point in the history
…apper for a native handle. (#4360)

* Convert it into a MM8027/MT8027 error (with documentation).
* Add information about the selector and managed method that triggered the error.

Now the problem reported in issue #4254 shows up as:

> ObjCRuntime.RuntimeException: Failed to marshal the Objective-C object 0x7f8080412810 (type: UIView). Could not find an existing managed instance for this object, nor was it possible to create a new managed instance (because the type 'UIKit.UIView&' does not have a constructor that takes one IntPtr argument).
> Additional information:
> 	Selector: popoverController:willRepositionPopoverToRect:inView:
> 	Method: UIKit.UIPopoverController+_UIPopoverControllerDelegate.WillReposition(UIKit.UIPopoverController, CoreGraphics.CGRect ByRef, UIKit.UIView ByRef)

instead of just:

> ObjCRuntime.RuntimeException: Failed to marshal the Objective-C object 0x7f8080412810 (type: UIView). Could not find an existing managed instance for this object, nor was it possible to create a new managed instance (because the type 'UIKit.UIView&' does not have a constructor that takes one IntPtr argument).

which makes it much easier to understand, track down, and fix/work around,
both for customers and ourselves.
  • Loading branch information
rolfbjarne committed Jun 29, 2018
1 parent d79bef7 commit e110612
Show file tree
Hide file tree
Showing 13 changed files with 177 additions and 40 deletions.
29 changes: 29 additions & 0 deletions docs/website/mmp-errors.md
Expand Up @@ -477,3 +477,32 @@ This usually indicates a bug in Xamarin.Mac, because the dynamic registrar shoul
It's possible to force the linker to keep the dynamic registrar by adding
`--optimize=-remove-dynamic-registrar` to the additional mmp arguments in
the project's Mac Build options.

### <a name="MT8027"/>MT8027: Failed to marshal the Objective-C object {handle} (type: {managed_type}). Could not find an existing managed instance for this object, nor was it possible to create a new managed instance.

This occurs when the Xamarin.Mac runtime finds an Objective-C object without a
corresponding managed wrapper object, and when trying to create that managed
wrapper, it turns out it's not possible.

There are a few reasons this may happen:

* If it occurs when deserializing a storyboard/nib/xib and mentioning a
managed type that's used in that storyboard/nib/xib, then the fix is to add
a constructor that takes a single `IntPtr` argument to that managed type.
This constructor should not have any logic (because it's invoked before the
native and managed instances are fully created).

* A managed wrapper existed at some point, but was collected by the GC. If the
native object is still alive, and later resurfaces to managed code, the
Xamarin.Mac runtime will try to re-create a managed wrapper instance. In
most cases the problem here is that the managed wrapper shouldn't have been
collected by the GC in the first place.

Possible causes include:

* Manually calling Dispose too early on the managed wrapper.
* Incorrect bindings for third-party libraries.
* Reference-counting bugs in third-party libraries.

* It could be a bug in Xamarin.Mac. If this is the case, please file a bug at
[https://bugzilla.xamarin.com](https://bugzilla.xamarin.com/enter_bug.cgi?product=Xamarin.Mac).
29 changes: 29 additions & 0 deletions docs/website/mtouch-errors.md
Expand Up @@ -2482,3 +2482,32 @@ This usually indicates a bug in Xamarin.iOS, because the dynamic registrar shoul
It's possible to force the linker to keep the dynamic registrar by adding
`--optimize=-remove-dynamic-registrar` to the additional mtouch arguments in
the project's iOS Build options.

### <a name="MM8027"/>MM8027: Failed to marshal the Objective-C object {handle} (type: {managed_type}). Could not find an existing managed instance for this object, nor was it possible to create a new managed instance.

This occurs when the Xamarin.iOS runtime finds an Objective-C object without a
corresponding managed wrapper object, and when trying to create that managed
wrapper, it turns out it's not possible.

There are a few reasons this may happen:

* If it occurs when deserializing a storyboard/nib/xib and mentioning a
managed type that's used in that storyboard/nib/xib, then the fix is to add
a constructor that takes a single `IntPtr` argument to that managed type.
This constructor should not have any logic (because it's invoked before the
native and managed instances are fully created).

* A managed wrapper existed at some point, but was collected by the GC. If the
native object is still alive, and later resurfaces to managed code, the
Xamarin.iOS runtime will try to re-create a managed wrapper instance. In
most cases the problem here is that the managed wrapper shouldn't have been
collected by the GC in the first place.

Possible causes include:

* Manually calling Dispose too early on the managed wrapper.
* Incorrect bindings for third-party libraries.
* Reference-counting bugs in third-party libraries.

* It could be a bug in Xamarin.iOS. If this is the case, please file a bug at
[https://bugzilla.xamarin.com](https://bugzilla.xamarin.com/enter_bug.cgi?product=iOS).
12 changes: 11 additions & 1 deletion runtime/delegates.t4
Expand Up @@ -179,7 +179,9 @@
new XDelegate ("MonoObject *", "IntPtr", "xamarin_get_nsobject_with_type",
"id", "IntPtr", "obj",
"void *", "IntPtr", "type",
"int32_t *", "out bool", "created"
"int32_t *", "out bool", "created",
"SEL", "IntPtr", "selector",
"MonoReflectionMethod *", "IntPtr", "method"
) {
WrappedManagedFunction = "GetNSObjectWithType",
OnlyDynamicUsage = false,
Expand Down Expand Up @@ -271,6 +273,14 @@
WrappedManagedFunction = "ConvertNSStringToSmartEnum",
OnlyDynamicUsage = true,
},

new XDelegate ("int32_t", "int", "xamarin_create_runtime_exception",
"int32_t", "int", "code",
"const char *", "IntPtr", "message"
) {
WrappedManagedFunction = "CreateRuntimeException",
OnlyDynamicUsage = false,
},
};
delegates.CalculateLengths ();
#><#+
Expand Down
13 changes: 7 additions & 6 deletions runtime/runtime.m
Expand Up @@ -257,17 +257,17 @@
}

MonoObject *
xamarin_get_nsobject_with_type_for_ptr (id self, bool owns, MonoType* type, guint32 *exception_gchandle)
xamarin_get_nsobject_with_type_for_ptr (id self, bool owns, MonoType* type, SEL selector, MonoMethod *managed_method, guint32 *exception_gchandle)
{
// COOP: Reading managed data, must be in UNSAFE mode
MONO_ASSERT_GC_UNSAFE;

int32_t created;
return xamarin_get_nsobject_with_type_for_ptr_created (self, owns, type, &created, exception_gchandle);
return xamarin_get_nsobject_with_type_for_ptr_created (self, owns, type, &created, selector, managed_method, exception_gchandle);
}

MonoObject *
xamarin_get_nsobject_with_type_for_ptr_created (id self, bool owns, MonoType *type, int32_t *created, guint32 *exception_gchandle)
xamarin_get_nsobject_with_type_for_ptr_created (id self, bool owns, MonoType *type, int32_t *created, SEL selector, MonoMethod *managed_method, guint32 *exception_gchandle)
{
// COOP: Reading managed data, must be in UNSAFE mode
MONO_ASSERT_GC_UNSAFE;
Expand All @@ -288,7 +288,7 @@
return mobj;
}

return xamarin_get_nsobject_with_type (self, mono_type_get_object (mono_domain_get (), type), created, exception_gchandle);
return xamarin_get_nsobject_with_type (self, mono_type_get_object (mono_domain_get (), type), created, selector, managed_method == NULL ? NULL : mono_method_get_object (mono_domain_get (), managed_method, NULL), exception_gchandle);
}

MonoObject *
Expand Down Expand Up @@ -672,9 +672,10 @@ -(void) xamarinSetGCHandle: (int) gc_handle;
char *type_name = xamarin_lookup_managed_type_name ([self class], exception_gchandle);
if (*exception_gchandle == 0) {
char *msg = xamarin_strdup_printf (m, self, object_getClassName (self), type_name, sel_getName (sel), method_full_name);
MonoException *mex = xamarin_create_exception (msg);
guint32 ex_handle = xamarin_create_runtime_exception (8027, msg, exception_gchandle);
xamarin_free (msg);
*exception_gchandle = mono_gchandle_new ((MonoObject *) mex, FALSE);
if (*exception_gchandle == 0)
*exception_gchandle = ex_handle;
}
mono_free (type_name);
mono_free (method_full_name);
Expand Down
10 changes: 5 additions & 5 deletions runtime/trampolines-invoke.m
Expand Up @@ -118,7 +118,7 @@
if (isCategoryInstance) {
// we know this must be an id
p = mono_signature_get_params (msig, &iter);
arg_ptrs [0] = xamarin_get_nsobject_with_type_for_ptr (self, false, p, &exception_gchandle);
arg_ptrs [0] = xamarin_get_nsobject_with_type_for_ptr (self, false, p, sel, method, &exception_gchandle);
if (exception_gchandle != 0)
goto exception_handling;
mofs = 1;
Expand Down Expand Up @@ -195,7 +195,7 @@
MonoObject *obj;
NSObject *targ = *(NSObject **) arg;

obj = xamarin_get_nsobject_with_type_for_ptr (targ, false, p, &exception_gchandle);
obj = xamarin_get_nsobject_with_type_for_ptr (targ, false, p, sel, method, &exception_gchandle);
if (exception_gchandle != 0)
goto exception_handling;
#if DEBUG
Expand Down Expand Up @@ -324,7 +324,7 @@
} else {
MonoObject *obj;
id targ = [arr objectAtIndex: j];
obj = xamarin_get_nsobject_with_type_for_ptr (targ, false, e, &exception_gchandle);
obj = xamarin_get_nsobject_with_type_for_ptr (targ, false, e, sel, method, &exception_gchandle);
if (exception_gchandle != 0)
goto exception_handling;
#if DEBUG
Expand All @@ -343,7 +343,7 @@
}
MonoObject *obj;
int32_t created = false;
obj = xamarin_get_nsobject_with_type_for_ptr_created (id_arg, false, p, &created, &exception_gchandle);
obj = xamarin_get_nsobject_with_type_for_ptr_created (id_arg, false, p, &created, sel, method, &exception_gchandle);
if (exception_gchandle != 0)
goto exception_handling;

Expand Down Expand Up @@ -380,7 +380,7 @@
[id_arg autorelease];
}
MonoObject *obj;
obj = xamarin_get_nsobject_with_type_for_ptr (id_arg, false, p, &exception_gchandle);
obj = xamarin_get_nsobject_with_type_for_ptr (id_arg, false, p, sel, method, &exception_gchandle);
if (exception_gchandle != 0)
goto exception_handling;
#if DEBUG
Expand Down
2 changes: 1 addition & 1 deletion runtime/trampolines.m
Expand Up @@ -1122,7 +1122,7 @@
managed_method = xamarin_get_managed_method_for_token (context /* token ref */, exception_gchandle);
if (*exception_gchandle != 0) return NULL;

arg_ptrs [0] = xamarin_get_nsobject_with_type_for_ptr (value, false, xamarin_get_parameter_type (managed_method, 0), exception_gchandle);
arg_ptrs [0] = xamarin_get_nsobject_with_type_for_ptr (value, false, xamarin_get_parameter_type (managed_method, 0), NULL, NULL, exception_gchandle);
if (*exception_gchandle != 0) return NULL;

obj = mono_runtime_invoke (managed_method, NULL, arg_ptrs, &exception);
Expand Down
4 changes: 2 additions & 2 deletions runtime/xamarin/runtime.h
Expand Up @@ -173,8 +173,8 @@ bool xamarin_is_class_nsstring (MonoClass *cls);
bool xamarin_is_class_nullable (MonoClass *cls, MonoClass **element_type, guint32 *exception_gchandle);
MonoClass * xamarin_get_nullable_type (MonoClass *cls, guint32 *exception_gchandle);
MonoType * xamarin_get_parameter_type (MonoMethod *managed_method, int index);
MonoObject * xamarin_get_nsobject_with_type_for_ptr (id self, bool owns, MonoType* type, guint32 *exception_gchandle);
MonoObject * xamarin_get_nsobject_with_type_for_ptr_created (id self, bool owns, MonoType *type, int32_t *created, guint32 *exception_gchandle);
MonoObject * xamarin_get_nsobject_with_type_for_ptr (id self, bool owns, MonoType* type, SEL selector, MonoMethod *managed_method, guint32 *exception_gchandle);
MonoObject * xamarin_get_nsobject_with_type_for_ptr_created (id self, bool owns, MonoType *type, int32_t *created, SEL selector, MonoMethod *managed_method, guint32 *exception_gchandle);
int * xamarin_get_delegate_for_block_parameter (MonoMethod *method, guint32 token_ref, int par, void *nativeBlock, guint32 *exception_gchandle);
id xamarin_get_block_for_delegate (MonoMethod *method, MonoObject *delegate, const char *signature /* NULL allowed, but requires the dynamic registrar at runtime to compute */, guint32 *exception_gchandle);
id xamarin_get_nsobject_handle (MonoObject *obj);
Expand Down
2 changes: 1 addition & 1 deletion src/ObjCRuntime/DynamicRegistrar.cs
Expand Up @@ -906,7 +906,7 @@ public void GetMethodDescriptionAndObject (Type type, IntPtr selector, bool is_s
if (res.IsInstanceCategory) {
mthis = IntPtr.Zero;
} else {
var nsobj = Runtime.GetNSObject (obj, Runtime.MissingCtorResolution.ThrowConstructor1NotFound, true);
var nsobj = Runtime.GetNSObject (obj, Runtime.MissingCtorResolution.ThrowConstructor1NotFound, true, selector, ObjectWrapper.Convert (res.Method));
mthis = ObjectWrapper.Convert (nsobj);
if (res.Method.ContainsGenericParameters) {
res.WriteUnmanagedDescription (desc, Runtime.FindClosedMethod (nsobj.GetType (), res.Method));
Expand Down
43 changes: 31 additions & 12 deletions src/ObjCRuntime/Runtime.cs
Expand Up @@ -416,6 +416,12 @@ static int CreateNSException (IntPtr ns_exception)
return GCHandle.ToIntPtr (GCHandle.Alloc (ex)).ToInt32 ();
}

static int CreateRuntimeException (int code, IntPtr message)
{
var ex = ErrorHelper.CreateError (code, Marshal.PtrToStringAuto (message));
return GCHandle.ToIntPtr (GCHandle.Alloc (ex)).ToInt32 ();
}

static IntPtr UnwrapNSException (int exc_handle)
{
var obj = GCHandle.FromIntPtr (new IntPtr (exc_handle)).Target;
Expand Down Expand Up @@ -698,11 +704,11 @@ static IntPtr GetINativeObject_Static (IntPtr ptr, bool owns, string typename, s
return ObjectWrapper.Convert (GetINativeObject (ptr, owns, iface, type));
}

static IntPtr GetNSObjectWithType (IntPtr ptr, IntPtr type_ptr, out bool created)
static IntPtr GetNSObjectWithType (IntPtr ptr, IntPtr type_ptr, out bool created, IntPtr selector, IntPtr method)
{
// It doesn't work to use System.Type in the signature, we get garbage.
var type = (System.Type) ObjectWrapper.Convert (type_ptr);
return ObjectWrapper.Convert (GetNSObject (ptr, type, MissingCtorResolution.ThrowConstructor1NotFound, true, out created));
return ObjectWrapper.Convert (GetNSObject (ptr, type, MissingCtorResolution.ThrowConstructor1NotFound, true, out created, selector, method));
}

static void Dispose (IntPtr mobj)
Expand Down Expand Up @@ -1016,7 +1022,7 @@ internal enum MissingCtorResolution {
Ignore,
}

static void MissingCtor (IntPtr ptr, IntPtr klass, Type type, MissingCtorResolution resolution)
static void MissingCtor (IntPtr ptr, IntPtr klass, Type type, MissingCtorResolution resolution, IntPtr selector = default (IntPtr), IntPtr method = default (IntPtr))
{
string msg;

Expand All @@ -1041,15 +1047,26 @@ static void MissingCtor (IntPtr ptr, IntPtr klass, Type type, MissingCtorResolut
return;
}

throw new Exception (string.Format (msg, ptr.ToString ("x"), new Class (klass).Name, type.FullName));
if (selector != IntPtr.Zero || method != IntPtr.Zero)
msg += "\nAdditional information:\n";

if (selector != IntPtr.Zero)
msg += $"\tSelector: {Selector.GetName (selector)}\n";
if (method != IntPtr.Zero) {
var mi = ObjectWrapper.Convert (method) as MethodBase;
if (mi != null)
msg += $"\tMethod: {mi.FullName}\n";
}

throw ErrorHelper.CreateError (8027, string.Format (msg, ptr.ToString ("x"), new Class (klass).Name, type.FullName));
}

static NSObject ConstructNSObject (IntPtr ptr, IntPtr klass, MissingCtorResolution missingCtorResolution)
static NSObject ConstructNSObject (IntPtr ptr, IntPtr klass, MissingCtorResolution missingCtorResolution, IntPtr selector = default (IntPtr), IntPtr method = default (IntPtr))
{
Type type = Class.Lookup (klass);

if (type != null) {
return ConstructNSObject<NSObject> (ptr, type, missingCtorResolution);
return ConstructNSObject<NSObject> (ptr, type, missingCtorResolution, selector, method);
} else {
return new NSObject (ptr);
}
Expand All @@ -1061,15 +1078,16 @@ static NSObject ConstructNSObject (IntPtr ptr, IntPtr klass, MissingCtorResoluti
}

// The generic argument T is only used to cast the return value.
static T ConstructNSObject<T> (IntPtr ptr, Type type, MissingCtorResolution missingCtorResolution) where T: class, INativeObject
// The 'selector' and 'method' arguments are only used in error messages.
static T ConstructNSObject<T> (IntPtr ptr, Type type, MissingCtorResolution missingCtorResolution, IntPtr selector = default (IntPtr), IntPtr method = default (IntPtr)) where T: class, INativeObject
{
if (type == null)
throw new ArgumentNullException ("type");

var ctor = GetIntPtrConstructor (type);

if (ctor == null) {
MissingCtor (ptr, IntPtr.Zero, type, missingCtorResolution);
MissingCtor (ptr, IntPtr.Zero, type, missingCtorResolution, selector, method);
return null;
}

Expand Down Expand Up @@ -1158,7 +1176,7 @@ internal static NSObject TryGetNSObject (IntPtr ptr, bool evenInFinalizerQueue =
return GetNSObject (ptr, MissingCtorResolution.ThrowConstructor1NotFound);
}

internal static NSObject GetNSObject (IntPtr ptr, MissingCtorResolution missingCtorResolution, bool evenInFinalizerQueue = false) {
internal static NSObject GetNSObject (IntPtr ptr, MissingCtorResolution missingCtorResolution, bool evenInFinalizerQueue = false, IntPtr selector = default (IntPtr), IntPtr method = default (IntPtr)) {
if (ptr == IntPtr.Zero)
return null;

Expand All @@ -1167,7 +1185,7 @@ internal static NSObject TryGetNSObject (IntPtr ptr, bool evenInFinalizerQueue =
if (o != null)
return o;

return ConstructNSObject (ptr, Class.GetClassForObject (ptr), missingCtorResolution);
return ConstructNSObject (ptr, Class.GetClassForObject (ptr), missingCtorResolution, selector, method);
}

static public T GetNSObject<T> (IntPtr ptr) where T : NSObject
Expand Down Expand Up @@ -1237,7 +1255,8 @@ internal static NSObject TryGetNSObject (IntPtr ptr, bool evenInFinalizerQueue =
// NSObject subclasses (the test case in #13518 should work even with type checks).
//

static NSObject GetNSObject (IntPtr ptr, Type target_type, MissingCtorResolution missingCtorResolution, bool evenInFinalizerQueue, out bool created) {
// The 'selector' and 'method' arguments are only used in error messages.
static NSObject GetNSObject (IntPtr ptr, Type target_type, MissingCtorResolution missingCtorResolution, bool evenInFinalizerQueue, out bool created, IntPtr selector = default (IntPtr), IntPtr method = default (IntPtr)) {
created = false;

if (ptr == IntPtr.Zero)
Expand Down Expand Up @@ -1269,7 +1288,7 @@ internal static NSObject TryGetNSObject (IntPtr ptr, bool evenInFinalizerQueue =
}

created = true;
return ConstructNSObject<NSObject> (ptr, target_type, MissingCtorResolution.ThrowConstructor1NotFound);
return ConstructNSObject<NSObject> (ptr, target_type, MissingCtorResolution.ThrowConstructor1NotFound, selector, method);
}

static Type LookupINativeObjectImplementation (IntPtr ptr, Type target_type, Type implementation = null)
Expand Down
17 changes: 8 additions & 9 deletions tests/api-shared/ObjCRuntime/RegistrarTest.cs
Expand Up @@ -31,31 +31,30 @@ public class RegistrarSharedTest {
}
}

#if __UNIFIED__
[Test]
public void IntPtrCtor ()
{
IntPtr ptr = IntPtr.Zero;
try {
ptr = Messaging.IntPtr_objc_msgSend (Class.GetHandle (typeof (IntPtrCtorTestClass)), Selector.GetHandle ("alloc"));
ptr = Messaging.IntPtr_objc_msgSend (ptr, Selector.GetHandle ("init"));
var ex = Assert.Throws<Exception> (() => Messaging.bool_objc_msgSend_IntPtr (ptr, Selector.GetHandle ("conformsToProtocol:"), IntPtr.Zero));
var ex = Assert.Throws<RuntimeException> (() => Messaging.bool_objc_msgSend_IntPtr (ptr, Selector.GetHandle ("conformsToProtocol:"), IntPtr.Zero));
var msg = string.Format ("Failed to marshal the Objective-C object 0x{0} (type: IntPtrCtorTestClass). Could not find an existing managed instance for this object, nor was it possible to create a new managed instance (because the type 'XamarinTests.ObjCRuntime.RegistrarSharedTest+IntPtrCtorTestClass' does not have a constructor that takes one IntPtr argument).", ptr.ToString ("x"));
msg += "\nAdditional information:\n\tSelector: conformsToProtocol:\n\tMethod: ";
// The difference between the registrars is basically whether this string
// was constructed by native mono API or managed API.
if (CurrentRegistrar == Registrars.Static) {
msg += "\nAdditional information:\n\tSelector: conformsToProtocol:\n\tMethod: ";
#if !XAMCORE_2_0
#if __IOS__
msg += "MonoTouch.";
#else
msg += "MonoMac.";
#endif
#endif
msg += "Foundation.NSObject:InvokeConformsToProtocol (intptr)\n";
} else {
msg += "Foundation.NSObject.InvokeConformsToProtocol(IntPtr)\n";
}
Assert.AreEqual (msg, ex.Message, "#message");
} finally {
Messaging.void_objc_msgSend (ptr, Selector.GetHandle ("release"));
}
}
#endif

[Register ("IntPtrCtorTestClass")]
class IntPtrCtorTestClass : NSObject {
Expand Down
2 changes: 1 addition & 1 deletion tests/monotouch-test/ObjCRuntime/RegistrarTest.cs
Expand Up @@ -2087,7 +2087,7 @@ public void TestCtors ()
ptr = Messaging.IntPtr_objc_msgSend (Class.GetHandle (typeof (D2)), Selector.GetHandle ("alloc"));
ptr = Messaging.IntPtr_objc_msgSend (ptr, Selector.GetHandle ("init"));
// Failed to marshal the Objective-C object 0x7adf5920 (type: AppDelegate_D2). Could not find an existing managed instance for this object, nor was it possible to create a new managed instance (because the type 'AppDelegate+D2' does not have a constructor that takes one IntPtr argument).
Assert.Throws<Exception> (() => Runtime.GetNSObject<D2> (ptr), "c");
Assert.Throws<RuntimeException> (() => Runtime.GetNSObject<D2> (ptr), "c");
} finally {
Messaging.void_objc_msgSend (ptr, Selector.GetHandle ("release"));
}
Expand Down

1 comment on commit e110612

@xamarin-release-manager
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥 Jenkins job (on internal Jenkins) failed in stage 'Test run' 🔥 : hudson.AbortException: script returned exit code 2

Build succeeded
API Diff (from stable)
ℹ️ API Diff (from PR only) (please review changes)
Generator Diff (only version changes)
🔥 Test run failed 🔥

Test results

1 tests failed, 0 tests skipped, 224 tests passed.

Failed tests

  • mscorlib/iOS Unified 32-bits - simulator/Debug: Failed

Please sign in to comment.