[marshal] Add EH callback for native-to-managed wrapper
authorLudovic Henry <ludovic@xamarin.com>
Mon, 25 Apr 2016 13:42:26 +0000 (09:42 -0400)
committerLudovic Henry <ludovic@xamarin.com>
Wed, 4 May 2016 16:05:47 +0000 (12:05 -0400)
This is going to be used by Xamarin.iOS to convert a managed exception to an Obj-C exception. We need this as if we don't do that, the managed exception mechanism will unwind native stack, which can lead to undefined behavior. By converting to an Obj-C exception, we let the native exception mechanism do what it's supposed to do.

mono/metadata/marshal.c
mono/metadata/marshal.h

index 65478587aa0133b2f44b62f0f8db6bff6e70875c..4a3a5e78a50c362ba662db929b44813a818ed57c 100644 (file)
@@ -79,6 +79,8 @@ static MonoNativeTlsKey load_type_info_tls_id;
 
 static gboolean use_aot_wrappers;
 
+static MonoFtnPtrEHCallback ftnptr_eh_callback;
+
 static void
 delegate_hash_table_add (MonoDelegate *d);
 
@@ -192,6 +194,12 @@ mono_array_to_lparray (MonoArray *array);
 void
 mono_free_lparray (MonoArray *array, gpointer* nativeArray);
 
+static gint32
+mono_marshal_has_ftnptr_eh_callback (void);
+
+static void
+mono_marshal_ftnptr_eh_callback (guint32 gchandle);
+
 /* Lazy class loading functions */
 static GENERATE_GET_CLASS_WITH_CACHE (string_builder, System.Text, StringBuilder)
 static GENERATE_GET_CLASS_WITH_CACHE (date_time, System, DateTime)
@@ -303,7 +311,10 @@ mono_marshal_init (void)
                register_icall (mono_context_set, "mono_context_set", "void object", FALSE);
                register_icall (mono_gc_wbarrier_generic_nostore, "wb_generic", "void ptr", FALSE);
                register_icall (mono_gchandle_get_target, "mono_gchandle_get_target", "object int32", TRUE);
+               register_icall (mono_gchandle_new, "mono_gchandle_new", "uint32 object bool", TRUE);
                register_icall (mono_marshal_isinst_with_cache, "mono_marshal_isinst_with_cache", "object object ptr ptr", FALSE);
+               register_icall (mono_marshal_has_ftnptr_eh_callback, "mono_marshal_has_ftnptr_eh_callback", "int32", TRUE);
+               register_icall (mono_marshal_ftnptr_eh_callback, "mono_marshal_ftnptr_eh_callback", "void uint32", TRUE);
 
                mono_cominterop_init ();
                mono_remoting_init ();
@@ -7957,9 +7968,9 @@ mono_marshal_emit_managed_wrapper (MonoMethodBuilder *mb, MonoMethodSignature *i
        }
 #else
        MonoMethodSignature *sig, *csig;
-       MonoExceptionClause *clause;
-       int i, *tmp_locals;
-       int leave_pos;
+       MonoExceptionClause *clauses, *clause_finally, *clause_catch;
+       int i, *tmp_locals, ex_local, e_local;
+       int leave_try_pos, leave_catch_pos, ex_m1_pos, rethrow_pos;
        gboolean closed = FALSE;
 
        sig = m->sig;
