c++: use __cxa_call_terminate for MUST_NOT_THROW [PR97720]

[except.handle]/7 says that when we enter std::terminate due to a throw,
that is considered an active handler.  We already implemented that properly
for the case of not finding a handler (__cxa_throw calls __cxa_begin_catch
before std::terminate) and the case of finding a callsite with no landing
pad (the personality function calls __cxa_call_terminate which calls
__cxa_begin_catch), but for the case of a throw in a try/catch in a noexcept
function, we were emitting a cleanup that calls std::terminate directly
without ever calling __cxa_begin_catch to handle the exception.

A straightforward way to fix this seems to be calling __cxa_call_terminate
instead.  However, that requires exporting it from libstdc++, which we have
not previously done.  Despite the name, it isn't actually part of the ABI
standard.  Nor is __cxa_call_unexpected, as far as I can tell, but that one
is also used by clang.  For this case they use __clang_call_terminate; it
seems reasonable to me for us to stick with __cxa_call_terminate.

I also change __cxa_call_terminate to take void* for simplicity in the front
end (and consistency with __cxa_call_unexpected) but that isn't necessary if
it's undesirable for some reason.

This patch does not fix the issue that representing the noexcept as a
cleanup is wrong, and confuses the handler search; since it looks like a
cleanup in the EH tables, the unwinder keeps looking until it finds the
catch in main(), which it should never have gotten to.  Without the
try/catch in main, the unwinder would reach the end of the stack and say no
handler was found.  The noexcept is a handler, and should be treated as one,
as it is when the landing pad is omitted.

The best fix for that issue seems to me to be to represent an
ERT_MUST_NOT_THROW after an ERT_TRY in an action list as though it were an
ERT_ALLOWED_EXCEPTIONS (since indeed it is an exception-specification).  The
actual code generation shouldn't need to change (apart from the change made
by this patch), only the action table entry.

	PR c++/97720

gcc/cp/ChangeLog:

	* cp-tree.h (enum cp_tree_index): Add CPTI_CALL_TERMINATE_FN.
	(call_terminate_fn): New macro.
	* cp-gimplify.cc (gimplify_must_not_throw_expr): Use it.
	* except.cc (init_exception_processing): Set it.
	(cp_protect_cleanup_actions): Return it.

gcc/ChangeLog:

	* tree-eh.cc (lower_resx): Pass the exception pointer to the
	failure_decl.
	* except.h: Tweak comment.

libstdc++-v3/ChangeLog:

	* libsupc++/eh_call.cc (__cxa_call_terminate): Take void*.
	* config/abi/pre/gnu.ver: Add it.

gcc/testsuite/ChangeLog:

	* g++.dg/eh/terminate2.C: New test.
This commit is contained in:
Jason Merrill 2023-05-23 12:25:15 -04:00
parent 3991b2f623
commit 2415024e0f
8 changed files with 63 additions and 5 deletions

View File

@ -343,7 +343,7 @@ gimplify_must_not_throw_expr (tree *expr_p, gimple_seq *pre_p)
gimple *mnt;
gimplify_and_add (body, &try_);
mnt = gimple_build_eh_must_not_throw (terminate_fn);
mnt = gimple_build_eh_must_not_throw (call_terminate_fn);
gimple_seq_add_stmt_without_update (&catch_, mnt);
mnt = gimple_build_try (try_, catch_, GIMPLE_TRY_CATCH);

View File

@ -217,6 +217,7 @@ enum cp_tree_index
definitions. */
CPTI_ALIGN_TYPE,
CPTI_TERMINATE_FN,
CPTI_CALL_TERMINATE_FN,
CPTI_CALL_UNEXPECTED_FN,
/* These are lazily inited. */
@ -358,6 +359,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
/* Exception handling function declarations. */
#define terminate_fn cp_global_trees[CPTI_TERMINATE_FN]
#define call_unexpected_fn cp_global_trees[CPTI_CALL_UNEXPECTED_FN]
#define call_terminate_fn cp_global_trees[CPTI_CALL_TERMINATE_FN]
#define get_exception_ptr_fn cp_global_trees[CPTI_GET_EXCEPTION_PTR_FN]
#define begin_catch_fn cp_global_trees[CPTI_BEGIN_CATCH_FN]
#define end_catch_fn cp_global_trees[CPTI_END_CATCH_FN]

