Hello, Most of the comments were addressed as suggested. Please see below for the comments that may still need your attention.
Best, Nina On Sun, 18 Jan 2026 at 10:01, Jason Merrill <[email protected]> wrote: > On 12/1/25 10:18 PM, Iain Sandoe wrote: > > From: Nina Ranns <[email protected]> > > > > Changes since v1 > > - fixed a merge error in the removal of C++2a code. > > - rebased onto r16-5785-g3b30d09ac7bbf8 (includes change to default to > > C++20). > > > > --- 8< --- > > > > > (build_call_a_1): Split out the functionality needed > > for thunk-like calls. > > You can't add a _1 function to cp-tree.h, it needs a real name. > > But it seems like we should be able to avoid splitting build_call_a, see > comments on build_thunk_like_call. > > This is the one issue in this patch that needs to be resolved before merge. > Done. This will require an adjustment in patch 9 to account for new argument to `build_thunk_like_call` > > > (build_over_call): Ensure that we pass the original > > > -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; > > So many hash_maps! > We discussed adding pre and post fn to the contract_decl_map, but that would mean we would store a pre and post fn information for every function with contracts. Having it in separate maps means we only have a pre entry for a function with preconditions and a post entry for a function with post conditions. > > > > +/* 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; > > + > > + if (DECL_IOBJ_MEMBER_FUNCTION_P (fndecl) > > + && !TYPE_METHOD_BASETYPE (TREE_TYPE (fndecl))) > > + return error_mark_node; > > I know this is carried over from the old implementation, but this seems > like an oddly specific error recovery case; can it ever actually occur? > I don’t know what this is checking for, and we do not have any tests that capture it. I will assume it’s not needed. If I’m wrong, we will address it if and when we have a failure. > > > + /* The contract check functions are never a cdtor */ > > + DECL_CXX_DESTRUCTOR_P (fn) = DECL_CXX_CONSTRUCTOR_P (fn) = 0; > > + > > + DECL_NAME (fn) = contracts_fixup_name (DECL_NAME(fndecl), > > + pre, > > + DECL_CXX_CONSTRUCTOR_P (fndecl) > > + || DECL_CXX_DESTRUCTOR_P > (fndecl)); > > + > > + DECL_INITIAL (fn) = NULL_TREE; > > + CONTRACT_HELPER (fn) = pre ? ldf_contract_pre : ldf_contract_post; > > + > > + DECL_VIRTUAL_P (fn) = false; > > + > > + /* These functions should be internal. */ > > ...but for callee checks for a vague linkage fndecl they should be in a > comdat group with it. > Reinstating the old code that did this. It will require a change in the caller side wrappers (patch9 ) to make sure the wrapper is added to a new comdat group with its respective pre and post function > > > + > > + return NULL_TREE; > > +} > > + > > +/* 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)); > > This still won't preserve modifications of scalar return values, right? > > It looks like there are no tests of const_cast modifications of > constified things. > Some tests added. I don't fully understand why we think this will not preserve modifications of scalar return values. I could not trigger a problem where a modification of a scalar return value was not preserved. Can you elaborate, please ?
From 09b0569b6b323d8d8081589ee43fbed637882c41 Mon Sep 17 00:00:00 2001 From: Nina Ranns <[email protected]> Date: Fri, 31 Oct 2025 00:20:43 +0000 Subject: [PATCH] c++, contracts: Allow contract checks as outlined functions. 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. gcc/cp/ChangeLog: * call.cc (build_over_call): Ensure that we pass the original function decl to build_cxx_call. (build_cxx_call): Use the original function decl when available. * 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. * 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: Contract flags documentation. gcc/testsuite/ChangeLog: * g++.dg/contracts/cpp26/empty-nt-param.C: Enable outlined checks. * g++.dg/contracts/cpp26/expr.prim.id.unqual.p7-3.C: New test. * 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. Co-Authored-by: Iain Sandoe <[email protected]> Co-Authored-by: Ville Voutilainen <[email protected]> Signed-off-by: Iain Sandoe <[email protected]> --- gcc/c-family/c.opt | 8 + gcc/cp/call.cc | 12 +- gcc/cp/contracts.cc | 534 +++++++++++++++++- gcc/cp/contracts.h | 26 + gcc/cp/cp-tree.h | 14 +- gcc/cp/decl.cc | 4 + gcc/cp/module.cc | 31 +- gcc/doc/invoke.texi | 17 +- .../g++.dg/contracts/cpp26/empty-nt-param.C | 2 +- .../cpp26/expr.prim.id.unqual.p7-3.C | 56 ++ .../outline-checks/freefunc-noexcept-post.C | 46 ++ .../outline-checks/freefunc-noexcept-pre.C | 46 ++ .../outline-checks/func-noexcept-assert.C | 55 ++ .../outline-checks/memberfunc-noexcept-post.C | 49 ++ .../outline-checks/memberfunc-noexcept-pre.C | 49 ++ 15 files changed, 921 insertions(+), 28 deletions(-) create mode 100644 gcc/testsuite/g++.dg/contracts/cpp26/expr.prim.id.unqual.p7-3.C create mode 100644 gcc/testsuite/g++.dg/contracts/cpp26/outline-checks/freefunc-noexcept-post.C create mode 100644 gcc/testsuite/g++.dg/contracts/cpp26/outline-checks/freefunc-noexcept-pre.C create mode 100644 gcc/testsuite/g++.dg/contracts/cpp26/outline-checks/func-noexcept-assert.C create mode 100644 gcc/testsuite/g++.dg/contracts/cpp26/outline-checks/memberfunc-noexcept-post.C create mode 100644 gcc/testsuite/g++.dg/contracts/cpp26/outline-checks/memberfunc-noexcept-pre.C diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt index 659296046e3..209df89e42b 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -1928,6 +1928,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). diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc index 103e68ae9db..c71fd17f06f 100644 --- a/gcc/cp/call.cc +++ b/gcc/cp/call.cc @@ -45,6 +45,7 @@ along with GCC; see the file COPYING3. If not see #include "c-family/c-type-mismatch.h" #include "tristate.h" #include "tree-pretty-print-markup.h" +#include "contracts.h" /* The various kinds of conversion. */ @@ -5305,9 +5306,7 @@ build_new_function_call (tree fn, vec<tree, va_gc> **args, result = error_mark_node; } else - { - result = build_over_call (cand, LOOKUP_NORMAL, complain); - } + result = build_over_call (cand, LOOKUP_NORMAL, complain); if (flag_coroutines && result @@ -11029,6 +11028,7 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain) && DECL_BUILT_IN_CLASS (fn) == BUILT_IN_NORMAL) maybe_warn_class_memaccess (input_location, fn, args); + tree orig_fn = fn; if (DECL_VINDEX (fn) && (flags & LOOKUP_NONVIRTUAL) == 0) { tree t; @@ -11062,7 +11062,7 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain) ADDR_EXPR_DENOTES_CALL_P (fn) = true; } - tree call = build_cxx_call (fn, nargs, argarray, complain|decltype_flag); + tree call = build_cxx_call (fn, nargs, argarray, complain|decltype_flag, orig_fn); if (call == error_mark_node) return call; if (cand->flags & LOOKUP_LIST_INIT_CTOR) @@ -11534,7 +11534,9 @@ build_cxx_call (tree fn, int nargs, tree *argarray, SET_EXPR_LOCATION (fn, loc); fndecl = get_callee_fndecl (fn); - if (!orig_fndecl) + if (!fndecl && orig_fndecl) + fndecl = orig_fndecl; + else if (!orig_fndecl) orig_fndecl = fndecl; /* Check that arguments to builtin functions match the expectations. */ diff --git a/gcc/cp/contracts.cc b/gcc/cp/contracts.cc index 4008550fb32..6fe9f705233 100644 --- a/gcc/cp/contracts.cc +++ b/gcc/cp/contracts.cc @@ -333,10 +333,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. */ @@ -596,14 +596,260 @@ 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_assert (!decl_pre_fn->get (fndecl)); + decl_pre_fn->put (fndecl, pre); + + DECL_ABSTRACT_ORIGIN (pre) = fndecl; +} + +/* Makes POST the postcondition function for FNDECL. */ + +static void +set_postcondition_function (tree fndecl, tree post) +{ + gcc_assert (post); + hash_map_maybe_create<hm_ggc> (decl_post_fn); + gcc_assert (!decl_post_fn->get (fndecl)); + decl_post_fn->put (fndecl, post); + + DECL_ABSTRACT_ORIGIN (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); + return DECL_ABSTRACT_ORIGIN (fndecl); +} + +/* For a given function decl name identifier, return identifier representing + the name of the contracts check. Using the same identifier is not possible + with functions with special meaning names (i.e. main and cdtors). For + consistency reasons we use the same naming convention for all contract check + functions. + PRE specifies if we need an identifier for a pre or post contract check. + CDTOR specifies if the checked function is a cdtor. */ +static tree +contracts_fixup_name (tree idin, bool pre, bool cdtor) +{ + const char *fname = IDENTIFIER_POINTER (idin); + 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 ? "pre" : "post"); + tree newid = get_identifier (nn); + free (nn); + return newid; +} + +/* 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; + + /* DECL_ASSEMBLER_NAME will be copied from FNDECL if it's already set. Unset + it so fn name later gets mangled according to the rules for pre and post + functions. */ + SET_DECL_ASSEMBLER_NAME (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 optimizations 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. */ + DECL_CXX_DESTRUCTOR_P (fn) = DECL_CXX_CONSTRUCTOR_P (fn) = 0; + + DECL_NAME (fn) = contracts_fixup_name (DECL_NAME (fndecl), + pre, + DECL_CXX_CONSTRUCTOR_P (fndecl) + || DECL_CXX_DESTRUCTOR_P (fndecl)); + + DECL_INITIAL (fn) = NULL_TREE; + CONTRACT_HELPER (fn) = pre ? ldf_contract_pre : ldf_contract_post; + + DECL_VIRTUAL_P (fn) = false; + + /* 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 @@ -615,8 +861,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))) @@ -652,6 +898,135 @@ 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 + */ + +static tree +build_thunk_like_call (tree func, tree caller, int n, tree *argarray) +{ + /* 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. */ + bool old_decl_thunk_p = DECL_THUNK_P (caller); + LANG_DECL_FN_CHECK (caller)->thunk_p = true; + + tree call = build_call_a (func, n, argarray); + + /* Revert the `DECL_THUNK_P` flag. */ + LANG_DECL_FN_CHECK (caller)->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), 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; + + for (tree arg = DECL_ARGUMENTS (post); arg; arg = TREE_CHAIN (arg)) + if (tree_last (DECL_ARGUMENTS (post))) + return arg; + + return NULL_TREE; +} + +/* 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), fndecl, + args->length (), args->address ()); + finish_expr_stmt (call); } /* Allow specifying a sub-set of contract kinds to copy. */ @@ -754,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 @@ -772,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. */ @@ -782,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. @@ -1481,6 +1866,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); + remap_contract (fn, condfn, contract, /*duplicate_p=*/false); + if (!emit_contract_statement (contract)) + continue; + } + } +} + +/* 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. */ @@ -1935,6 +2430,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__ @@ -2141,7 +2639,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); diff --git a/gcc/cp/contracts.h b/gcc/cp/contracts.h index ba5a203321b..ef904e82f19 100644 --- a/gcc/cp/contracts.h +++ b/gcc/cp/contracts.h @@ -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); diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index fcb7ba185a1..1bed89b3569 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -3190,6 +3190,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 { @@ -3219,8 +3226,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. */ @@ -3368,6 +3376,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 @@ -7151,7 +7162,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); diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc index 3bece96b809..af397bb8933 100644 --- a/gcc/cp/decl.cc +++ b/gcc/cp/decl.cc @@ -20704,6 +20704,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; } diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc index df005c062f2..5c8aa40929e 100644 --- a/gcc/cp/module.cc +++ b/gcc/cp/module.cc @@ -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; diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 1621eee4074..efa19ddb80d 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -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 @@ -3337,6 +3338,20 @@ 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 +@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 +@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 @item -fcoroutines Enable support for the C++ coroutines extension. With @option{-std=c++20} diff --git a/gcc/testsuite/g++.dg/contracts/cpp26/empty-nt-param.C b/gcc/testsuite/g++.dg/contracts/cpp26/empty-nt-param.C index 8ac0d41e0f5..ab92144eacd 100644 --- a/gcc/testsuite/g++.dg/contracts/cpp26/empty-nt-param.C +++ b/gcc/testsuite/g++.dg/contracts/cpp26/empty-nt-param.C @@ -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 index 00000000000..711e65c395d --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/cpp26/expr.prim.id.unqual.p7-3.C @@ -0,0 +1,56 @@ +// 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" } + +#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 index 00000000000..48b752bcefa --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/cpp26/outline-checks/freefunc-noexcept-post.C @@ -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 index 00000000000..a662734d62d --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/cpp26/outline-checks/freefunc-noexcept-pre.C @@ -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 index 00000000000..f2bc62c432c --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/cpp26/outline-checks/func-noexcept-assert.C @@ -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 index 00000000000..536f7095f4c --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/cpp26/outline-checks/memberfunc-noexcept-post.C @@ -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 index 00000000000..f629587ae26 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/cpp26/outline-checks/memberfunc-noexcept-pre.C @@ -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; + +} -- 2.50.1