@@ -7990,28 +8001,48 @@ mono_marshal_emit_managed_wrapper (MonoMethodBuilder *mb, MonoMethodSignature *i
        if (MONO_TYPE_ISSTRUCT (sig->ret))
                m->vtaddr_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
 
+       ex_local = mono_mb_add_local (mb, &mono_defaults.uint32_class->byval_arg);
+       e_local = mono_mb_add_local (mb, &mono_defaults.exception_class->byval_arg);
+
        /*
+        * guint32 ex = -1;
         * try {
         *   mono_jit_attach ();
         *
         *   <interrupt check>
         *
         *   ret = method (...);
+        * } catch (Exception e) {
+        *   if (!mono_marshal_has_ftnptr_eh_callback ())
+        *     throw e;
+        *   ex = mono_gchandle_new (e, false);
         * } finally {
         *   mono_jit_detach ();
         * }
+        * if (ex != -1)
+        *   mono_marshal_ftnptr_eh_callback (ex);
         *
         * return ret;
         */
 
-       clause = g_new0 (MonoExceptionClause, 1);
-       clause->flags = MONO_EXCEPTION_CLAUSE_FINALLY;
+       clauses = g_new0 (MonoExceptionClause, 2);
+
+       clause_catch = &clauses [0];
+       clause_catch->flags = MONO_EXCEPTION_CLAUSE_NONE;
+       clause_catch->data.catch_class = mono_defaults.exception_class;
+
+       clause_finally = &clauses [1];
+       clause_finally->flags = MONO_EXCEPTION_CLAUSE_FINALLY;
 
        mono_mb_emit_icon (mb, 0);
        mono_mb_emit_stloc (mb, 2);
 
+       mono_mb_emit_icon (mb, -1);
+       mono_mb_emit_byte (mb, CEE_CONV_U4);
+       mono_mb_emit_stloc (mb, ex_local);
+
        /* try { */
-       clause->try_offset = mono_mb_get_label (mb);
+       clause_catch->try_offset = clause_finally->try_offset = mono_mb_get_label (mb);
 
        /*
         * Might need to attach the thread to the JIT or change the
@@ -8165,11 +8196,39 @@ mono_marshal_emit_managed_wrapper (MonoMethodBuilder *mb, MonoMethodSignature *i
                }
        }
 
-       leave_pos = mono_mb_emit_branch (mb, CEE_LEAVE);
+       leave_try_pos = mono_mb_emit_branch (mb, CEE_LEAVE);
+
+       /* } catch (Exception e) { */
+       clause_catch->try_len = mono_mb_get_label (mb) - clause_catch->try_offset;
+       clause_catch->handler_offset = mono_mb_get_label (mb);
+
+       mono_mb_emit_stloc (mb, e_local);
+
+       /* if (!mono_marshal_has_ftnptr_eh_callback ()) { */
+       mono_mb_emit_icall (mb, mono_marshal_has_ftnptr_eh_callback);
+       rethrow_pos = mono_mb_emit_branch (mb, CEE_BRTRUE);
+
+       /* throw e; */
+       mono_mb_emit_ldloc (mb, e_local);
+       mono_mb_emit_byte (mb, CEE_THROW);
+
+       /* } [endif] */
+       mono_mb_patch_branch (mb, rethrow_pos);
+
+       /* ex = mono_gchandle_new (e, false); */
+       mono_mb_emit_ldloc (mb, e_local);
+       mono_mb_emit_icon (mb, 0);
+       mono_mb_emit_icall (mb, mono_gchandle_new);
+       mono_mb_emit_stloc (mb, ex_local);
+
+       leave_catch_pos = mono_mb_emit_branch (mb, CEE_LEAVE);
+
+       /* } [endcatch] */
+       clause_catch->handler_len = mono_mb_get_pos (mb) - clause_catch->handler_offset;
 
        /* } finally { */
-       clause->try_len = mono_mb_get_label (mb) - clause->try_offset;
-       clause->handler_offset = mono_mb_get_label (mb);
+       clause_finally->try_len = mono_mb_get_label (mb) - clause_finally->try_offset;
+       clause_finally->handler_offset = mono_mb_get_label (mb);
 
        /*
         * Also does the RUNNING -> (BLOCKING|RUNNING) thread state transition
@@ -8182,9 +8241,23 @@ mono_marshal_emit_managed_wrapper (MonoMethodBuilder *mb, MonoMethodSignature *i
        mono_mb_emit_byte (mb, CEE_ENDFINALLY);
 
        /* } [endfinally] */
-       clause->handler_len = mono_mb_get_pos (mb) - clause->handler_offset;
+       clause_finally->handler_len = mono_mb_get_pos (mb) - clause_finally->handler_offset;
+
+       mono_mb_patch_branch (mb, leave_try_pos);
+       mono_mb_patch_branch (mb, leave_catch_pos);
+
+       /* if (ex != -1) */
+       mono_mb_emit_ldloc (mb, ex_local);
+       mono_mb_emit_icon (mb, -1);
+       mono_mb_emit_byte (mb, CEE_CONV_U4);
+       ex_m1_pos = mono_mb_emit_branch (mb, CEE_BEQ);
+
+       /* mono_marshal_ftnptr_eh_callback (ex) */
+       mono_mb_emit_ldloc (mb, ex_local);
+       mono_mb_emit_icall (mb, mono_marshal_ftnptr_eh_callback);
 
-       mono_mb_patch_branch (mb, leave_pos);
+       /* [ex == -1] */
+       mono_mb_patch_branch (mb, ex_m1_pos);
 
        /* return ret; */
        if (m->retobj_var) {
@@ -8198,7 +8271,7 @@ mono_marshal_emit_managed_wrapper (MonoMethodBuilder *mb, MonoMethodSignature *i
                mono_mb_emit_byte (mb, CEE_RET);
        }
 
-       mono_mb_set_clauses (mb, 1, clause);
+       mono_mb_set_clauses (mb, 2, clauses);
 
        if (closed)
                g_free (sig);
@@ -11807,3 +11880,31 @@ mono_marshal_free_dynamic_wrappers (MonoMethod *method)
        if (marshal_mutex_initialized)
                mono_marshal_unlock ();
 }
+
+static gint32
+mono_marshal_has_ftnptr_eh_callback (void)
+{
+       return ftnptr_eh_callback != NULL;
+}
+
+static void
+mono_marshal_ftnptr_eh_callback (guint32 gchandle)
+{
+       g_assert (ftnptr_eh_callback);
+       ftnptr_eh_callback (gchandle);
+}
+
+/*
+ * mono_install_ftnptr_eh_callback:
+ *
+ *   Install a callback that should be called when there is a managed exception
+ *   in a native-to-managed wrapper. This is mainly used by iOS to convert a
+ *   managed exception to a native exception, to properly unwind the native
+ *   stack; this native exception will then be converted back to a managed
+ *   exception in their managed-to-native wrapper.
+ */
+void
+mono_install_ftnptr_eh_callback (MonoFtnPtrEHCallback callback)
+{
+       ftnptr_eh_callback = callback;
+}
index 818aea36d2ae5485c3f6332b0d0fd8aa27be77a6..e3efe674fb90dcc4abf21fc316673c030b8ee7d7 100644 (file)
@@ -610,6 +610,11 @@ mono_mb_create_and_cache_full (GHashTable *cache, gpointer key,
                                                           MonoMethodBuilder *mb, MonoMethodSignature *sig,
                                                           int max_stack, WrapperInfo *info, gboolean *out_found);
 
+typedef void (*MonoFtnPtrEHCallback) (guint32 gchandle);
+
+MONO_API void
+mono_install_ftnptr_eh_callback (MonoFtnPtrEHCallback callback);
+
 G_END_DECLS
 
 #endif /* __MONO_MARSHAL_H__ */