diff --git a/mono/metadata/object-internals.h b/mono/metadata/object-internals.h index 840a0bb597d8..aa2ef0c62039 100644 --- a/mono/metadata/object-internals.h +++ b/mono/metadata/object-internals.h @@ -641,6 +641,7 @@ typedef struct { void (*mono_raise_exception_with_ctx) (MonoException *ex, MonoContext *ctx); gboolean (*mono_exception_walk_trace) (MonoException *ex, MonoInternalExceptionFrameWalk func, gpointer user_data); gboolean (*mono_install_handler_block_guard) (MonoThreadUnwindState *unwind_state); + void (*mono_uninstall_current_handler_block_guard) (void); gboolean (*mono_current_thread_has_handle_block_guard) (void); gboolean (*mono_above_abort_threshold) (void); void (*mono_clear_abort_threshold) (void); diff --git a/mono/metadata/threads-types.h b/mono/metadata/threads-types.h index 6043c058a2c9..b55aa6479a12 100644 --- a/mono/metadata/threads-types.h +++ b/mono/metadata/threads-types.h @@ -238,7 +238,7 @@ void mono_threads_set_shutting_down (void); gunichar2* mono_thread_get_name (MonoInternalThread *this_obj, guint32 *name_len); MONO_API MonoException* mono_thread_get_undeniable_exception (void); -void mono_thread_self_abort (void); +void ves_icall_thread_finish_async_abort (void); MONO_PROFILER_API void mono_thread_set_name_internal (MonoInternalThread *this_obj, MonoString *name, gboolean permanent, gboolean reset, MonoError *error); diff --git a/mono/metadata/threads.c b/mono/metadata/threads.c index 0c6ac16e7786..094b7fd4bd0c 100644 --- a/mono/metadata/threads.c +++ b/mono/metadata/threads.c @@ -193,6 +193,9 @@ static void self_abort_internal (MonoError *error); static void async_suspend_internal (MonoInternalThread *thread, gboolean interrupt); static void self_suspend_internal (void); +static gboolean +mono_thread_set_interruption_requested_flags (MonoInternalThread *thread, gboolean sync); + static MonoException* mono_thread_execute_interruption (void); static void ref_stack_destroy (gpointer rs); @@ -366,6 +369,30 @@ mono_thread_set_interruption_requested (MonoInternalThread *thread) { //always force when the current thread is doing it to itself. gboolean sync = thread == mono_thread_internal_current (); + /* Normally synchronous interruptions can bypass abort protection. */ + return mono_thread_set_interruption_requested_flags (thread, sync); +} + +/* Returns TRUE if there was a state change and the interruption can be + * processed. This variant defers a self abort when inside an abort protected + * block. Normally this should only be done when a thread has received an + * outside indication that it should abort. (For example when the JIT sets a + * flag in an finally block.) + */ + +static gboolean +mono_thread_set_self_interruption_respect_abort_prot (void) +{ + MonoInternalThread *thread = mono_thread_internal_current (); + /* N.B. Sets the ASYNC_REQUESTED_BIT for current this thread, + * which is unusual. */ + return mono_thread_set_interruption_requested_flags (thread, FALSE); +} + +/* Returns TRUE if there was a state change and the interruption can be processed. */ +static gboolean +mono_thread_set_interruption_requested_flags (MonoInternalThread *thread, gboolean sync) +{ gsize old_state, new_state; do { old_state = thread->thread_state; @@ -4081,12 +4108,48 @@ mono_threads_abort_appdomain_threads (MonoDomain *domain, int timeout) return TRUE; } +/* This is a JIT icall. This icall is called from a finally block when + * mono_install_handler_block_guard called by another thread has flipped the + * finally block's exvar (see mono_find_exvar_for_offset). In that case, if + * the finally is in an abort protected block, we must defer the abort + * exception until we leave the abort protected block. Otherwise we proceed + * with a synchronous self-abort. + */ void -mono_thread_self_abort (void) -{ - ERROR_DECL (error); - self_abort_internal (error); - mono_error_set_pending_exception (error); +ves_icall_thread_finish_async_abort (void) +{ + /* We were called from the handler block and are about to + * leave it. (If we end up postponing the abort because we're + * in an abort protected block, the unwinder won't run and + * won't clear the handler block itself which will confuse the + * unwinder if we're in a try {} catch {} and we throw again. + * ie, this: + * static Constructor () { + * try { + * try { + * } finally { + * icall (); // Thread.Abort landed here, + * // and caused the handler block to be installed + * if (exvar) + * ves_icall_thread_finish_async_abort (); // we're here + * } + * throw E (); + * } catch (E) { + * // unwinder will get confused here and synthesize a self abort + * } + * } + * + * More interestingly, this doesn't only happen with icalls - a JIT + * trampoline is native code that will cause a handler to be installed. + * So the above situation can happen with any code in a "finally" + * clause. + */ + mono_get_eh_callbacks ()->mono_uninstall_current_handler_block_guard (); + /* Just set the async interruption requested bit. Rely on the icall + * wrapper of this icall to process the thread interruption, respecting + * any abort protection blocks in our call stack. + */ + mono_thread_set_self_interruption_respect_abort_prot (); } /* diff --git a/mono/mini/method-to-ir.c b/mono/mini/method-to-ir.c index c7969650759d..d7d7d120a868 100644 --- a/mono/mini/method-to-ir.c +++ b/mono/mini/method-to-ir.c @@ -11522,7 +11522,7 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b NEW_BBLOCK (cfg, dont_throw); MONO_EMIT_NEW_BIALU_IMM (cfg, OP_COMPARE_IMM, -1, abort_exc->dreg, 0); MONO_EMIT_NEW_BRANCH_BLOCK (cfg, OP_PBEQ, dont_throw); - mono_emit_jit_icall (cfg, mono_thread_self_abort, NULL); + mono_emit_jit_icall (cfg, ves_icall_thread_finish_async_abort, NULL); cfg->cbb->clause_holes = tmp; MONO_START_BB (cfg, dont_throw); diff --git a/mono/mini/mini-exceptions.c b/mono/mini/mini-exceptions.c index 556039a79a50..f1ef6f5e2449 100644 --- a/mono/mini/mini-exceptions.c +++ b/mono/mini/mini-exceptions.c @@ -117,6 +117,7 @@ static void mono_raise_exception_with_ctx (MonoException *exc, MonoContext *ctx) static void mono_runtime_walk_stack_with_ctx (MonoJitStackWalk func, MonoContext *start_ctx, MonoUnwindOptions unwind_options, void *user_data); static gboolean mono_current_thread_has_handle_block_guard (void); static gboolean mono_install_handler_block_guard (MonoThreadUnwindState *ctx); +static void mono_uninstall_current_handler_block_guard (void); static gboolean first_managed (MonoStackFrameInfo *frame, MonoContext *ctx, gpointer addr) @@ -232,6 +233,7 @@ mono_exceptions_init (void) cbs.mono_raise_exception_with_ctx = mono_raise_exception_with_ctx; cbs.mono_exception_walk_trace = mono_exception_walk_trace; cbs.mono_install_handler_block_guard = mono_install_handler_block_guard; + cbs.mono_uninstall_current_handler_block_guard = mono_uninstall_current_handler_block_guard; cbs.mono_current_thread_has_handle_block_guard = mono_current_thread_has_handle_block_guard; cbs.mono_clear_abort_threshold = mini_clear_abort_threshold; cbs.mono_above_abort_threshold = mini_above_abort_threshold; @@ -2955,10 +2957,10 @@ install_handler_block_guard (MonoJitInfo *ji, MonoContext *ctx) break; } - /*no matching finally */ - if (i == ji->num_clauses) - return; + /*no matching finally - can't happen, we parallel the logic in find_last_handler_block. */ + g_assert (i < ji->num_clauses); + g_message (" installed handler block guard at %p\n", ip); /*Load the spvar*/ bp = (guint8*)MONO_CONTEXT_GET_BP (ctx); *(bp + clause->exvar_offset) = 1; @@ -2996,6 +2998,15 @@ mono_install_handler_block_guard (MonoThreadUnwindState *ctx) return TRUE; } +static void +mono_uninstall_current_handler_block_guard (void) +{ + MonoJitTlsData *jit_tls = (MonoJitTlsData *)mono_tls_get_jit_tls (); + if (jit_tls) + jit_tls->handler_block = NULL; +} + + static gboolean mono_current_thread_has_handle_block_guard (void) { diff --git a/mono/mini/mini-runtime.c b/mono/mini/mini-runtime.c index 8971ba306510..a51b8d638d8f 100644 --- a/mono/mini/mini-runtime.c +++ b/mono/mini/mini-runtime.c @@ -4206,7 +4206,7 @@ register_icalls (void) register_dyn_icall (mono_get_rethrow_exception (), "mono_arch_rethrow_exception", "void object", TRUE); register_dyn_icall (mono_get_throw_corlib_exception (), "mono_arch_throw_corlib_exception", "void ptr", TRUE); register_icall (mono_thread_get_undeniable_exception, "mono_thread_get_undeniable_exception", "object", FALSE); - register_icall (mono_thread_self_abort, "mono_thread_self_abort", "void", FALSE); + register_icall (ves_icall_thread_finish_async_abort, "ves_icall_thread_finish_async_abort", "void", FALSE); register_icall (mono_thread_interruption_checkpoint, "mono_thread_interruption_checkpoint", "object", FALSE); register_icall (mono_thread_force_interruption_checkpoint_noraise, "mono_thread_force_interruption_checkpoint_noraise", "object", FALSE); diff --git a/mono/tests/Makefile.am b/mono/tests/Makefile.am index 9c8cfac87a83..8c1d3dccdbf0 100755 --- a/mono/tests/Makefile.am +++ b/mono/tests/Makefile.am @@ -970,7 +970,6 @@ CI_PR_DISABLED_TESTS = \ if ENABLE_COOP CI_PR_DISABLED_TESTS += \ - abort-cctor.exe \ bug-58782-plain-throw.exe \ bug-58782-capture-and-throw.exe endif diff --git a/mono/tests/abort-cctor.cs b/mono/tests/abort-cctor.cs index 7ff7a3b28e51..d4551e034938 100644 --- a/mono/tests/abort-cctor.cs +++ b/mono/tests/abort-cctor.cs @@ -5,6 +5,7 @@ class Driver { + public static SemaphoreSlim sema1 = new SemaphoreSlim (0); public static ManualResetEvent mre1 = new ManualResetEvent (false); public static ManualResetEvent mre2 = new ManualResetEvent (false); @@ -367,6 +368,62 @@ static void Test5 () Environment.Exit (15); } + public static bool got_to_the_end_of_outer_finally = false; + public static bool got_to_the_end_of_inner_finally = false; + + class StaticConstructor6 { + + static StaticConstructor6 () + { + try { + Setup6 (); + } finally { + Driver.got_to_the_end_of_outer_finally = true; + } + } + + [MethodImplAttribute (MethodImplOptions.NoInlining)] + public static void Setup6 () { + try { + } finally { + Driver.sema1.Release (); + Thread.Sleep (1000); /* hopefully we get woken up here */ + Driver.got_to_the_end_of_inner_finally = true; + } + } + } + + [MethodImplAttribute (MethodImplOptions.NoInlining)] + static void IsStaticConstructor6Viable () { + Console.WriteLine ("IsStaticConstructor6Viable? got to inner finally? {0} got to outer finally? {1}", + Driver.got_to_the_end_of_inner_finally, + Driver.got_to_the_end_of_outer_finally); + new StaticConstructor6 (); + if (!Driver.got_to_the_end_of_inner_finally) + Environment.Exit (17); + if (!Driver.got_to_the_end_of_outer_finally) + Environment.Exit (18); + } + + public static void Test6 () + { + Thread thread = new Thread (() => { + new StaticConstructor6 (); + }); + + thread.Start (); + Driver.sema1.Wait (); + thread.Abort (); + thread.Join (); + try { + IsStaticConstructor6Viable (); + Console.WriteLine ("StaticConstructor6 is viable"); + } catch (TypeInitializationException e) { + Console.WriteLine ("StaticConstructor6 is not viable"); + Environment.Exit (19); + } + } + public static int Main () { Test1 (); @@ -374,6 +431,7 @@ public static int Main () Test3 (); Test4 (); Test5 (); + Test6 (); Console.WriteLine ("done, all things good"); return 0; }