c++, contracts: Work around GCC IPA bug, PR121936 by wrapping terminate.

This implements a no-ipa wrapper around the calls made from terminating
contract assertions so that callers can no longer make assuptions about
the no-return behaviour.  This is sufficient to work around the reported
bug while a suitable general fix is evaluated.

gcc/c-family/ChangeLog:

	* c.opt (fcontracts-conservative-ipa): New.

gcc/cp/ChangeLog:

	* contracts.cc (__tu_terminate_wrapper): New.
	(build_terminate_wrapper): New.
	(declare_terminate_wrapper): New.
	(maybe_emit_violation_handler_wrappers): Build a no-ipa wrapper
	for terminating contract violations if required.

gcc/ChangeLog:

	* doc/invoke.texi: Document -fcontracts-conservative-ipa.

Co-Authored-by: Iain Sandoe <iain@sandoe.co.uk>
Signed-off-by: Iain Sandoe <iain@sandoe.co.uk>
This commit is contained in:
Nina Ranns
2025-10-31 16:08:15 +00:00
committed by Iain Sandoe
parent c928dc5196
commit cac79586e1
3 changed files with 91 additions and 6 deletions

View File

@@ -1925,6 +1925,11 @@ fcontract-evaluation-semantic=
C++ ObjC++ Joined RejectNegative Enum(contract_semantic) Var(flag_contract_evaluation_semantic) Init(3)
-fcontract-evaluation-semantic=[ignore|observe|enforce|quick_enforce] Select the contract evaluation semantic (defaults to enforce).
fcontracts-conservative-ipa
C++ ObjC++ Var(flag_contracts_conservative_ipa) Init(1)
-fcontracts-conservative-ipa Do not allow certain optimisations between
functions in contract assertions.
fcoroutines
C++ ObjC++ LTO Var(flag_coroutines)
Enable C++ coroutines (experimental).

View File

