From: Vlad Brezae Date: Tue, 9 May 2017 21:54:47 +0000 (+0300) Subject: [remoting] Serialize exceptions between domains inside try/catch X-Git-Url: http://wien.tomnetworks.com/gitweb/?p=mono.git;a=commitdiff_plain;h=df047ec2db46f2af653693c0842b08b8a3c87f05 [remoting] Serialize exceptions between domains inside try/catch If a remote invoke in another domain throws an exception, the xdomain-dispatch wrapper will serialize the exception and pass it over to the calling domain so that it can be rethrown there. Serializing the exception object can itself throw an exception which we didn't properly catch, leading to unwinding to the caller domain without changing the domain state back and wreaking havoc. If the serialization of the original exception throws a new exception, this new exception takes its place, being passed instead to the caller domain. --- diff --git a/mono/metadata/remoting.c b/mono/metadata/remoting.c index 9578b47e5d5..ea2ff0c3d7b 100644 --- a/mono/metadata/remoting.c +++ b/mono/metadata/remoting.c @@ -671,8 +671,8 @@ mono_marshal_get_xappdomain_dispatch (MonoMethod *method, int *marshal_types, in int i, j, param_index, copy_locals_base; MonoClass *ret_class = NULL; int loc_array=0, loc_return=0, loc_serialized_exc=0; - MonoExceptionClause *main_clause; - int pos, pos_leave; + MonoExceptionClause *clauses, *main_clause, *serialization_clause; + int pos, pos_leave, pos_leave_serialization; gboolean copy_return; WrapperInfo *info; @@ -714,7 +714,8 @@ mono_marshal_get_xappdomain_dispatch (MonoMethod *method, int *marshal_types, in /* try */ - main_clause = (MonoExceptionClause *)mono_image_alloc0 (method->klass->image, sizeof (MonoExceptionClause)); + clauses = (MonoExceptionClause *)mono_image_alloc0 (method->klass->image, 2 * sizeof (MonoExceptionClause)); + main_clause = &clauses [0]; main_clause->try_offset = mono_mb_get_label (mb); /* Clean the call context */ @@ -894,15 +895,48 @@ mono_marshal_get_xappdomain_dispatch (MonoMethod *method, int *marshal_types, in /* handler code */ main_clause->handler_offset = mono_mb_get_label (mb); + + /* + * We deserialize the exception in another try-catch so we can catch + * serialization failure exceptions. + */ + serialization_clause = &clauses [1]; + serialization_clause->try_offset = mono_mb_get_label (mb); + + mono_mb_emit_managed_call (mb, method_rs_serialize_exc, NULL); + mono_mb_emit_stloc (mb, loc_serialized_exc); + mono_mb_emit_ldarg (mb, 2); + mono_mb_emit_ldloc (mb, loc_serialized_exc); + mono_mb_emit_byte (mb, CEE_STIND_REF); + pos_leave_serialization = mono_mb_emit_branch (mb, CEE_LEAVE); + + /* Serialization exception catch */ + serialization_clause->flags = MONO_EXCEPTION_CLAUSE_NONE; + serialization_clause->try_len = mono_mb_get_pos (mb) - serialization_clause->try_offset; + serialization_clause->data.catch_class = mono_defaults.object_class; + + /* handler code */ + serialization_clause->handler_offset = mono_mb_get_label (mb); + + /* + * If the serialization of the original exception failed we serialize the newly + * thrown exception, which should always succeed, passing it over to the calling + * domain. + */ mono_mb_emit_managed_call (mb, method_rs_serialize_exc, NULL); mono_mb_emit_stloc (mb, loc_serialized_exc); mono_mb_emit_ldarg (mb, 2); mono_mb_emit_ldloc (mb, loc_serialized_exc); mono_mb_emit_byte (mb, CEE_STIND_REF); mono_mb_emit_branch (mb, CEE_LEAVE); - main_clause->handler_len = mono_mb_get_pos (mb) - main_clause->handler_offset; - /* end catch */ + /* end serialization exception catch */ + serialization_clause->handler_len = mono_mb_get_pos (mb) - serialization_clause->handler_offset; + mono_mb_patch_branch (mb, pos_leave_serialization); + + mono_mb_emit_branch (mb, CEE_LEAVE); + /* end main catch */ + main_clause->handler_len = mono_mb_get_pos (mb) - main_clause->handler_offset; mono_mb_patch_branch (mb, pos_leave); if (copy_return) @@ -910,7 +944,7 @@ mono_marshal_get_xappdomain_dispatch (MonoMethod *method, int *marshal_types, in mono_mb_emit_byte (mb, CEE_RET); - mono_mb_set_clauses (mb, 1, main_clause); + mono_mb_set_clauses (mb, 2, clauses); #endif info = mono_wrapper_info_create (mb, WRAPPER_SUBTYPE_NONE); diff --git a/mono/tests/Makefile.am b/mono/tests/Makefile.am index f8946b03be7..fc1259252f3 100644 --- a/mono/tests/Makefile.am +++ b/mono/tests/Makefile.am @@ -458,6 +458,7 @@ TESTS_CS_SRC= \ appdomain1.cs \ appdomain2.cs \ appdomain-exit.cs \ + appdomain-serialize-exception.cs \ assemblyresolve_event2.2.cs \ appdomain-unload-callback.cs \ appdomain-unload-doesnot-raise-pending-events.cs \ diff --git a/mono/tests/appdomain-serialize-exception.cs b/mono/tests/appdomain-serialize-exception.cs new file mode 100644 index 00000000000..8b0fe47fdbf --- /dev/null +++ b/mono/tests/appdomain-serialize-exception.cs @@ -0,0 +1,45 @@ +using System; +using System.Reflection; +using System.Runtime.Serialization; + +public class UnserializableException : Exception +{ +} + +public class TestOutput : MarshalByRefObject +{ + public void ThrowUnserializable () + { + Console.WriteLine("Throwing Unserializable exception in AppDomain \"{0}\"", AppDomain.CurrentDomain.FriendlyName); + throw new UnserializableException (); + } +} + +public class Example +{ + public static int Main () + { + string original_domain = AppDomain.CurrentDomain.FriendlyName; + + AppDomain ad = AppDomain.CreateDomain("subdomain"); + try { + TestOutput remoteOutput = (TestOutput) ad.CreateInstanceAndUnwrap( + typeof (TestOutput).Assembly.FullName, + "TestOutput"); + remoteOutput.ThrowUnserializable (); + } catch (SerializationException) { + Console.WriteLine ("Caught serialization exception"); + } catch (Exception) { + Console.WriteLine ("Caught other exception"); + Environment.Exit (1); + } finally { + Console.WriteLine ("Finally in domain {0}", AppDomain.CurrentDomain.FriendlyName); + if (original_domain != AppDomain.CurrentDomain.FriendlyName) + Environment.Exit (2); + AppDomain.Unload (ad); + } + + Console.WriteLine ("All OK"); + return 0; + } +}