From: Nina Ranns <[email protected]> Changes since v3
see: https://inbox.sourceware.org/gcc-patches/0b4fe7d5fb28d1cbf84e5c0e10bea39dfd877592.1763334718.git.i...@sandoe.co.uk/T/#t - rebased onto r16-7033-gfb6a2e3f1fa842 (includes reflection changes). Changes since v2 - Addressed Jason's review comments - Addressed Sandra's review comments - 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< --- This a (currently GCC-only) extension that implements caller-side checking of pre and post conditions. It is completely in scope with the C++26 CDIS wording, but is not mandated. The implementation here allows applying caller or callee-side checking independently. gcc/c-family/ChangeLog: * c.opt (fcontracts-definition-check=, fcontracts-client-check=): New. gcc/cp/ChangeLog: * call.cc (build_cxx_call): Where enabled, wrap calls to functions with contract specifiers. * contracts.cc (enum contract_match_kind): Move to contracts header. (build_contract_condition_function): Copy caller-side wrapper state. (set_contract_wrapper_function, get_contract_wrapper_function, get_orig_func_for_wrapper, contracts_fixup_cdtorname, build_contract_wrapper_function, get_or_create_contract_wrapper_function): New. (start_function_contracts): Handle caller-side wrappers. (maybe_apply_function_contracts): Likewise. (copy_and_remap_contracts): Likewise. (should_contract_wrap_call, maybe_contract_wrap_call, define_contract_wrapper_func, emit_contract_wrapper_func): New. (finish_function_contracts): Handle caller-side wrappers. (get_src_loc_impl_ptr): Likewise. * contracts.h (DECL_IS_WRAPPER_FN_P): New. (enum contract_match_kind): Moved from contracts.cc. (copy_and_remap_contracts): Allow selection on the specific contract kind. (maybe_contract_wrap_call, emit_contract_wrapper_func): New. (set_decl_contracts): Delete dead code. * cp-tree.h (struct lang_decl_fn): Add wrapper function bit. (DECL_CONTRACT_WRAPPER): New. * decl2.cc (c_parse_final_cleanups): Emit wrappers. gcc/testsuite/ChangeLog: * g++.dg/contracts/cpp26/callerside-checks/callerside-checks-all.C: New test. * g++.dg/contracts/cpp26/callerside-checks/callerside-checks-non-trivial.C: New test. * g++.dg/contracts/cpp26/callerside-checks/callerside-checks-none.C: New test. * g++.dg/contracts/cpp26/callerside-checks/callerside-checks-pre.C: New test. * g++.dg/contracts/cpp26/callerside-checks/ctor.C: New test. * g++.dg/contracts/cpp26/callerside-checks/freefunc-noexcept-post.C: New test. * g++.dg/contracts/cpp26/callerside-checks/freefunc-noexcept-pre.C: New test. * g++.dg/contracts/cpp26/definition-checks/contract-assert-no-def-check.C: New test. * g++.dg/contracts/cpp26/non-trivial-ice.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 | 29 ++ gcc/cp/call.cc | 8 +- gcc/cp/contracts.cc | 302 +++++++++++++++++- gcc/cp/contracts.h | 22 +- gcc/cp/cp-tree.h | 8 +- gcc/cp/decl2.cc | 11 +- gcc/doc/invoke.texi | 20 ++ .../callerside-checks/callerside-checks-all.C | 52 +++ .../callerside-checks-non-trivial.C | 18 ++ .../callerside-checks-none.C | 64 ++++ .../callerside-checks/callerside-checks-pre.C | 65 ++++ .../contracts/cpp26/callerside-checks/ctor.C | 23 ++ .../freefunc-noexcept-post.C | 49 +++ .../callerside-checks/freefunc-noexcept-pre.C | 49 +++ .../contract-assert-no-def-check.C | 25 ++ .../g++.dg/contracts/cpp26/non-trivial-ice.C | 21 ++ 16 files changed, 740 insertions(+), 26 deletions(-) create mode 100644 gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/callerside-checks-all.C create mode 100644 gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/callerside-checks-non-trivial.C create mode 100644 gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/callerside-checks-none.C create mode 100644 gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/callerside-checks-pre.C create mode 100644 gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/ctor.C create mode 100644 gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/freefunc-noexcept-post.C create mode 100644 gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/freefunc-noexcept-pre.C create mode 100644 gcc/testsuite/g++.dg/contracts/cpp26/definition-checks/contract-assert-no-def-check.C create mode 100644 gcc/testsuite/g++.dg/contracts/cpp26/non-trivial-ice.C diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt index d473377d91b8..1f18e91541b5 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -1938,6 +1938,35 @@ fcontract-disable-optimized-checks C++ ObjC++ Var(flag_contract_disable_optimized_checks) Init(0) -fcontract-disable-optimized-checks Disable optimisation of contract checks. +Enum +Name(client_contract_check) Type(int) UnknownError(unrecognized client contract check option %qs) + +EnumValue +Enum(client_contract_check) String(none) Value(0) + +EnumValue +Enum(client_contract_check) String(pre) Value(1) + +EnumValue +Enum(client_contract_check) String(all) Value(2) + +fcontracts-client-check= +C++ ObjC++ Joined RejectNegative Enum(client_contract_check) Var(flag_contract_client_check) Init (0) +-fcontracts-client-check=[none|pre|all] Select which contracts will be checked on the client side for non virtual functions + +Enum +Name(on_off) Type(int) UnknownError(argument %qs must be either %<on%> or %<off%>) + +EnumValue +Enum(on_off) String(off) Value(0) + +EnumValue +Enum(on_off) String(on) Value(1) + +fcontracts-definition-check= +C++ ObjC++ Joined RejectNegative Enum(on_off) Var(flag_contracts_definition_check) Init(1) +-fcontracts-definition-check=[on|off] Enable or disable contract checks on the definition side for all functions (default on). + fcoroutines C++ ObjC++ LTO Var(flag_coroutines) Enable C++ coroutines (experimental). diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc index 103e68ae9db8..62d25ce6ebed 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" // maybe_contract_wrap_call /* The various kinds of conversion. */ @@ -4932,7 +4933,7 @@ implicit_conversion_error (location_t loc, tree type, tree expr, && !CP_AGGREGATE_TYPE_P (type)) error_at (loc, "designated initializers cannot be used with a " "non-aggregate type %qT", type); - else + else { auto_diagnostic_group d; if (is_stub_object (expr)) @@ -11577,7 +11578,7 @@ build_cxx_call (tree fn, int nargs, tree *argarray, } if (VOID_TYPE_P (TREE_TYPE (fn))) - return fn; + return maybe_contract_wrap_call (fndecl, fn); /* 5.2.2/11: If a function call is a prvalue of object type: if the function call is either the operand of a decltype-specifier or the @@ -11589,6 +11590,7 @@ build_cxx_call (tree fn, int nargs, tree *argarray, fn = require_complete_type (fn, complain); if (fn == error_mark_node) return error_mark_node; + fn = maybe_contract_wrap_call (fndecl, fn); if (MAYBE_CLASS_TYPE_P (TREE_TYPE (fn))) { @@ -11596,6 +11598,8 @@ build_cxx_call (tree fn, int nargs, tree *argarray, maybe_warn_parm_abi (TREE_TYPE (fn), loc); } } + else + fn = maybe_contract_wrap_call (fndecl, fn); return convert_from_reference (fn); } diff --git a/gcc/cp/contracts.cc b/gcc/cp/contracts.cc index a153306b275d..f05f89eddf46 100644 --- a/gcc/cp/contracts.cc +++ b/gcc/cp/contracts.cc @@ -658,12 +658,13 @@ get_orig_for_outlined (tree fndecl) 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) +contracts_fixup_names (tree new_fn, tree old_fn, bool pre, bool wrapper) { 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"; + const char *append = wrapper ? "contract_wrapper" + : (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. */ @@ -671,13 +672,13 @@ contracts_fixup_names (tree new_fn, tree old_fn, bool pre) cdtor ? (int)len-1 : int(len), fname, JOIN_STR, - pre_or_post); + append); 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); + nn = xasprintf ("%s%s%s", fname, JOIN_STR, append); SET_DECL_ASSEMBLER_NAME (new_fn, get_identifier (nn)); free (nn); } @@ -779,10 +780,12 @@ build_contract_condition_function (tree fndecl, bool pre) DECL_VIRTUAL_P (fn) = false; /* Append .pre / .post to a usable name for the original function. */ - contracts_fixup_names (fn, fndecl, pre); + contracts_fixup_names (fn, fndecl, pre, /*wrapper*/false); DECL_INITIAL (fn) = NULL_TREE; CONTRACT_HELPER (fn) = pre ? ldf_contract_pre : ldf_contract_post; + /* We might have a pre/post for a wrapper. */ + DECL_CONTRACT_WRAPPER (fn) = DECL_CONTRACT_WRAPPER (fndecl); /* 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 @@ -858,6 +861,136 @@ build_contract_function_decls (tree fndecl) set_postcondition_function (fndecl, post); } +/* Map from FUNCTION_DECL to a FUNCTION_DECL for contract wrapper. */ + +static GTY(()) hash_map<tree, tree> *decl_wrapper_fn = nullptr; + +/* Map from the function decl of a wrapper to the function that it wraps. */ + +static GTY(()) hash_map<tree, tree> *decl_for_wrapper = nullptr; + +/* Makes wrapper the precondition function for FNDECL. */ + +static void +set_contract_wrapper_function (tree fndecl, tree wrapper) +{ + gcc_checking_assert (wrapper && fndecl); + hash_map_maybe_create<hm_ggc> (decl_wrapper_fn); + gcc_checking_assert (decl_wrapper_fn && !decl_wrapper_fn->get (fndecl)); + decl_wrapper_fn->put (fndecl, wrapper); + + /* We need to know the wrapped function when composing the diagnostic. */ + hash_map_maybe_create<hm_ggc> (decl_for_wrapper); + gcc_checking_assert (decl_for_wrapper && !decl_for_wrapper->get (wrapper)); + decl_for_wrapper->put (wrapper, fndecl); +} + +/* Returns the wrapper function decl for FNDECL, or null if not set. */ + +static tree +get_contract_wrapper_function (tree fndecl) +{ + gcc_checking_assert (fndecl); + tree *result = hash_map_safe_get (decl_wrapper_fn, fndecl); + return result ? *result : NULL_TREE; +} + +/* Given a wrapper function WRAPPER, find the original function decl. */ + +static tree +get_orig_func_for_wrapper (tree wrapper) +{ + gcc_checking_assert (wrapper); + tree *result = hash_map_safe_get (decl_for_wrapper, wrapper); + return result ? *result : NULL_TREE; +} + +/* Build a declaration for the contract wrapper of a caller FNDECL. + We're making a caller side contract check wrapper. For caller side contract + checks, postconditions are only checked if check_post is true. + Defer the attachment of the contracts to this function until the callee + is non-dependent, or we get cases where the conditions can be non-dependent + but still need tsubst-ing. */ + +static tree +build_contract_wrapper_function (tree fndecl) +{ + if (error_operand_p (fndecl)) + return error_mark_node; + + /* We should not be trying to build wrappers for templates or functions that + are still dependent. */ + gcc_checking_assert (!processing_template_decl + && !TYPE_DEPENDENT_P (TREE_TYPE (fndecl))); + + location_t loc = DECL_SOURCE_LOCATION (fndecl); + + /* Fill in the names later. */ + tree wrapdecl + = build_lang_decl_loc (loc, FUNCTION_DECL, NULL_TREE, TREE_TYPE (fndecl)); + + /* Put the wrapper in the same context as the callee. */ + DECL_CONTEXT (wrapdecl) = DECL_CONTEXT (fndecl); + + /* This declaration is a contract wrapper function. */ + DECL_CONTRACT_WRAPPER (wrapdecl) = true; + + contracts_fixup_names (wrapdecl, fndecl, /*pre*/false, /*wrapper*/true); + + DECL_SOURCE_LOCATION (wrapdecl) = loc; + /* The declaration was implicitly generated by the compiler. */ + DECL_ARTIFICIAL (wrapdecl) = true; + /* Declaration, no definition yet. */ + DECL_INITIAL (wrapdecl) = NULL_TREE; + + /* Let the start function code fill in the result decl. */ + DECL_RESULT (wrapdecl) = NULL_TREE; + + /* Copy the function parameters, if present. Suppress (e.g. unused) + warnings on them. */ + DECL_ARGUMENTS (wrapdecl) = NULL_TREE; + if (tree p = DECL_ARGUMENTS (fndecl)) + { + tree *last_a = &DECL_ARGUMENTS (wrapdecl); + for (; p; p = TREE_CHAIN (p)) + { + *last_a = copy_decl (p); + suppress_warning (*last_a); + DECL_CONTEXT (*last_a) = wrapdecl; + last_a = &TREE_CHAIN (*last_a); + } + } + + /* Copy selected attributes from the original function. */ + TREE_USED (wrapdecl) = TREE_USED (fndecl); + + /* Copy any alignment added. */ + if (DECL_ALIGN (fndecl)) + SET_DECL_ALIGN (wrapdecl, DECL_ALIGN (fndecl)); + DECL_USER_ALIGN (wrapdecl) = DECL_USER_ALIGN (fndecl); + + /* Make this function internal. */ + TREE_PUBLIC (wrapdecl) = false; + DECL_EXTERNAL (wrapdecl) = false; + DECL_WEAK (wrapdecl) = false; + + /* We know this is an internal function. */ + DECL_INTERFACE_KNOWN (wrapdecl) = true; + return wrapdecl; +} + +static tree +get_or_create_contract_wrapper_function (tree fndecl) +{ + tree wrapdecl = get_contract_wrapper_function (fndecl); + if (!wrapdecl) + { + wrapdecl = build_contract_wrapper_function (fndecl); + set_contract_wrapper_function (fndecl, wrapdecl); + } + return wrapdecl; +} + void start_function_contracts (tree fndecl) { @@ -867,6 +1000,12 @@ start_function_contracts (tree fndecl) if (!handle_contracts_p (fndecl)) return; + /* If this is not a client side check and definition side checks are + disabled, do nothing. */ + if (!flag_contracts_definition_check + && !DECL_CONTRACT_WRAPPER (fndecl)) + return; + /* 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)) @@ -1032,15 +1171,6 @@ add_post_condition_fn_call (tree fndecl) finish_expr_stmt (call); } -/* Allow specifying a sub-set of contract kinds to copy. */ - -enum contract_match_kind -{ - cmk_pre, - cmk_post, - cmk_all -}; - /* Copy (possibly a sub-set of) contracts from CONTRACTS on FNDECL. */ static tree @@ -1195,6 +1325,12 @@ maybe_apply_function_contracts (tree fndecl) popped by our caller. */ return; + /* If this is not a client side check and definition side checks are + disabled, do nothing. */ + if (!flag_contracts_definition_check + && !DECL_CONTRACT_WRAPPER (fndecl)) + return; + bool do_pre = has_active_preconditions (fndecl); bool do_post = has_active_postconditions (fndecl); /* We should not have reached here with nothing to do... */ @@ -1360,12 +1496,21 @@ remap_contract (tree src, tree dst, tree contract, bool duplicate_p) PARM_DECLs have been rewritten to the corresponding PARM_DECL in DEST. */ tree -copy_and_remap_contracts (tree dest, tree source) +copy_and_remap_contracts (tree dest, tree source, + contract_match_kind remap_kind) { tree last = NULL_TREE, contracts_copy= NULL_TREE; tree contracts = get_fn_contract_specifiers (source); for (; contracts; contracts = TREE_CHAIN (contracts)) { + if ((remap_kind == cmk_pre + && (TREE_CODE (CONTRACT_STATEMENT (contracts)) + == POSTCONDITION_STMT)) + || (remap_kind == cmk_post + && (TREE_CODE (CONTRACT_STATEMENT (contracts)) + == PRECONDITION_STMT))) + continue; + /* The first part is copying of the legacy attribute layout - eventually this will go away. */ tree c = copy_node (contracts); @@ -1603,6 +1748,124 @@ update_contract_arguments (tree srcdecl, tree destdecl) } } +/* Checks if a contract check wrapper is needed for fndecl. */ + +static bool +should_contract_wrap_call (bool do_pre, bool do_post) +{ + /* Only if the target function actually has any contracts. */ + if (!do_pre && !do_post) + return false; + + + return ((flag_contract_client_check > 1) + || ((flag_contract_client_check > 0) + && do_pre)); +} + +/* Possibly replace call with a call to a wrapper function which + will do the contracts check required around a CALL to FNDECL. */ + +tree +maybe_contract_wrap_call (tree fndecl, tree call) +{ + /* We can be called from build_cxx_call without a known callee. */ + if (!fndecl) + return call; + + if (error_operand_p (fndecl) || !call || call == error_mark_node) + return error_mark_node; + + if (!handle_contracts_p (fndecl)) + return call; + + bool do_pre = has_active_preconditions (fndecl); + bool do_post = has_active_postconditions (fndecl); + + /* Check if we need a wrapper. */ + if (!should_contract_wrap_call (do_pre, do_post)) + return call; + + /* Build the declaration of the wrapper, if we need to. */ + tree wrapdecl = get_or_create_contract_wrapper_function (fndecl); + + unsigned nargs = call_expr_nargs (call); + vec<tree, va_gc> *argwrap; + vec_alloc (argwrap, nargs); + + tree arg; + call_expr_arg_iterator iter; + FOR_EACH_CALL_EXPR_ARG (arg, iter, call) + argwrap->quick_push (arg); + + tree wrapcall = build_call_expr_loc_vec (DECL_SOURCE_LOCATION (wrapdecl), + wrapdecl, argwrap); + + return wrapcall; +} + +/* Map traversal callback to define a wrapper function. + This generates code for client-side contract check wrappers and the + noexcept wrapper around the contract violation handler. */ + +bool +define_contract_wrapper_func (const tree& fndecl, const tree& wrapdecl, void*) +{ + /* If we already built this function on a previous pass, then do nothing. */ + if (DECL_INITIAL (wrapdecl) && DECL_INITIAL (wrapdecl) != error_mark_node) + return true; + + gcc_checking_assert (!DECL_HAS_CONTRACTS_P (wrapdecl)); + /* We check postconditions if postcondition checks are enabled for clients. + We should not get here unless there are some checks to make. */ + bool check_post = flag_contract_client_check > 1; + /* For wrappers on CDTORs we need to refer to the original contracts, + when the wrapper is around a clone. */ + set_fn_contract_specifiers ( wrapdecl, + copy_and_remap_contracts (wrapdecl, DECL_ORIGIN (fndecl), + check_post? cmk_all : cmk_pre)); + + start_preparsed_function (wrapdecl, /*DECL_ATTRIBUTES*/NULL_TREE, + SF_DEFAULT | SF_PRE_PARSED); + tree body = begin_function_body (); + tree compound_stmt = begin_compound_stmt (BCS_FN_BODY); + + vec<tree, va_gc> * args = build_arg_list (wrapdecl); + + /* We do not support contracts on virtual functions yet. */ + gcc_checking_assert (!DECL_IOBJ_MEMBER_FUNCTION_P (fndecl) + || !DECL_VIRTUAL_P (fndecl)); + + tree call = build_thunk_like_call (fndecl, args->length (), args->address ()); + + finish_return_stmt (call); + + finish_compound_stmt (compound_stmt); + finish_function_body (body); + expand_or_defer_fn (finish_function (/*inline_p=*/false)); + return true; +} + +/* If any wrapper functions have been declared, emit their definition. + This might be called multiple times, as we instantiate functions. When + the processing here adds more wrappers, then flag to the caller that + possible additional instantiations should be considered. + Once instantiations are complete, this will be called with done == true. */ + +bool +emit_contract_wrapper_func (bool done) +{ + if (!decl_wrapper_fn || decl_wrapper_fn->is_empty ()) + return false; + size_t start_elements = decl_wrapper_fn->elements (); + decl_wrapper_fn->traverse<void *, define_contract_wrapper_func>(NULL); + bool more = decl_wrapper_fn->elements () > start_elements; + if (done) + decl_wrapper_fn->empty (); + gcc_checking_assert (!done || !more); + return more; +} + /* Mark most of a contract as being invalid. */ tree @@ -1951,6 +2214,12 @@ finish_function_outlined_contracts (tree fndecl) || !flag_contract_checks_outlined) return; + /* If this is not a client side check and definition side checks are + disabled, do nothing. */ + if (!flag_contracts_definition_check + && !DECL_CONTRACT_WRAPPER (fndecl)) + 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); @@ -2460,6 +2729,9 @@ get_src_loc_impl_ptr (location_t loc) /* 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); + /* We might be a wrapper. */ + if (DECL_IS_WRAPPER_FN_P (fndecl)) + fndecl = get_orig_func_for_wrapper (fndecl); gcc_checking_assert (fndecl); tree impl__ diff --git a/gcc/cp/contracts.h b/gcc/cp/contracts.h index ef904e82f19d..705de1584f8e 100644 --- a/gcc/cp/contracts.h +++ b/gcc/cp/contracts.h @@ -140,6 +140,18 @@ enum detection_mode : uint16_t { (DECL_DECLARES_FUNCTION_P (NODE) && DECL_LANG_SPECIFIC (NODE) \ && CONTRACT_HELPER (NODE) == ldf_contract_post) +#define DECL_IS_WRAPPER_FN_P(NODE) \ + (DECL_DECLARES_FUNCTION_P (NODE) && DECL_LANG_SPECIFIC (NODE) && \ + DECL_CONTRACT_WRAPPER (NODE)) + +/* Allow specifying a sub-set of contract kinds to copy. */ +enum contract_match_kind +{ + cmk_all, + cmk_pre, + cmk_post +}; + /* contracts.cc */ extern void init_contracts (void); @@ -150,7 +162,7 @@ extern tree finish_contract_condition (cp_expr); extern void update_late_contract (tree, tree, cp_expr); extern void check_redecl_contract (tree, tree); extern tree invalidate_contract (tree); -extern tree copy_and_remap_contracts (tree, tree); +extern tree copy_and_remap_contracts (tree, tree, contract_match_kind = cmk_all); extern tree constify_contract_access (tree); extern tree view_as_const (tree); @@ -180,16 +192,12 @@ extern void maybe_apply_function_contracts (tree); extern void finish_function_outlined_contracts (tree); extern void set_contract_functions (tree, tree, tree); +extern tree maybe_contract_wrap_call (tree, tree); +extern bool emit_contract_wrapper_func (bool); extern void maybe_emit_violation_handler_wrappers (void); extern tree build_contract_check (tree); -inline void -set_decl_contracts (tree decl, tree contract_attrs) -{ - set_fn_contract_specifiers (decl, contract_attrs); -} - /* Test if EXP is a contract const wrapper node. */ inline bool diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index a1f82947d1e7..e17d95184cde 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -3219,9 +3219,10 @@ struct GTY(()) lang_decl_fn { unsigned escalated_p : 1; unsigned xobj_func : 1; + unsigned contract_wrapper : 1; ENUM_BITFIELD(lang_contract_helper) contract_helper : 2; - unsigned spare : 5; + unsigned spare : 4; /* 32-bits padding on 64-bit host. */ @@ -3645,6 +3646,11 @@ struct GTY(()) lang_decl { (TREE_CODE (STRIP_TEMPLATE (NODE)) == FUNCTION_DECL \ && DECL_FUNCTION_XOBJ_FLAG (NODE) == 1) +/* Nonzero for FUNCTION_DECL means that this decl is a contract + wrapper function. */ +#define DECL_CONTRACT_WRAPPER(NODE) \ + LANG_DECL_FN_CHECK (NODE)->contract_wrapper + /* Nonzero if NODE is a member function with an object argument, in other words, a non-static member function. */ #define DECL_OBJECT_MEMBER_FUNCTION_P(NODE) \ diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc index 50b4857793a3..20ee662eea22 100644 --- a/gcc/cp/decl2.cc +++ b/gcc/cp/decl2.cc @@ -5893,6 +5893,12 @@ c_parse_final_cleanups (void) importer. */ continue; + /* Emit wrappers where needed, and if that causes more to be added then + make sure we account for possible additional instantiations. */ + if (flag_contracts) + if (emit_contract_wrapper_func (/*done*/false)) + reconsider = true; + /* Write out virtual tables as required. Writing out the virtual table for a template class may cause the instantiation of members of that class. If we write out @@ -6106,7 +6112,10 @@ c_parse_final_cleanups (void) } if (flag_contracts) - maybe_emit_violation_handler_wrappers (); + { + emit_contract_wrapper_func (/*done*/true); + maybe_emit_violation_handler_wrappers (); + } /* All templates have been instantiated. */ at_eof = 2; diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 727549a72669..49fa503ddeae 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -228,6 +228,8 @@ in the following sections. -fcontract-evaluation-semantic=@r{[}ignore@r{|}observe@r{|}enforce@r{|}quick_enforce@r{]} -fcontracts-conservative-ipa -fcontract-checks-outlined -fcontract-disable-optimized-checks +-fcontracts-client-check=@r{[}none@r{|}pre@r{|}all@r{]} +-fcontracts-definition-check=@r{[}on@r{|}off@r{]} -fcoroutines -fdiagnostics-all-candidates -fno-elide-constructors -fno-enforce-eh-specs @@ -3376,6 +3378,24 @@ 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 fcontracts-client-check +@item -fcontracts-client-check=@var{mode} +This option enables insertion of checks at call sites (caller-side checking). + +The value of @var{mode} determines which contract checks are made: +'@code{none}' No caller/client side checking (default) + +'@code{pre}' Preconditions are checked at the caller/client side. + +'@code{all}' Both pre and post-conditions are checked at the caller/client side. + +@opindex fcontracts-definition-check +@item -fcontracts-definition-check=@var{mode} +This option introduces the ability to disable callee/definition-side checks, +which are otherwise enabled by default when the contracts feature is active. + +The variable @var{mode} has the values '@code{on}' and '@code{off}'. + @opindex fcoroutines @opindex fno-coroutines @item -fcoroutines diff --git a/gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/callerside-checks-all.C b/gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/callerside-checks-all.C new file mode 100644 index 000000000000..07c09321c406 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/callerside-checks-all.C @@ -0,0 +1,52 @@ +// { dg-do run { target c++20 } } +// { dg-additional-options "-fcontracts -fcontract-evaluation-semantic=observe -fcontracts-client-check=all" } + + + +int f(const int a, const int b) pre (a > 2) post(r : r > 2){ return b; } + + +struct S +{ + int f(const int a, const int b) pre (a > 3) post(r : r > 3){ return b; } +}; + +template<typename T> +struct TS +{ + int f(const int a, const T b) pre (a > 4) post(r : r > 4){ return b; } + + template <typename U> + int tf(const int a, const U b) pre (a > 5) post(r : r > 5){ return b; } +}; + +int main(int, char**) +{ + f(1,1); + + S s; + s.f(1,1); + + TS<int> ts; + ts.f(1,1); + + ts.tf(1,1); + return 0; +} + +// { dg-output "contract violation in function int f.int, int. at .*: a > 2.*(\n|\r\n|\r)" } +// { dg-output "contract violation in function int f.int, int. at .*: a > 2.*(\n|\r\n|\r)" } +// { dg-output "contract violation in function int f.int, int. at .*: r > 2.*(\n|\r\n|\r)" } +// { dg-output "contract violation in function int f.int, int. at .*: r > 2.*(\n|\r\n|\r)" } +// { dg-output "contract violation in function int S::f.int, int. at .*: a > 3.*(\n|\r\n|\r)" } +// { dg-output "contract violation in function int S::f.int, int. at .*: a > 3.*(\n|\r\n|\r)" } +// { dg-output "contract violation in function int S::f.int, int. at .*: r > 3.*(\n|\r\n|\r)" } +// { dg-output "contract violation in function int S::f.int, int. at .*: r > 3.*(\n|\r\n|\r)" } +// { dg-output "contract violation in function int TS<T>::f.int, T. .with T = int. at .*: a > 4.*(\n|\r\n|\r)" } +// { dg-output "contract violation in function int TS<T>::f.int, T. .with T = int. at .*: a > 4.*(\n|\r\n|\r)" } +// { dg-output "contract violation in function int TS<T>::f.int, T. .with T = int. at .*: r > 4.*(\n|\r\n|\r)" } +// { dg-output "contract violation in function int TS<T>::f.int, T. .with T = int. at .*: r > 4.*(\n|\r\n|\r)" } +// { dg-output "contract violation in function int TS<T>::tf.int, U. .with U = int; T = int. at .*: a > 5.*(\n|\r\n|\r)" } +// { dg-output "contract violation in function int TS<T>::tf.int, U. .with U = int; T = int. at .*: a > 5.*(\n|\r\n|\r)" } +// { dg-output "contract violation in function int TS<T>::tf.int, U. .with U = int; T = int. at .*: r > 5.*(\n|\r\n|\r)" } +// { dg-output "contract violation in function int TS<T>::tf.int, U. .with U = int; T = int. at .*: r > 5.*(\n|\r\n|\r)" } diff --git a/gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/callerside-checks-non-trivial.C b/gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/callerside-checks-non-trivial.C new file mode 100644 index 000000000000..5ea8b61e7d36 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/callerside-checks-non-trivial.C @@ -0,0 +1,18 @@ +// { dg-do compile { target c++20 } } +// { dg-additional-options "-fcontracts -fcontract-evaluation-semantic=observe -fcontracts-client-check=all" } + + +struct S{ + S(){}; + S(const S&){} + ~S(){}; + int x = 0; +}; + +void f(S s) pre(s.x == 1 ) {}; + +int main() +{ + S s; + f(s); +} diff --git a/gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/callerside-checks-none.C b/gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/callerside-checks-none.C new file mode 100644 index 000000000000..22e0ded4241c --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/callerside-checks-none.C @@ -0,0 +1,64 @@ +// { dg-do run { target c++26 } } +// { dg-additional-options "-fcontracts -fcontracts-client-check=none" } +static int pre_check = 0; +bool fpre() +{ + pre_check++; + return true; +} + +static int post_check = 0; +bool fpost() +{ + post_check++; + return true; +} + + +int f(const int a, const int b) pre (fpre()) post(fpost()){ return b; } + + +struct S +{ + int f(const int a, const int b) post(fpost()){ return b; } +}; + +template<typename T> +struct TS +{ + int f(const int a, const T b) pre (fpre()) { return b; } + + template <typename U> + int tf(const int a, const U b) pre (fpre()) post(fpost()){ return b; } +}; + +int main(int, char**) +{ + f(1,1); + contract_assert(pre_check == 1); + contract_assert(post_check == 1); + + pre_check = 0; + post_check = 0; + + S s; + s.f(1,1); + contract_assert(post_check == 1); + + pre_check = 0; + post_check = 0; + + TS<int> ts; + ts.f(1,1); + contract_assert(pre_check == 1); + + pre_check = 0; + post_check = 0; + + ts.tf(1,1); + contract_assert(pre_check == 1); + contract_assert(post_check == 1); + + + return 0; +} diff --git a/gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/callerside-checks-pre.C b/gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/callerside-checks-pre.C new file mode 100644 index 000000000000..1a3a693e3ab2 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/callerside-checks-pre.C @@ -0,0 +1,65 @@ +// { dg-do run { target c++26 } } +// { dg-additional-options "-fcontracts -fcontracts-client-check=pre" } + +static int pre_check = 0; +bool fpre() +{ + pre_check++; + return true; +} + +static int post_check = 0; +bool fpost() +{ + post_check++; + return true; +} + + +int f(const int a, const int b) pre (fpre()) post(fpost()){ return b; } + + +struct S +{ + int f(const int a, const int b) post(fpost()){ return b; } +}; + +template<typename T> +struct TS +{ + int f(const int a, const T b) pre (fpre()) { return b; } + + template <typename U> + int tf(const int a, const U b) pre (fpre()) post(fpost()){ return b; } +}; + +int main(int, char**) +{ + f(1,1); + contract_assert(pre_check == 2); + contract_assert(post_check == 1); + + pre_check = 0; + post_check = 0; + + S s; + s.f(1,1); + contract_assert(post_check == 1); + + pre_check = 0; + post_check = 0; + + TS<int> ts; + ts.f(1,1); + contract_assert(pre_check == 2); + + pre_check = 0; + post_check = 0; + + ts.tf(1,1); + contract_assert(pre_check == 2); + contract_assert(post_check == 1); + + + return 0; +} diff --git a/gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/ctor.C b/gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/ctor.C new file mode 100644 index 000000000000..3aff02766017 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/ctor.C @@ -0,0 +1,23 @@ +// { dg-do compile { target c++20 } } +// { dg-additional-options "-fcontracts -fcontracts-client-check=pre" } + +struct X +{ + X (int x) noexcept + pre (x>1) + { + try { + int i = 1; + } + catch(...) { + } + } +}; + +int main() +{ + try { + X x(-42); + } catch (...) { + } +} diff --git a/gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/freefunc-noexcept-post.C b/gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/freefunc-noexcept-post.C new file mode 100644 index 000000000000..99c080f52db2 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/freefunc-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 +// with caller side checks. +// { dg-do run { target c++26 } } +// { dg-additional-options "-fcontracts -fcontract-evaluation-semantic=observe -fcontracts-client-check=all " } + +#include <contracts> +#include <iostream> +#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 >= 0) +{ + 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/callerside-checks/freefunc-noexcept-pre.C b/gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/freefunc-noexcept-pre.C new file mode 100644 index 000000000000..7e3968b453bb --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/freefunc-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 post condition on a member function +// with caller side checks. +// { dg-do run { target c++26 } } +// { dg-additional-options "-fcontracts -fcontract-evaluation-semantic=observe -fcontracts-client-check=all " } + +#include <contracts> +#include <iostream> +#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 >= 0) +{ + 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/definition-checks/contract-assert-no-def-check.C b/gcc/testsuite/g++.dg/contracts/cpp26/definition-checks/contract-assert-no-def-check.C new file mode 100644 index 000000000000..74d743b38e1a --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/cpp26/definition-checks/contract-assert-no-def-check.C @@ -0,0 +1,25 @@ +// Check that contract asserts are checked when the definition side contracts +// are turned off +// { dg-do run { target c++26 } } +// { dg-options "-fcontracts -fcontracts-definition-check=off -fcontract-evaluation-semantic=observe" } + +#include <cstdlib> + +bool terminating_check(){ + std::exit(-1); + return true; +} +// pre and post check would cause termination +void foo(int i) noexcept pre(terminating_check()) post(terminating_check()) { + + contract_assert(i > 4); + +} + +int main(int, char**) +{ + + foo(1); + return 0; +} +// { dg-output "contract violation in function void foo.int. at .*: i > 4.*(\n|\r\n|\r)" } diff --git a/gcc/testsuite/g++.dg/contracts/cpp26/non-trivial-ice.C b/gcc/testsuite/g++.dg/contracts/cpp26/non-trivial-ice.C new file mode 100644 index 000000000000..6fd8a2f7a0c5 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/cpp26/non-trivial-ice.C @@ -0,0 +1,21 @@ +// Test that there is no ICE with outlined contracts, caller side checks and +// Nontrivial types in inlined precondition checks +// { dg-do compile { target c++23 } } +// { dg-options "-fcontracts -fcontracts-client-check=all" } +struct NonTrivial{ + NonTrivial(){}; + NonTrivial(const NonTrivial&){} + ~NonTrivial(){}; + int x = 0; +}; + +void f(const NonTrivial s) pre(s.x >0); + +void f(const NonTrivial g) {}; + + +int main() +{ + NonTrivial nt; + f(nt); +} -- 2.50.1 (Apple Git-155)
