]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c++, contracts: Allow contract checks as outlined functions.
authorNina Ranns <dinka.ranns@gmail.com>
Fri, 31 Oct 2025 00:20:43 +0000 (00:20 +0000)
committerIain Sandoe <iain@sandoe.co.uk>
Wed, 28 Jan 2026 01:26:10 +0000 (01:26 +0000)
In this variant of the contracts handling, we emit the contract
checks for pre and post conditions into small TU-local functions
that are then called in the relevant positions at the start of
the function and on each return edge (as a try-finally).

The rationale for adding this is that it is possible to treat
these outlined functions specially (for example, with different
(potentially fixed) optimisation settings from the code body.
Doing this can be a mechanism to work around cases where
optimisation of the contract conditions (or some function that
they might call) can lead to the elision of checks.

In order to pass parameters through to these small outlined
functions, we need similar functionality to that used for thunk
calls with respect copies of non-trivial values.  We are calling
this a "thunk-like" call, since none of the adjustments are
relevant here.

gcc/c-family/ChangeLog:

* c.opt (fcontract-checks-outlined,
fcontract-disable-optimized-checks): New.
* c.opt.urls: Regenerate.

gcc/cp/ChangeLog:

* contracts.cc (handle_contracts_p): Check that we are
handling an original function, not an outlined check.
(set_precondition_function, set_postcondition_function,
get_orig_for_outlined, contracts_fixup_name,
build_contract_condition_function,
build_precondition_function, build_postcondition_function,
build_contract_function_decls): New.
(start_function_contracts): Update for the case that we
outline the contract checks.
(build_arg_list, build_thunk_like_call,
add_pre_condition_fn_call,
get_postcondition_result_parameter,
add_post_condition_fn_call): New.
(apply_preconditions): Allow outlined checks.
(apply_postconditions): Likewise.
(get_precondition_function, get_postcondition_function,
set_contract_functions, remap_and_emit_conditions,
finish_function_contracts): New.
(get_src_loc_impl_ptr): Handle outlined checks.
(build_contract_check): Likewise.
* contracts.h (DECL_PRE_FN, DECL_POST_FN,
DECL_IS_PRE_FN_P, DECL_IS_POST_FN_P,
get_precondition_function, get_postcondition_function,
get_orig_for_outlined, finish_function_contracts,
set_contract_functions): New.
* cp-tree.h (enum lang_contract_helper): New.
(struct lang_decl_fn): Add contract helper enum.
(CONTRACT_HELPER): New.
(mangle_decl_string): New.
* decl.cc (finish_function): Emit outlined checks when
in use.
* module.cc (trees_out::fn_parms_init): Stream pre and post
outlined checks.
(trees_in::fn_parms_init): Reload pre and post outlined checks.
(check_mergeable_decl): Handle pre and post outlined functions.
(module_state_config::get_dialect): Add contracts dialect.

gcc/ChangeLog:

* doc/invoke.texi: Document -fcontract-checks-outlined and
-fcontract-disable-optimized-checks.

gcc/testsuite/ChangeLog:

* g++.dg/contracts/cpp26/outline-checks/freefunc-noexcept-post.C: New test.
* g++.dg/contracts/cpp26/outline-checks/freefunc-noexcept-pre.C: New test.
* g++.dg/contracts/cpp26/outline-checks/func-noexcept-assert.C: New test.
* g++.dg/contracts/cpp26/outline-checks/memberfunc-noexcept-post.C: New test.
* g++.dg/contracts/cpp26/outline-checks/memberfunc-noexcept-pre.C: New test.
* g++.dg/contracts/cpp26/empty-nt-param.C: Test with outlined checks.

Co-Authored-by: Iain Sandoe <iain@sandoe.co.uk>
Co-Authored-by: Ville Voutilainen <ville.voutilainen@gmail.com>
Signed-off-by: Iain Sandoe <iain@sandoe.co.uk>
16 files changed:
gcc/c-family/c.opt
gcc/c-family/c.opt.urls
gcc/cp/contracts.cc
gcc/cp/contracts.h
gcc/cp/cp-tree.h
gcc/cp/decl.cc
gcc/cp/module.cc
gcc/doc/invoke.texi
gcc/testsuite/g++.dg/contracts/cpp26/empty-nt-param.C
gcc/testsuite/g++.dg/contracts/cpp26/expr.prim.id.unqual.p7-3.C [new file with mode: 0644]
gcc/testsuite/g++.dg/contracts/cpp26/expr.prim.id.unqual.p7-4.C [new file with mode: 0644]
gcc/testsuite/g++.dg/contracts/cpp26/outline-checks/freefunc-noexcept-post.C [new file with mode: 0644]
gcc/testsuite/g++.dg/contracts/cpp26/outline-checks/freefunc-noexcept-pre.C [new file with mode: 0644]
gcc/testsuite/g++.dg/contracts/cpp26/outline-checks/func-noexcept-assert.C [new file with mode: 0644]
gcc/testsuite/g++.dg/contracts/cpp26/outline-checks/memberfunc-noexcept-post.C [new file with mode: 0644]
gcc/testsuite/g++.dg/contracts/cpp26/outline-checks/memberfunc-noexcept-pre.C [new file with mode: 0644]

index 4d1aa2384c8c615c150b64b13069af3662b963a7..d473377d91b8b6d696df057fd8d4cd56b2ff46f4 100644 (file)
@@ -1930,6 +1930,14 @@ C++ ObjC++ Var(flag_contracts_conservative_ipa) Init(1)
 -fcontracts-conservative-ipa   Do not allow certain optimisations between
 functions in contract assertions.
 
+fcontract-checks-outlined
+C++ ObjC++ Var(flag_contract_checks_outlined) Init(0)
+-fcontract-checks-outlined     Build contract checks using outlined functions.
+
+fcontract-disable-optimized-checks
+C++ ObjC++ Var(flag_contract_disable_optimized_checks) Init(0)
+-fcontract-disable-optimized-checks    Disable optimisation of contract checks.
+
 fcoroutines
 C++ ObjC++ LTO Var(flag_coroutines)
 Enable C++ coroutines (experimental).
index d214b1a7d9e4f051dd5a957f863187198a22b29f..12c1584663c5b4272acb99430421c84d046a0fc3 100644 (file)
@@ -1098,6 +1098,15 @@ UrlSuffix(gcc/C_002b_002b-Dialect-Options.html#index-fcontracts)
 fcontract-evaluation-semantic=
 UrlSuffix(gcc/C_002b_002b-Dialect-Options.html#index-fcontract-evaluation-semantic)
 
+fcontracts-conservative-ipa
+UrlSuffix(gcc/C_002b_002b-Dialect-Options.html#index-fcontracts-conservative-ipa)
+
+fcontract-checks-outlined
+UrlSuffix(gcc/C_002b_002b-Dialect-Options.html#index-fcontract-checks-outlined)
+
+fcontract-disable-optimized-checks
+UrlSuffix(gcc/C_002b_002b-Dialect-Options.html#index-fcontract-disable-optimized-checks)
+
 fcoroutines
 UrlSuffix(gcc/C_002b_002b-Dialect-Options.html#index-fcoroutines)
 
index cae1be9d991f810952f736e7c4b454184ea4bb42..0d9499a782ec9f1016a67b6d91eeaa6ad88215fd 100644 (file)
@@ -164,9 +164,9 @@ contract_valid_p (tree contract)
 /* True if the contract specifier is valid.  */
 
 static bool
-contract_specifier_valid_p (tree specifier)
+contract_specifier_valid_p (tree contract)
 {
-  return contract_valid_p (TREE_VALUE (TREE_VALUE (specifier)));
+  return contract_valid_p (TREE_VALUE (TREE_VALUE (contract)));
 }
 
 /* Compare the contract conditions of OLD_CONTRACT and NEW_CONTRACT.
@@ -335,10 +335,10 @@ handle_contracts_p (tree fndecl)
 {
   return (flag_contracts
          && !processing_template_decl
+         && (CONTRACT_HELPER (fndecl) == ldf_contract_none)
          && contract_any_active_p (fndecl));
 }
 
-
 /* For use with the tree inliner. This preserves non-mapped local variables,
    such as postcondition result variables, during remapping.  */
 
@@ -598,14 +598,264 @@ check_postconditions_in_redecl (tree olddecl, tree newdecl)
     }
 }
 
-void
-maybe_update_postconditions (tree fndecl)
+/* Map from FUNCTION_DECL to a FUNCTION_DECL for either the PRE_FN or POST_FN.
+   These are used to parse contract conditions and are called inside the body
+   of the guarded function.  */
+static GTY(()) hash_map<tree, tree> *decl_pre_fn;
+static GTY(()) hash_map<tree, tree> *decl_post_fn;
+
+/* Given a pre or post function decl (for an outlined check function) return
+   the decl for the function for which the outlined checks are being
+   performed.  */
+static GTY(()) hash_map<tree, tree> *orig_from_outlined;
+
+/* Makes PRE the precondition function for FNDECL.  */
+
+static void
+set_precondition_function (tree fndecl, tree pre)
 {
-  /* Update any postconditions and the postcondition checking function
-     as needed.  If there are postconditions, we'll use those to rewrite
-     return statements to check postconditions.  */
-  if (has_active_postconditions (fndecl))
-    rebuild_postconditions (fndecl);
+  gcc_assert (pre);
+  hash_map_maybe_create<hm_ggc> (decl_pre_fn);
+  gcc_checking_assert (!decl_pre_fn->get (fndecl));
+  decl_pre_fn->put (fndecl, pre);
+
+  hash_map_maybe_create<hm_ggc> (orig_from_outlined);
+  gcc_checking_assert (!orig_from_outlined->get (pre));
+  orig_from_outlined->put (pre, fndecl);
+}
+
+/* Makes POST the postcondition function for FNDECL.  */
+
+static void
+set_postcondition_function (tree fndecl, tree post)
+{
+  gcc_checking_assert (post);
+  hash_map_maybe_create<hm_ggc> (decl_post_fn);
+  gcc_checking_assert (!decl_post_fn->get (fndecl));
+  decl_post_fn->put (fndecl, post);
+
+  hash_map_maybe_create<hm_ggc> (orig_from_outlined);
+  gcc_checking_assert (!orig_from_outlined->get (post));
+  orig_from_outlined->put (post, fndecl);
+}
+
+/* For a given pre or post condition function, find the checked function.  */
+tree
+get_orig_for_outlined (tree fndecl)
+{
+  gcc_checking_assert (fndecl);
+  tree *result = hash_map_safe_get (orig_from_outlined, fndecl);
+  return result ? *result : NULL_TREE ;
+}
+
+/* For a given function OLD_FN set suitable names for NEW_FN (which is an
+   outlined contract check) usually by appending '.pre' or '.post'.
+
+   For functions with special meaning names (i.e. main and cdtors) we need to
+   make special provisions and therefore handle all the contracts function
+   name changes here, rather than requiring a separate update to mangle.cc.
+
+   PRE specifies if we need an identifier for a pre or post contract check.  */
+
+static void
+contracts_fixup_names (tree new_fn, tree old_fn, bool pre)
+{
+  bool cdtor = DECL_CXX_CONSTRUCTOR_P (old_fn)
+              || DECL_CXX_DESTRUCTOR_P (old_fn);
+  const char *fname = IDENTIFIER_POINTER (DECL_NAME (old_fn));
+  const char *pre_or_post = pre ? "pre" : "post";
+  size_t len = strlen (fname);
+  /* Cdtor names have a space at the end.  We need to remove that space
+     when forming the new identifier.  */
+  char *nn = xasprintf ("%.*s%s%s",
+                       cdtor ? (int)len-1 : int(len),
+                       fname,
+                       JOIN_STR,
+                       pre_or_post);
+  DECL_NAME (new_fn) = get_identifier (nn);
+  free (nn);
+
+  /* Now do the mangled version.  */
+  fname = IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (old_fn));
+  nn = xasprintf ("%s%s%s", fname, JOIN_STR, pre_or_post);
+  SET_DECL_ASSEMBLER_NAME (new_fn, get_identifier (nn));
+  free (nn);
+}
+
+/* Build a declaration for the pre- or postcondition of a guarded FNDECL.  */
+
+static tree
+build_contract_condition_function (tree fndecl, bool pre)
+{
+  if (error_operand_p (fndecl))
+    return error_mark_node;
+
+  /* Start the copy.  */
+  tree fn = copy_decl (fndecl);
+
+  /* Don't propagate declaration attributes to the checking function,
+     including the original contracts.  */
+  DECL_ATTRIBUTES (fn) = NULL_TREE;
+
+  /* If requested, disable optimisation of checking functions; this can, in
+     some cases, prevent UB from eliding the checks themselves.  */
+  if (flag_contract_disable_optimized_checks)
+    DECL_ATTRIBUTES (fn)
+      = tree_cons (get_identifier ("optimize"),
+                  build_tree_list (NULL_TREE, build_string (3, "-O0")),
+                  NULL_TREE);
+
+  /* Now parse and add any internal representation of these attrs to the
+     decl.  */
+  if (DECL_ATTRIBUTES (fn))
+    cplus_decl_attributes (&fn, DECL_ATTRIBUTES (fn), 0);
+
+  /* A possible later optimization may delete unused args to prevent extra arg
+     passing.  */
+  /* Handle the args list.  */
+  tree arg_types = NULL_TREE;
+  tree *last = &arg_types;
+  for (tree arg_type = TYPE_ARG_TYPES (TREE_TYPE (fn));
+      arg_type && arg_type != void_list_node;
+      arg_type = TREE_CHAIN (arg_type))
+    {
+      if (DECL_IOBJ_MEMBER_FUNCTION_P (fndecl)
+         && TYPE_ARG_TYPES (TREE_TYPE (fn)) == arg_type)
+      continue;
+      *last = build_tree_list (TREE_PURPOSE (arg_type), TREE_VALUE (arg_type));
+      last = &TREE_CHAIN (*last);
+    }
+
+  /* Copy the function parameters, if present.  Disable warnings for them.  */
+  DECL_ARGUMENTS (fn) = NULL_TREE;
+  if (DECL_ARGUMENTS (fndecl))
+    {
+      tree *last_a = &DECL_ARGUMENTS (fn);
+      for (tree p = DECL_ARGUMENTS (fndecl); p; p = TREE_CHAIN (p))
+       {
+         *last_a = copy_decl (p);
+         suppress_warning (*last_a);
+         DECL_CONTEXT (*last_a) = fn;
+         last_a = &TREE_CHAIN (*last_a);
+       }
+    }
+
+  tree orig_fn_value_type = TREE_TYPE (TREE_TYPE (fn));
+  if (!pre && !VOID_TYPE_P (orig_fn_value_type))
+    {
+      /* For post contracts that deal with a non-void function, append a
+        parameter to pass the return value.  */
+      tree name = get_identifier ("__r");
+      tree parm = build_lang_decl (PARM_DECL, name, orig_fn_value_type);
+      DECL_CONTEXT (parm) = fn;
+      DECL_ARTIFICIAL (parm) = true;
+      suppress_warning (parm);
+      DECL_ARGUMENTS (fn) = chainon (DECL_ARGUMENTS (fn), parm);
+      *last = build_tree_list (NULL_TREE, orig_fn_value_type);
+      last = &TREE_CHAIN (*last);
+    }
+
+  *last = void_list_node;
+
+  tree adjusted_type = NULL_TREE;
+
+  /* The handlers are void fns.  */
+  if (DECL_IOBJ_MEMBER_FUNCTION_P (fndecl))
+    adjusted_type = build_method_type_directly (DECL_CONTEXT (fndecl),
+                                               void_type_node,
+                                               arg_types);
+  else
+    adjusted_type = build_function_type (void_type_node, arg_types);
+
+  /* If the original function is noexcept, build a noexcept function.  */
+  if (flag_exceptions && type_noexcept_p (TREE_TYPE (fndecl)))
+    adjusted_type = build_exception_variant (adjusted_type, noexcept_true_spec);
+
+  TREE_TYPE (fn) = adjusted_type;
+  DECL_RESULT (fn) = NULL_TREE; /* Let the start function code fill it in.  */
+
+  /* The contract check functions are never a cdtor, nor virtual.  */
+  DECL_CXX_DESTRUCTOR_P (fn) = DECL_CXX_CONSTRUCTOR_P (fn) = 0;
+  DECL_VIRTUAL_P (fn) = false;
+
+  /* Append .pre / .post to a usable name for the original function.  */
+  contracts_fixup_names (fn, fndecl, pre);
+
+  DECL_INITIAL (fn) = NULL_TREE;
+  CONTRACT_HELPER (fn) = pre ? ldf_contract_pre : ldf_contract_post;
+
+  /* Make these functions internal if we can, i.e. if the guarded function is
+     not vague linkage, or if we can put them in a comdat group with the
+     guarded function.  */
+  if (!DECL_WEAK (fndecl) || HAVE_COMDAT_GROUP)
+    {
+      TREE_PUBLIC (fn) = false;
+      DECL_EXTERNAL (fn) = false;
+      DECL_WEAK (fn) = false;
+      DECL_COMDAT (fn) = false;
+
+      /* We may not have set the comdat group on the guarded function yet.
+        If we haven't, we'll add this to the same group in comdat_linkage
+        later.  Otherwise, add it to the same comdat group now.  */
+      if (DECL_ONE_ONLY (fndecl))
+       {
+         symtab_node *n = symtab_node::get (fndecl);
+         cgraph_node::get_create (fn)->add_to_same_comdat_group (n);
+       }
+
+    }
+
+  DECL_INTERFACE_KNOWN (fn) = true;
+  DECL_ARTIFICIAL (fn) = true;
+  suppress_warning (fn);
+
+  return fn;
+}
+
+/* Build the precondition checking function for FNDECL.  */
+
+static tree
+build_precondition_function (tree fndecl)
+{
+  if (!has_active_preconditions (fndecl))
+    return NULL_TREE;
+
+  return build_contract_condition_function (fndecl, /*pre=*/true);
+}
+
+/* Build the postcondition checking function for FNDECL.  If the return
+   type is undeduced, don't build the function yet.  We do that in
+   apply_deduced_return_type.  */
+
+static tree
+build_postcondition_function (tree fndecl)
+{
+  if (!has_active_postconditions (fndecl))
+    return NULL_TREE;
+
+  tree type = TREE_TYPE (TREE_TYPE (fndecl));
+  if (is_auto (type))
+    return NULL_TREE;
+
+  return build_contract_condition_function (fndecl, /*pre=*/false);
+}
+
+/* If we're outlining the contract, build the functions to do the
+   precondition and postcondition checks, and associate them with
+   the function decl FNDECL.
+ */
+
+static void
+build_contract_function_decls (tree fndecl)
+{
+  /* Build the pre/post functions (or not).  */
+  if (!get_precondition_function (fndecl))
+    if (tree pre = build_precondition_function (fndecl))
+      set_precondition_function (fndecl, pre);
+
+  if (!get_postcondition_function (fndecl))
+    if (tree post = build_postcondition_function (fndecl))
+      set_postcondition_function (fndecl, post);
 }
 
 void
@@ -617,8 +867,8 @@ start_function_contracts (tree fndecl)
   if (!handle_contracts_p (fndecl))
     return;
 
-  /* Check that the user did not try to shadow a function parameter with the
-     specified postcondition result name.  */
+  /* Check that the postcondition result name, if any, does not shadow a
+     function parameter.  */
   for (tree ca = get_fn_contract_specifiers (fndecl); ca; ca = TREE_CHAIN (ca))
     if (POSTCONDITION_P (CONTRACT_STATEMENT (ca)))
       if (tree id = POSTCONDITION_IDENTIFIER (CONTRACT_STATEMENT (ca)))
@@ -644,8 +894,7 @@ start_function_contracts (tree fndecl)
                location_t co_l = EXPR_LOCATION (CONTRACT_STATEMENT (ca));
                if (id_l != UNKNOWN_LOCATION)
                  co_l = make_location (id_l, co_l, co_l);
-               error_at (co_l,
-                         "contract postcondition result name shadows a"
+               error_at (co_l, "contract postcondition result name shadows a"
                          " function parameter");
                inform (DECL_SOURCE_LOCATION (seen),
                        "parameter declared here");
@@ -654,6 +903,130 @@ start_function_contracts (tree fndecl)
                CONTRACT_CONDITION (CONTRACT_STATEMENT (ca)) = error_mark_node;
            }
        }
+
+  /* If we are expanding contract assertions inline then no need to declare
+     the outline function decls.  */
+  if (!flag_contract_checks_outlined)
+    return;
+
+  /* Contracts may have just been added without a chance to parse them, though
+     we still need the PRE_FN available to generate a call to it.  */
+  /* Do we already have declarations generated ? */
+  if (!DECL_PRE_FN (fndecl) && !DECL_POST_FN (fndecl))
+    build_contract_function_decls (fndecl);
+}
+
+void
+maybe_update_postconditions (tree fndecl)
+{
+  /* Update any postconditions and the postcondition checking function
+     as needed.  If there are postconditions, we'll use those to rewrite
+     return statements to check postconditions.  */
+  if (has_active_postconditions (fndecl))
+    {
+      rebuild_postconditions (fndecl);
+      tree post = build_postcondition_function (fndecl);
+      set_postcondition_function (fndecl, post);
+    }
+}
+
+/* Build and return an argument list containing all the parameters of the
+   (presumably guarded) function decl FNDECL.  This can be used to forward
+   all of FNDECL arguments to a function taking the same list of arguments
+   -- namely the unchecked form of FNDECL.
+
+   We use CALL_FROM_THUNK_P instead of forward_parm for forwarding
+   semantics.  */
+
+static vec<tree, va_gc> *
+build_arg_list (tree fndecl)
+{
+  vec<tree, va_gc> *args = make_tree_vector ();
+  for (tree t = DECL_ARGUMENTS (fndecl); t; t = DECL_CHAIN (t))
+    vec_safe_push (args, t);
+  return args;
+}
+
+/* Build and return a thunk like call to FUNC from CALLER using the supplied
+   arguments.  The call is like a thunk call in the fact that we do not
+   want to create additional copies of the arguments.  We can not simply reuse
+   the thunk machinery as it does more than we want.  More specifically, we
+   don't want to mark the calling function as `DECL_THUNK_P` for this
+   particular purpose, we only want the special treatment for the parameters
+   of the call we are about to generate.  We temporarily mark the calling
+   function as DECL_THUNK_P so build_call_a does the right thing.  */
+
+static tree
+build_thunk_like_call (tree func, int n, tree *argarray)
+{
+  bool old_decl_thunk_p = DECL_THUNK_P (current_function_decl);
+  LANG_DECL_FN_CHECK (current_function_decl)->thunk_p  = true;
+
+  tree call = build_call_a (func, n, argarray);
+
+  /* Revert the `DECL_THUNK_P` flag.  */
+  LANG_DECL_FN_CHECK (current_function_decl)->thunk_p = old_decl_thunk_p;
+
+  /* Mark the call as a thunk call to allow for correct gimplification
+   of the arguments.  */
+  CALL_FROM_THUNK_P (call) = true;
+
+  return call;
+}
+
+/* If we have a precondition function and it's valid, call it.  */
+
+static void
+add_pre_condition_fn_call (tree fndecl)
+{
+  /* If we're starting a guarded function with valid contracts, we need to
+     insert a call to the pre function.  */
+  gcc_checking_assert (DECL_PRE_FN (fndecl)
+                      && DECL_PRE_FN (fndecl) != error_mark_node);
+
+  releasing_vec args = build_arg_list (fndecl);
+  tree call = build_thunk_like_call (DECL_PRE_FN (fndecl),
+                                    args->length (), args->address ());
+
+  finish_expr_stmt (call);
+}
+
+/* Returns the parameter corresponding to the return value of a guarded
+   function FNDECL.  Returns NULL_TREE if FNDECL has no postconditions or
+   is void.  */
+
+static tree
+get_postcondition_result_parameter (tree fndecl)
+{
+  if (!fndecl || fndecl == error_mark_node)
+    return NULL_TREE;
+
+  if (VOID_TYPE_P (TREE_TYPE (TREE_TYPE (fndecl))))
+    return NULL_TREE;
+
+  tree post = DECL_POST_FN (fndecl);
+  if (!post || post == error_mark_node)
+    return NULL_TREE;
+
+  /* The last param is the return value.  */
+  return tree_last (DECL_ARGUMENTS (post));
+}
+
+/* Build and add a call to the post-condition checking function, when that
+   is in use.  */
+
+static void
+add_post_condition_fn_call (tree fndecl)
+{
+  gcc_checking_assert (DECL_POST_FN (fndecl)
+                      && DECL_POST_FN (fndecl) != error_mark_node);
+
+  releasing_vec args = build_arg_list (fndecl);
+  if (get_postcondition_result_parameter (fndecl))
+    vec_safe_push (args, DECL_RESULT (fndecl));
+  tree call = build_thunk_like_call (DECL_POST_FN (fndecl),
+                                    args->length (), args->address ());
+  finish_expr_stmt (call);
 }
 
 /* Allow specifying a sub-set of contract kinds to copy.  */
@@ -756,7 +1129,7 @@ emit_contract_statement (tree contract)
   return true;
 }
 
-/* Generate the statement for the given contract by adding the
+/* Generate the statement for the given contract by adding the contract
    statement to the current block. Returns the next contract in the chain.  */
 
 static tree
@@ -774,9 +1147,14 @@ emit_contract (tree contract)
 static void
 apply_preconditions (tree fndecl)
 {
-  tree contract_copy = copy_contracts (fndecl, cmk_pre);
-  for (; contract_copy; contract_copy = TREE_CHAIN (contract_copy))
-    emit_contract (contract_copy);
+  if (flag_contract_checks_outlined)
+    add_pre_condition_fn_call (fndecl);
+  else
+  {
+    tree contract_copy = copy_contracts (fndecl, cmk_pre);
+    for (; contract_copy; contract_copy = TREE_CHAIN (contract_copy))
+      emit_contract (contract_copy);
+  }
 }
 
 /* Add a call or a direct evaluation of the post checks.  */
@@ -784,9 +1162,14 @@ apply_preconditions (tree fndecl)
 static void
 apply_postconditions (tree fndecl)
 {
-  tree contract_copy = copy_contracts (fndecl, cmk_post);
-  for (; contract_copy; contract_copy = TREE_CHAIN (contract_copy))
-    emit_contract (contract_copy);
+  if (flag_contract_checks_outlined)
+    add_post_condition_fn_call (fndecl);
+  else
+    {
+      tree contract_copy = copy_contracts (fndecl, cmk_post);
+      for (; contract_copy; contract_copy = TREE_CHAIN (contract_copy))
+       emit_contract (contract_copy);
+    }
 }
 
 /* Add contract handling to the function in FNDECL.
@@ -1490,6 +1873,116 @@ update_late_contract (tree contract, tree result, cp_expr condition)
   CONTRACT_CONDITION (contract) = condition;
 }
 
+/* Returns the precondition funtion for FNDECL, or null if not set.  */
+
+tree
+get_precondition_function (tree fndecl)
+{
+  gcc_checking_assert (fndecl);
+  tree *result = hash_map_safe_get (decl_pre_fn, fndecl);
+  return result ? *result : NULL_TREE;
+}
+
+/* Returns the postcondition funtion for FNDECL, or null if not set.  */
+
+tree
+get_postcondition_function (tree fndecl)
+{
+  gcc_checking_assert (fndecl);
+  tree *result = hash_map_safe_get (decl_post_fn, fndecl);
+  return result ? *result : NULL_TREE;
+}
+
+/* Set the PRE and POST functions for FNDECL.  Note that PRE and POST can
+   be null in this case.  If so the functions are not recorded.  Used by the
+   modules code.  */
+
+void
+set_contract_functions (tree fndecl, tree pre, tree post)
+{
+  if (pre)
+    set_precondition_function (fndecl, pre);
+
+  if (post)
+    set_postcondition_function (fndecl, post);
+}
+
+
+/* We're compiling the pre/postcondition function CONDFN; remap any FN
+   contracts that match CODE and emit them.  */
+
+static void
+remap_and_emit_conditions (tree fn, tree condfn, tree_code code)
+{
+  gcc_assert (code == PRECONDITION_STMT || code == POSTCONDITION_STMT);
+  tree contract_spec = get_fn_contract_specifiers (fn);
+  for (; contract_spec; contract_spec = TREE_CHAIN (contract_spec))
+    {
+      tree contract = CONTRACT_STATEMENT (contract_spec);
+      if (TREE_CODE (contract) == code)
+       {
+         contract = copy_node (contract);
+         if (CONTRACT_CONDITION (contract) != error_mark_node)
+           remap_contract (fn, condfn, contract, /*duplicate_p=*/false);
+         emit_contract_statement (contract);
+       }
+    }
+}
+
+/* Finish up the pre & post function definitions for a guarded FNDECL,
+   and compile those functions all the way to assembler language output.  */
+
+void
+finish_function_outlined_contracts (tree fndecl)
+{
+  /* If the guarded func is either already decided to be ill-formed or is
+     not yet complete return early.  */
+  if (error_operand_p (fndecl)
+      || !DECL_INITIAL (fndecl)
+      || DECL_INITIAL (fndecl) == error_mark_node)
+    return;
+
+  /* If there are no contracts here, or we're building them in-line then we
+     do not need to build the outlined functions.  */
+  if (!handle_contracts_p (fndecl)
+      || !flag_contract_checks_outlined)
+    return;
+
+  /* If either the pre or post functions are bad, don't bother emitting
+     any contracts.  The program is already ill-formed.  */
+  tree pre = DECL_PRE_FN (fndecl);
+  tree post = DECL_POST_FN (fndecl);
+  if (pre == error_mark_node || post == error_mark_node)
+    return;
+
+  /* We are generating code, deferred parses should be complete.  */
+  tree contract_spec = get_fn_contract_specifiers (fndecl);
+  gcc_checking_assert (!contract_any_deferred_p (contract_spec));
+
+  int flags = SF_DEFAULT | SF_PRE_PARSED;
+
+  if (pre && !DECL_INITIAL (pre))
+    {
+      DECL_PENDING_INLINE_P (pre) = false;
+      start_preparsed_function (pre, DECL_ATTRIBUTES (pre), flags);
+      remap_and_emit_conditions (fndecl, pre, PRECONDITION_STMT);
+      finish_return_stmt (NULL_TREE);
+      pre = finish_function (false);
+      expand_or_defer_fn (pre);
+    }
+
+  if (post && !DECL_INITIAL (post))
+    {
+      DECL_PENDING_INLINE_P (post) = false;
+      start_preparsed_function (post, DECL_ATTRIBUTES (post), flags);
+      remap_and_emit_conditions (fndecl, post, POSTCONDITION_STMT);
+      gcc_checking_assert (VOID_TYPE_P (TREE_TYPE (TREE_TYPE (post))));
+      finish_return_stmt (NULL_TREE);
+      post = finish_function (false);
+      expand_or_defer_fn (post);
+    }
+}
+
 /* ===== Code generation ===== */
 
 /* Insert a BUILT_IN_OBSERVABLE_CHECKPOINT epoch marker.  */
@@ -1961,6 +2454,9 @@ get_src_loc_impl_ptr (location_t loc)
     get_contracts_source_location_impl_type ();
 
   tree fndecl = current_function_decl;
+  /* We might be an outlined function.  */
+  if (DECL_IS_PRE_FN_P (fndecl) || DECL_IS_POST_FN_P (fndecl))
+    fndecl = get_orig_for_outlined (fndecl);
 
   gcc_checking_assert (fndecl);
   tree impl__
@@ -2155,7 +2651,7 @@ build_contract_check (tree contract)
   if (condition == error_mark_node)
     return NULL_TREE;
 
-  if (POSTCONDITION_P (contract))
+  if (!flag_contract_checks_outlined && POSTCONDITION_P (contract))
     {
       remap_retval (current_function_decl, contract);
       condition = CONTRACT_CONDITION (contract);
index ba5a203321b286549a8007e2013374c66e63b6a6..ef904e82f19dfa27fcccb6865f1aba68b0df3ff2 100644 (file)
@@ -120,6 +120,26 @@ enum detection_mode : uint16_t {
 #define POSTCONDITION_IDENTIFIER(NODE) \
   (TREE_OPERAND (POSTCONDITION_STMT_CHECK (NODE), 5))
 
+/* For a FUNCTION_DECL of a guarded function, this holds the function decl
+   where pre contract checks are emitted.  */
+#define DECL_PRE_FN(NODE) \
+  (get_precondition_function ((NODE)))
+
+/* For a FUNCTION_DECL of a guarded function, this holds the function decl
+   where post contract checks are emitted.  */
+#define DECL_POST_FN(NODE) \
+  (get_postcondition_function ((NODE)))
+
+/* True iff the FUNCTION_DECL is the pre function for a guarded function.  */
+#define DECL_IS_PRE_FN_P(NODE) \
+  (DECL_DECLARES_FUNCTION_P (NODE) && DECL_LANG_SPECIFIC (NODE) \
+   && CONTRACT_HELPER (NODE) == ldf_contract_pre)
+
+/* True iff the FUNCTION_DECL is the post function for a guarded function.  */
+#define DECL_IS_POST_FN_P(NODE) \
+  (DECL_DECLARES_FUNCTION_P (NODE) && DECL_LANG_SPECIFIC (NODE) \
+   && CONTRACT_HELPER (NODE) == ldf_contract_post)
+
 /* contracts.cc */
 
 extern void init_contracts                     (void);
@@ -151,8 +171,14 @@ extern bool check_postcondition_result             (tree, tree, location_t);
 
 extern bool contract_any_deferred_p            (tree);
 
+extern tree get_precondition_function          (tree);
+extern tree get_postcondition_function         (tree);
+extern tree get_orig_for_outlined              (tree);
+
 extern void start_function_contracts           (tree);
 extern void maybe_apply_function_contracts     (tree);
+extern void finish_function_outlined_contracts (tree);
+extern void set_contract_functions             (tree, tree, tree);
 
 extern void maybe_emit_violation_handler_wrappers (void);
 
index 9153ff5b43d62c981888c6cacb374f9e278b1a27..215bef49adf6cfda95ccf7719f878237fc7743b8 100644 (file)
@@ -3183,6 +3183,13 @@ struct GTY(()) lang_decl_min {
   tree access;
 };
 
+enum lang_contract_helper
+{
+  ldf_contract_none = 0,
+  ldf_contract_pre,
+  ldf_contract_post
+};
+
 /* Additional DECL_LANG_SPECIFIC information for functions.  */
 
 struct GTY(()) lang_decl_fn {
@@ -3212,8 +3219,9 @@ struct GTY(()) lang_decl_fn {
   unsigned escalated_p : 1;
 
   unsigned xobj_func : 1;
+  ENUM_BITFIELD(lang_contract_helper) contract_helper : 2;
 
-  unsigned spare : 7;
+  unsigned spare : 5;
 
   /* 32-bits padding on 64-bit host.  */
 
@@ -3361,6 +3369,9 @@ struct GTY(()) lang_decl {
 
 #endif /* ENABLE_TREE_CHECKING */
 
+#define CONTRACT_HELPER(NODE) \
+ (LANG_DECL_FN_CHECK (NODE)->contract_helper)
+
 /* For a FUNCTION_DECL or a VAR_DECL, the language linkage for the
    declaration.  Some entities (like a member function in a local
    class, or a local variable) do not have linkage at all, and this
@@ -7149,7 +7160,6 @@ extern tree build_conditional_expr                (const op_location_t &,
 extern tree build_addr_func                    (tree, tsubst_flags_t);
 extern void set_flags_from_callee              (tree);
 extern tree build_call_a                       (tree, int, tree*);
-extern tree build_call_a_1                     (tree, int, tree*);
 extern tree build_call_n                       (tree, int, ...);
 extern bool null_ptr_cst_p                     (tree);
 extern bool null_member_pointer_value_p                (tree);
index 1e353ef1ebcf69f5a8ef99f0b52a1d52cfb0c2e8..270158510dff4867c276e082c53c7bc5fce1508a 100644 (file)
@@ -20708,6 +20708,10 @@ finish_function (bool inline_p)
       delete coroutine;
     }
 
+  /* If we have used outlined contracts checking functions, build and emit
+     them here.  */
+  finish_function_outlined_contracts (fndecl);
+
   return fndecl;
 }
 \f
index 086be96ca50eea6ebfcd91e05179f1b067667a2a..e2097027dbd915deca5ac1d11e880dd9e1bdd4d8 100644 (file)
@@ -232,6 +232,7 @@ Classes used:
 #include "attribs.h"
 #include "intl.h"
 #include "langhooks.h"
+#include "contracts.h"
 /* This TU doesn't need or want to see the networking.  */
 #define CODY_NETWORKING 0
 #include "mapper-client.h"
@@ -11295,6 +11296,20 @@ trees_out::fn_parms_init (tree fn)
                   base_tag - ix, ix, parm, fn);
       tree_node_vals (parm);
     }
+
+  if (!streaming_p ())
+    {
+      /* We must walk contract specifiers so the dependency graph is
+        complete.  */
+      tree contract = get_fn_contract_specifiers (fn);
+      for (; contract; contract = TREE_CHAIN (contract))
+       tree_node (contract);
+    }
+
+  /* Write a reference to contracts pre/post functions, if any, to avoid
+     regenerating them in importers.  */
+  tree_node (DECL_PRE_FN (fn));
+  tree_node (DECL_POST_FN (fn));
 }
 
 /* Build skeleton parm nodes, read their flags, type & parm indices.  */
@@ -11329,6 +11344,11 @@ trees_in::fn_parms_init (tree fn)
        return 0;
     }
 
+  /* Reload references to contract functions, if any.  */
+  tree pre_fn = tree_node ();
+  tree post_fn = tree_node ();
+  set_contract_functions (fn, pre_fn, post_fn);
+
   return base_tag;
 }
 
