]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c++, contracts: Work around GCC IPA bug, PR121936 by wrapping terminate.
authorNina Ranns <dinka.ranns@gmail.com>
Fri, 31 Oct 2025 16:08:15 +0000 (16:08 +0000)
committerIain Sandoe <iain@sandoe.co.uk>
Wed, 28 Jan 2026 01:24:17 +0000 (01:24 +0000)
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>
gcc/c-family/c.opt
gcc/cp/contracts.cc
gcc/doc/invoke.texi

index 1e8a4683f0401fbf1592615f6c537267fa5e9d4b..4d1aa2384c8c615c150b64b13069af3662b963a7 100644 (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).
index 205ea52774b9de82521f020b386b2c5be301f2b7..a98f9a68e46dc04d33272ab0871f249ff5b0f2b4 100644 (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);
index dbffbc131141619e5f66c479c3832844000a2bcc..c2b91fe4465c3a8d27fccad18ea13cf093d2ab5e 100644 (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