From: Nina Ranns <[email protected]> Changes since v3
Address Jason's review comments see : https://inbox.sourceware.org/gcc-patches/[email protected]/T/#t - rebased onto r16-7033-gfb6a2e3f1fa842 (includes reflection changes). Changes since v2 - Addressed Sandra's review comments - renamed interfaces to reflect that contracts are no longer attributes. - rebased onto r16-6757-g460edeb8bea11e (includes new year (c) change). 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< --- 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_call_a): Use the split out builder. (build_call_a_1): Split out the functionality needed for thunk-like calls. (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. (mangle_decl_string): New. * decl.cc (finish_function): Emit outlined checks when in use. * mangle.cc (mangle_decl_string): Made public. (write_mangled_name): Handle outlined contract checks. * 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/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. 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/contracts.cc | 543 +++++++++++++++++- 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 | 55 ++ .../cpp26/expr.prim.id.unqual.p7-4.C | 57 ++ .../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, 975 insertions(+), 27 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/expr.prim.id.unqual.p7-4.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 4d1aa2384c8c..d473377d91b8 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -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). diff --git a/gcc/cp/contracts.cc b/gcc/cp/contracts.cc index cae1be9d991f..a153306b275d 100644 --- a/gcc/cp/contracts.cc +++ b/gcc/cp/contracts.cc @@ -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,133 @@ 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; + + 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), + args->length (), args->address ()); + finish_expr_stmt (call); } /* Allow specifying a sub-set of contract kinds to copy. */ @@ -756,7 +1132,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 +1150,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 +1165,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 +1876,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 +2457,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 +2654,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 ba5a203321b2..ef904e82f19d 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 d9bce876ab78..a1f82947d1e7 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -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); diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc index 3bece96b8099..af397bb8933c 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 df005c062f2b..5c8aa40929ec 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 4c25f66322f0..727549a72669 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 @@ -3361,6 +3362,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 @opindex fno-coroutines @item -fcoroutines 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 8ac0d41e0f51..ab92144eacda 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 000000000000..93ab26e13a44 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/cpp26/expr.prim.id.unqual.p7-3.C @@ -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 index 000000000000..c9cdbe517020 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/cpp26/expr.prim.id.unqual.p7-4.C @@ -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 index 000000000000..48b752bcefa0 --- /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 000000000000..a662734d62d9 --- /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 000000000000..f2bc62c432cb --- /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 000000000000..536f7095f4c4 --- /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 000000000000..f629587ae265 --- /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 (Apple Git-155)