@@ -1411,7 +1411,7 @@ static GTY(()) tree tu_has_violation = NULL_TREE;
static GTY(()) tree tu_has_violation_exception = NULL_TREE;
static void
maybe_declare_violation_handler_wrappers ()
declare_violation_handler_wrappers ()
{
if (tu_has_violation && tu_has_violation_exception)
return;
@@ -1433,6 +1433,65 @@ maybe_declare_violation_handler_wrappers ()
uint16_type_node);
}
static GTY(()) tree tu_terminate_wrapper = NULL_TREE;
/* Declare a noipa wrapper around the call to std::terminate */
static tree
declare_terminate_wrapper ()
{
if (tu_terminate_wrapper)
return tu_terminate_wrapper;
iloc_sentinel ils (input_location);
input_location = BUILTINS_LOCATION;
tree fn_type = build_function_type_list (void_type_node, NULL_TREE);
if (!TREE_NOTHROW (terminate_fn))
fn_type = build_exception_variant (fn_type, noexcept_true_spec);
tree fn_name = get_identifier ("__tu_terminate_wrapper");
tu_terminate_wrapper
= build_lang_decl_loc (input_location, FUNCTION_DECL, fn_name, fn_type);
DECL_CONTEXT (tu_terminate_wrapper) = FROB_CONTEXT(global_namespace);
DECL_ARTIFICIAL (tu_terminate_wrapper) = true;
DECL_INITIAL (tu_terminate_wrapper) = error_mark_node;
/* Let the start function code fill in the result decl. */
DECL_RESULT (tu_terminate_wrapper) = NULL_TREE;
/* Make this function internal. */
TREE_PUBLIC (tu_terminate_wrapper) = false;
DECL_EXTERNAL (tu_terminate_wrapper) = false;
DECL_WEAK (tu_terminate_wrapper) = false;
DECL_ATTRIBUTES (tu_terminate_wrapper)
= tree_cons (get_identifier ("noipa"), NULL, NULL_TREE);
cplus_decl_attributes (&tu_terminate_wrapper,
DECL_ATTRIBUTES (tu_terminate_wrapper), 0);
return tu_terminate_wrapper;
}
/* Define a noipa wrapper around the call to std::terminate */
static void
build_terminate_wrapper ()
{
/* We should not be trying to build this if we never used it. */
gcc_checking_assert (tu_terminate_wrapper);
start_preparsed_function (tu_terminate_wrapper,
DECL_ATTRIBUTES(tu_terminate_wrapper),
SF_DEFAULT | SF_PRE_PARSED);
tree body = begin_function_body ();
tree compound_stmt = begin_compound_stmt (BCS_FN_BODY);
finish_expr_stmt (build_call_a (terminate_fn, 0, nullptr));
finish_return_stmt (NULL_TREE);
finish_compound_stmt (compound_stmt);
finish_function_body (body);
tu_terminate_wrapper = finish_function (false);
expand_or_defer_fn (tu_terminate_wrapper);
}
/* Lookup a name in std::contracts, or inject it. */
static tree
@@ -1524,9 +1583,18 @@ build_contract_handler_call (tree violation)
void
maybe_emit_violation_handler_wrappers ()
{
/* We might need the terminate wrapper, even if we do not use the violation
handler wrappers. */
if (tu_terminate_wrapper && flag_contracts_conservative_ipa)
build_terminate_wrapper ();
if (!tu_has_violation && !tu_has_violation_exception)
return;
tree terminate_wrapper = terminate_fn;
if (flag_contracts_conservative_ipa)
terminate_wrapper = tu_terminate_wrapper;
/* tu_has_violation */
start_preparsed_function (tu_has_violation, NULL_TREE,
SF_DEFAULT | SF_PRE_PARSED);
@@ -1547,7 +1615,7 @@ maybe_emit_violation_handler_wrappers ()
finish_then_clause (if_observe);
begin_else_clause (if_observe);
/* else terminate. */
finish_expr_stmt (build_call_a (terminate_fn, 0, nullptr));
finish_expr_stmt (build_call_a (terminate_wrapper, 0, nullptr));
finish_else_clause (if_observe);
finish_if_stmt (if_observe);
finish_return_stmt (NULL_TREE);
@@ -1597,7 +1665,7 @@ maybe_emit_violation_handler_wrappers ()
finish_then_clause (if_observe);
begin_else_clause (if_observe);
/* else terminate. */
finish_expr_stmt (build_call_a (terminate_fn, 0, nullptr));
finish_expr_stmt (build_call_a (terminate_wrapper, 0, nullptr));
finish_else_clause (if_observe);
finish_if_stmt (if_observe);
finish_return_stmt (NULL_TREE);
@@ -1970,8 +2038,11 @@ build_contract_check (tree contract)
return NULL_TREE;
}
tree terminate_wrapper = terminate_fn;
if (flag_contracts_conservative_ipa)
terminate_wrapper = declare_terminate_wrapper ();
if (calls_handler)
maybe_declare_violation_handler_wrappers ();
declare_violation_handler_wrappers ();
bool check_might_throw = (flag_exceptions
&& !expr_noexcept_p (condition, tf_none));
@@ -2019,7 +2090,7 @@ build_contract_check (tree contract)
tree handler = begin_handler ();
finish_handler_parms (NULL_TREE, handler); /* catch (...) */
if (quick)
finish_expr_stmt (build_call_a (terminate_fn, 0, nullptr));
finish_expr_stmt (build_call_a (terminate_wrapper, 0, nullptr));
else
{
if (viol_is_var)
@@ -2061,7 +2132,7 @@ build_contract_check (tree contract)
tree do_check = begin_if_stmt ();
finish_if_stmt_cond (cond, do_check);
if (quick)
finish_expr_stmt (build_call_a (terminate_fn, 0, nullptr));
finish_expr_stmt (build_call_a (terminate_wrapper, 0, nullptr));
else
finish_expr_stmt (build_call_n (tu_has_violation, 2, violation, s_const));
finish_then_clause (do_check);

View File

@@ -226,6 +226,7 @@ in the following sections.
-fconstexpr-loop-limit=@var{n} -fconstexpr-ops-limit=@var{n}
-fcontracts
-fcontract-evaluation-semantic=@r{[}ignore@r{|}observe@r{|}enforce@r{|}quick_enforce@r{]}
-fcontracts-conservative-ipa
-fcoroutines -fdiagnostics-all-candidates
-fno-elide-constructors
-fno-enforce-eh-specs
@@ -3355,6 +3356,14 @@ fails, the execution is terminated immediately (without calling any handler).
At compile-time there is no difference in behaviour between this option and
the @code{enforce} case, since no handler is invoked at compile time.
@opindex fcontracts-conservative-ipa
@opindex fno-contracts-conservative-ipa
@item -fcontracts-conservative-ipa
This prevents inter-procedural analysis from taking action when the body
of an inline function is visible in a given TU. This allows for the case
that contract evaluation conditions are permitted to differ between TUs which
means that such actions would be potentially incorrect.
@opindex fcoroutines
@opindex fno-coroutines
@item -fcoroutines