@@ -12055,7 +12075,15 @@ check_mergeable_decl (merge_kind mk, tree decl, tree ovl, merge_key const &key)
                   Matches decls_match behaviour.  */
                && (!DECL_IS_UNDECLARED_BUILTIN (m_inner)
                    || !DECL_EXTERN_C_P (m_inner)
-                   || DECL_EXTERN_C_P (d_inner)))
+                   || DECL_EXTERN_C_P (d_inner))
+               /* Reject if one is a different member of a
+                  guarded/pre/post fn set.  */
+               && (!flag_contracts
+                   || (DECL_IS_PRE_FN_P (d_inner)
+                       == DECL_IS_PRE_FN_P (m_inner)))
+               && (!flag_contracts
+                   || (DECL_IS_POST_FN_P (d_inner)
+                       == DECL_IS_POST_FN_P (m_inner))))
              {
                tree m_reqs = get_constraints (m_inner);
                if (m_reqs)
@@ -17108,6 +17136,7 @@ module_state_config::get_dialect ()
                      (cxx_dialect < cxx20 && flag_coroutines
                       ? "/coroutines" : ""),
                      flag_module_implicit_inline ? "/implicit-inline" : "",
+                     flag_contracts ? "/contracts" : "",
                      NULL);
 
   return dialect;
