https://gcc.gnu.org/g:cac79586e1ab11fdb5480d7d1d93a48181fb3973
commit r16-7087-gcac79586e1ab11fdb5480d7d1d93a48181fb3973 Author: Nina Ranns <[email protected]> Date: Fri Oct 31 16:08:15 2025 +0000 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 <[email protected]> Signed-off-by: Iain Sandoe <[email protected]> Diff: --- gcc/c-family/c.opt | 5 ++++ gcc/cp/contracts.cc | 83 +++++++++++++++++++++++++++++++++++++++++++++++++---- gcc/doc/invoke.texi | 9 ++++++ 3 files changed, 91 insertions(+), 6 deletions(-) diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt index 1e8a4683f040..4d1aa2384c8c 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -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). diff --git a/gcc/cp/contracts.cc b/gcc/cp/contracts.cc index 205ea52774b9..a98f9a68e46d 100644 --- a/gcc/cp/contracts.cc +++ b/gcc/cp/contracts.cc @@ -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); diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index dbffbc131141..c2b91fe4465c 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -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