View File

@ -64,6 +64,9 @@ init_exception_processing (void)
tmp = build_function_type_list (void_type_node, ptr_type_node, NULL_TREE);
call_unexpected_fn
= push_throw_library_fn (get_identifier ("__cxa_call_unexpected"), tmp);
call_terminate_fn
= push_library_fn (get_identifier ("__cxa_call_terminate"), tmp, NULL_TREE,
ECF_NORETURN | ECF_COLD | ECF_NOTHROW);
}
/* Returns an expression to be executed if an unhandled exception is
@ -76,7 +79,7 @@ cp_protect_cleanup_actions (void)
When the destruction of an object during stack unwinding exits
using an exception ... void terminate(); is called. */
return terminate_fn;
return call_terminate_fn;
}
static tree

View File

@ -155,7 +155,7 @@ struct GTY(()) eh_region_d
struct eh_region_u_must_not_throw {
/* A function decl to be invoked if this region is actually reachable
from within the function, rather than implementable from the runtime.
The normal way for this to happen is for there to be a CLEANUP region
The normal way for this to happen is for there to be a TRY region
contained within this MUST_NOT_THROW region. Note that if the
runtime handles the MUST_NOT_THROW region, we have no control over
what termination function is called; it will be decided by the

View File

@ -0,0 +1,30 @@
// PR c++/97720
// { dg-do run }
// Test that there is an active exception when we reach the terminate handler.
#include <exception>
#include <cstdlib>
void bad_guy() throw() {
try { throw 0; }
catch (float) { }
// Don't catch int.
}
void level1() {
bad_guy();
throw "dead code";
}
void my_term()
{
try { throw; }
catch(...) { std::exit(0); }
}
int main() {
std::set_terminate (my_term);
try { level1(); }
catch (int) { }
}

View File

@ -3382,8 +3382,22 @@ lower_resx (basic_block bb, gresx *stmt,
lab = gimple_block_label (new_bb);
gsi2 = gsi_start_bb (new_bb);
/* Handle failure fns that expect either no arguments or the
exception pointer. */
fn = dst_r->u.must_not_throw.failure_decl;
x = gimple_build_call (fn, 0);
if (TYPE_ARG_TYPES (TREE_TYPE (fn)) != void_list_node)
{
tree epfn = builtin_decl_implicit (BUILT_IN_EH_POINTER);
src_nr = build_int_cst (integer_type_node, src_r->index);
x = gimple_build_call (epfn, 1, src_nr);
tree var = create_tmp_var (ptr_type_node);
var = make_ssa_name (var, x);
gimple_call_set_lhs (x, var);
gsi_insert_after (&gsi2, x, GSI_CONTINUE_LINKING);
x = gimple_build_call (fn, 1, var);
}
else
x = gimple_build_call (fn, 0);
gimple_set_location (x, dst_r->u.must_not_throw.failure_loc);
gsi_insert_after (&gsi2, x, GSI_CONTINUE_LINKING);

View File

@ -2841,6 +2841,13 @@ CXXABI_1.3.14 {
} CXXABI_1.3.13;
CXXABI_1.3.15 {
global:
__cxa_call_terminate;
} CXXABI_1.3.14;
# Symbols in the support library (libsupc++) supporting transactional memory.
CXXABI_TM_1 {

View File

@ -36,8 +36,10 @@ using namespace __cxxabiv1;
// terminate.
extern "C" void
__cxa_call_terminate(_Unwind_Exception* ue_header) throw ()
__cxa_call_terminate(void* ue_header_in) throw ()
{
_Unwind_Exception* ue_header
= reinterpret_cast<_Unwind_Exception*>(ue_header_in);
if (ue_header)
{