index c2b91fe4465c3a8d27fccad18ea13cf093d2ab5e..e5951aac3856eecd536afe12dd6bd69332f13b42 100644 (file)
@@ -226,7 +226,8 @@ 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
+-fcontracts-conservative-ipa  -fcontract-checks-outlined
+-fcontract-disable-optimized-checks
 -fcoroutines  -fdiagnostics-all-candidates
 -fno-elide-constructors
 -fno-enforce-eh-specs
@@ -3364,6 +3365,22 @@ 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 fcontract-checks-outlined
+@opindex fno-contract-checks-outlined
+@item -fcontract-checks-outlined
+By default, contract checks for @code{pre} and @code{post} condition checks are
+expanded in-line into function bodies.  This option causes a small function to
+be created instead (containing the checks) that is called in the relevant
+position.  This permits different criteria to be applied to the compilation of
+the checking and the remainder of the function body.
+
+@opindex fcontract-disable-optimized-checks
+@opindex fno-contract-disable-optimized-checks
+@item -fcontract-disable-optimized-checks
+When @code{pre} and @code{post} condition checks are outlined (using
+@option{-fcontract-checks-outlined}), this option disables the optimisation 
+steps for those functions which can avoid unwanted elision of checking steps.
+
 @opindex fcoroutines
 @opindex fno-coroutines
 @item -fcoroutines
