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

Reply via email to