index 8ac0d41e0f51853f43d0010b695f1489b979b911..ab92144eacda660b348fd00007148683568553f8 100644 (file)
@@ -1,6 +1,6 @@
 // check that we do not ICE with an empty nontrivial parameter
 // { dg-do run { target c++23 } }
-// { dg-additional-options "-fcontracts" }
+// { dg-additional-options "-fcontracts -fcontract-checks-outlined" }
 
 struct NTClass {
   NTClass(){};
diff --git a/gcc/testsuite/g++.dg/contracts/cpp26/expr.prim.id.unqual.p7-3.C b/gcc/testsuite/g++.dg/contracts/cpp26/expr.prim.id.unqual.p7-3.C
new file mode 100644 (file)
index 0000000..93ab26e
--- /dev/null
@@ -0,0 +1,55 @@
+// N5008 [expr.prim.id.unqual]/p7
+// Otherwise, if the unqualified-id appears in the predicate of a contract assertion C (6.10) and the entity is
+// (7.1) — a variable declared outside of C of object type T,
+// (7.2) — a variable or template parameter declared outside of C of type “reference to T”, or
+// (7.3) — a structured binding of type T whose corresponding variable is declared outside of C,
+// then the type of the expression is const T
+// This tests modifications to the constified things if checks are outlined
+// { dg-do run { target c++26 } }
+// { dg-additional-options "-fcontracts -fcontract-checks-outlined" }
+// { dg-xfail-run-if "PRXXXXXX" { *-*-* } }
+
+struct S{
+  S(){};
+  S(const S&){}
+  ~S(){};
+  int x = 0;
+};
+
+int i = 0;
+
+
+void f1() pre(const_cast<int&>(i)++) {};
+int f2(int n,const S m) pre(const_cast<int&>(n)++)
+                       pre((const_cast<S&>(m).x = 5))
+                       post(r: const_cast<int&>(r)++)
+                       post(r: const_cast<int&>(r)++)
+{
+  contract_assert (n == 3);
+  contract_assert (m.x == 5);
+
+  return 1;
+};
+
+
+S f3(S s) post(r: (const_cast<S&>(r).x = 10) )
+{
+  return s;
+}
+
+int main()
+{
+  i = 3;
+  f1();
+  contract_assert (i == 4);
+
+  int j = 2;
+  S s;
+  int k = f2(j,s);
+  contract_assert (k == 3);
+  contract_assert (s.x == 0);
+
+  s = f3(s);
+  contract_assert (s.x == 10);
+
+}
diff --git a/gcc/testsuite/g++.dg/contracts/cpp26/expr.prim.id.unqual.p7-4.C b/gcc/testsuite/g++.dg/contracts/cpp26/expr.prim.id.unqual.p7-4.C
new file mode 100644 (file)
index 0000000..c9cdbe5
--- /dev/null
@@ -0,0 +1,57 @@
+// N5008 [expr.prim.id.unqual]/p7
+// Otherwise, if the unqualified-id appears in the predicate of a contract assertion C (6.10) and the entity is
+// (7.1) — a variable declared outside of C of object type T,
+// (7.2) — a variable or template parameter declared outside of C of type “reference to T”, or
+// (7.3) — a structured binding of type T whose corresponding variable is declared outside of C,
+// then the type of the expression is const T
+// This tests modifications to the constified things
+// { dg-do run { target c++23 } }
+// { dg-additional-options "-fcontracts -fcontract-evaluation-semantic=observe -O2 -g" }
+// { dg-xfail-run-if "PRXXXXXX" { *-*-* } { "-fcontract-checks-outlined" } "" }
+
+#include <cassert>
+
+struct S{
+  S(){};
+  S(const S&){}
+  ~S(){};
+  int x = 0;
+};
+
+int i = 0;
+
+
+void f1() pre(const_cast<int&>(i)++) {};
+int f2(int n,const S m) pre(const_cast<int&>(n)++)
+                       pre((const_cast<S&>(m).x = 5))
+                       post(r: const_cast<int&>(r)++)
+                       post(r: const_cast<int&>(r)++)
+{
+  assert (n == 3);
+  assert (m.x == 5);
+
+  return 1;
+};
+
+
+S f3(S s) post(r: (const_cast<S&>(r).x = 10) )
+{
+  return s;
+}
+
+int main()
+{
+  i = 3;
+  f1();
+  assert (i == 4);
+
+  int j = 2;
+  S s;
+  int k = f2(j,s);
+  assert (k == 3);
+  assert (s.x == 0);
+
+  s = f3(s);
+  assert (s.x == 10);
+
+}
diff --git a/gcc/testsuite/g++.dg/contracts/cpp26/outline-checks/freefunc-noexcept-post.C b/gcc/testsuite/g++.dg/contracts/cpp26/outline-checks/freefunc-noexcept-post.C
new file mode 100644 (file)
index 0000000..48b752b
--- /dev/null
@@ -0,0 +1,46 @@
+// Throwing violation handler in a pre/post check on a noexcept function
+// behaves as if the function exited via an exception.
+// This tests the behaviour of a pre condition on a member function
+// { dg-do run { target c++26 } }
+// { dg-additional-options "-fcontracts -fcontract-evaluation-semantic=observe -fcontract-checks-outlined" }
+
+#include <contracts>
+#include <exception>
+#include <cstdlib>
+
+struct MyException{};
+
+// Test that there is an active exception when we reach the terminate handler.
+void my_term()
+{
+  try { throw; }
+  catch(MyException) { std::exit(0); }
+}
+
+
+void handle_contract_violation(const std::contracts::contract_violation& violation)
+{
+  throw MyException{};
+}
+
+void f(const int x) noexcept post(x>1)
+{
+  try{
+   int i = 1;
+  }
+  catch(...) {
+  }
+}
+
+int main()
+{
+  std::set_terminate (my_term);
+  try
+  {
+      f(-42);
+  } catch (...) {
+  }
+  // We should not get here
+  return 1;
+
+}
diff --git a/gcc/testsuite/g++.dg/contracts/cpp26/outline-checks/freefunc-noexcept-pre.C b/gcc/testsuite/g++.dg/contracts/cpp26/outline-checks/freefunc-noexcept-pre.C
new file mode 100644 (file)
index 0000000..a662734
--- /dev/null
@@ -0,0 +1,46 @@
+// Throwing violation handler in a pre/post check on a noexcept function
+// behaves as if the function exited via an exception.
+// This tests the behaviour of a pre condition on a member function
+// { dg-do run { target c++26 } }
+// { dg-additional-options "-fcontracts -fcontract-evaluation-semantic=observe -fcontract-checks-outlined" }
+
+#include <contracts>
+#include <exception>
+#include <cstdlib>
+
+struct MyException{};
+
+// Test that there is an active exception when we reach the terminate handler.
+void my_term()
+{
+  try { throw; }
+  catch(MyException) { std::exit(0); }
+}
+
+
+void handle_contract_violation(const std::contracts::contract_violation& violation)
+{
+  throw MyException{};
+}
+
+void f(int x) noexcept pre(x>1)
+{
+  try{
+   int i = 1;
+  }
+  catch(...) {
+  }
+}
+
+int main()
+{
+  std::set_terminate (my_term);
+  try
+  {
+      f(-42);
+  } catch (...) {
+  }
+  // We should not get here
+  return 1;
+
+}
diff --git a/gcc/testsuite/g++.dg/contracts/cpp26/outline-checks/func-noexcept-assert.C b/gcc/testsuite/g++.dg/contracts/cpp26/outline-checks/func-noexcept-assert.C
new file mode 100644 (file)
index 0000000..f2bc62c
--- /dev/null
@@ -0,0 +1,55 @@
+// Throwing violation handler in an assert check in a noexcept function
+// can be caught by the function.
+// { dg-do run { target c++26 } }
+// { dg-additional-options "-fcontracts -fcontract-evaluation-semantic=observe -fcontract-checks-outlined" }
+
+#include <contracts>
+#include <exception>
+#include <cstdlib>
+
+struct MyException{};
+
+
+void handle_contract_violation(const std::contracts::contract_violation& violation)
+{
+  throw MyException{};
+}
+
+void free_f(const int x) {
+  try {
+         contract_assert(x>1);
+         int i = 1;
+  }
+  catch(...){}
+}
+
+struct X
+{
+    void f(const int x) {
+      try {
+         contract_assert(x>1);
+         int i = 1;
+      }
+      catch(...){}
+    }
+
+    virtual void virt_f(const int x) {
+      try {
+         contract_assert(x>1);
+         int i = 1;
+      }
+      catch(...){}
+    }
+
+};
+
+int main()
+{
+  free_f(-42);
+
+  X x;
+  x.f(-42);
+  x.virt_f(-42);
+
+
+}
diff --git a/gcc/testsuite/g++.dg/contracts/cpp26/outline-checks/memberfunc-noexcept-post.C b/gcc/testsuite/g++.dg/contracts/cpp26/outline-checks/memberfunc-noexcept-post.C
new file mode 100644 (file)
index 0000000..536f709
--- /dev/null
@@ -0,0 +1,49 @@
+// Throwing violation handler in a pre/post check on a noexcept function
+// behaves as if the function exited via an exception.
+// This tests the behaviour of a post condition on a member function
+// { dg-do run { target c++26 } }
+// { dg-additional-options "-fcontracts -fcontract-evaluation-semantic=observe -fcontract-checks-outlined" }
+
+#include <contracts>
+#include <exception>
+#include <cstdlib>
+
+struct MyException{};
+
+// Test that there is an active exception when we reach the terminate handler.
+void my_term()
+{
+  try { throw; }
+  catch(MyException) { std::exit(0); }
+}
+
+
+void handle_contract_violation(const std::contracts::contract_violation& violation)
+{
+  throw MyException{};
+}
+
+struct X
+{
+    void f(const int x) noexcept post(x>1) {
+      try{
+       int i = 1;
+      }
+      catch(...) {
+      }
+    }
+};
+
+int main()
+{
+  std::set_terminate (my_term);
+  try
+  {
+      X x;
+      x.f(-42);
+  } catch (...) {
+  }
+  // We should not get here
+  return 1;
+
+}
diff --git a/gcc/testsuite/g++.dg/contracts/cpp26/outline-checks/memberfunc-noexcept-pre.C b/gcc/testsuite/g++.dg/contracts/cpp26/outline-checks/memberfunc-noexcept-pre.C
new file mode 100644 (file)
index 0000000..f629587
--- /dev/null
@@ -0,0 +1,49 @@
+// Throwing violation handler in a pre/post check on a noexcept function
+// behaves as if the function exited via an exception.
+// This tests the behaviour of a pre condition on a member function
+// { dg-do run { target c++26 } }
+// { dg-additional-options "-fcontracts -fcontract-evaluation-semantic=observe -fcontract-checks-outlined" }
+
+#include <contracts>
+#include <exception>
+#include <cstdlib>
+
+struct MyException{};
+
+// Test that there is an active exception when we reach the terminate handler.
+void my_term()
+{
+  try { throw; }
+  catch(MyException) { std::exit(0); }
+}
+
+
+void handle_contract_violation(const std::contracts::contract_violation& violation)
+{
+  throw MyException{};
+}
+
+struct X
+{
+    void f(int x) noexcept pre(x>1) {
+      try{
+       int i = 1;
+      }
+      catch(...) {
+      }
+    }
+};
+
+int main()
+{
+  std::set_terminate (my_term);
+  try
+  {
+      X x;
+      x.f(-42);
+  } catch (...) {
+  }
+  // We should not get here
+  return 1;
+
+}