/* Definitions for C++ contract levels Copyright (C) 2020-2024 Free Software Foundation, Inc. Contributed by Jeff Chapman II (jchapman@lock3software.com) This file is part of GCC. GCC is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GCC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GCC; see the file COPYING3. If not see . */ /* Design Notes A function is called a "guarded" function if it has pre or post contract attributes. A contract is considered an "active" contract if runtime code is needed for the contract under the current contract configuration. pre and post contract attributes are parsed and stored in DECL_ATTRIBUTES. assert contracts are parsed and wrapped in statements. When genericizing, all active and assumed contracts are transformed into an if block. An observed contract: [[ pre: v > 0 ]] is transformed into: if (!(v > 0)) { handle_contract_violation(__pseudo_contract_violation{ 5, // line_number, "main.cpp", // file_name, "fun", // function_name, "v > 0", // comment, "default", // assertion_level, "default", // assertion_role, maybe_continue, // continuation_mode }); terminate (); // if never_continue } We use an internal type with the same layout as contract_violation rather than try to define the latter internally and somehow deal with its actual definition in a TU that includes . ??? is it worth factoring out the calls to handle_contract_violation and terminate into a local function? Assumed contracts use the same implementation as C++23 [[assume]]. Parsing of pre and post contract conditions need to be deferred when the contracts are attached to a member function. The postcondition identifier cannot be used before the deduced return type of an auto function is used, except when used in a defining declaration in which case they conditions are fully parsed once the body is finished (see cpp2a/contracts-deduced{1,2}.C). A list of pre and post contracts can either be repeated in their entirety or completely absent in subsequent declarations. If contract lists appear on two matching declarations, their contracts have to be equivalent. In general this means that anything before the colon have to be token equivalent and the condition must be cp_tree_equal (primarily to allow for parameter renaming). Contracts on overrides must match those present on (all of) the overridee(s). Template specializations may have their own contracts. If no contracts are specified on the initial specialization they're assumed to be the same as the primary template. Specialization redeclarations must then match either the primary template (if they were unspecified originally), or those specified on the specialization. For non-cdtors two functions are generated for ease of implementation and to avoid some cases where code bloat may occurr. These are the DECL_PRE_FN and DECL_POST_FN. Each handles checking either the set of pre or post contracts of a guarded function. int fun(int v) [[ pre: v > 0 ]] [[ post r: r < 0 ]] { return -v; } The original decl is left alone and instead calls are generated to pre/post functions within the body: void fun.pre(int v) { [[ assert: v > 0 ]]; } int fun.post(int v, int __r) { [[ assert: __r < 0 ]]; return __r; } int fun(int v) { fun.pre(v); return fun.post(v, -v); } If fun returns in memory, the return value is not passed through the post function; instead, the return object is initialized directly and then passed to the post function by invisible reference. This sides steps a number of issues with having to rewrite the bodies or rewrite the parsed conditions as the parameters to the original function changes (as happens during redeclaration). The ultimate goal is to get something that optimizes well along the lines of int fun(int v) { [[ assert: v > 0 ]]; auto &&__r = -v; goto out; out: [[ assert: __r < 0 ]]; return __r; } With the idea being that multiple return statements could collapse the function epilogue after inlining the pre/post functions. clang is able to collapse common function epilogues, while gcc needs -O3 -Os combined. Directly laying the pre contracts down in the function body doesn't have many issues. The post contracts may need to be repeated multiple times, once for each return, or a goto epilogue would need to be generated. For this initial implementation, generating function calls and letting later optimizations decide whether to inline and duplicate the actual checks or whether to collapse the shared epilogue was chosen. For cdtors a post contract is implemented using a CLEANUP_STMT. FIXME the compiler already shores cleanup code on multiple exit paths, so this outlining seems unnecessary if we represent the postcondition as a cleanup for all functions. More helpful for optimization might be to make the contracts a wrapper function (for non-variadic functions), that could be inlined into a caller while preserving the call to the actual function? Either that or mirror a never-continue post contract with an assume in the caller. */ #include "config.h" #include "system.h" #include "coretypes.h" #include "cp-tree.h" #include "stringpool.h" #include "diagnostic.h" #include "options.h" #include "contracts.h" #include "tree.h" #include "tree-inline.h" #include "attribs.h" #include "tree-iterator.h" #include "print-tree.h" #include "stor-layout.h" #include "intl.h" const int max_custom_roles = 32; static contract_role contract_build_roles[max_custom_roles] = { }; bool valid_configs[CCS_MAYBE + 1][CCS_MAYBE + 1] = { { 0, 0, 0, 0, 0, }, { 0, 1, 0, 0, 0, }, { 0, 1, 1, 1, 1, }, { 0, 1, 1, 1, 1, }, { 0, 1, 0, 0, 1, }, }; void validate_contract_role (contract_role *role) { gcc_assert (role); if (!unchecked_contract_p (role->axiom_semantic)) error ("axiom contract semantic must be % or %"); if (!valid_configs[role->default_semantic][role->audit_semantic] ) warning (0, "the % semantic should be at least as strong as " "the % semantic"); } contract_semantic lookup_concrete_semantic (const char *name) { if (strcmp (name, "ignore") == 0) return CCS_IGNORE; if (strcmp (name, "assume") == 0) return CCS_ASSUME; if (strcmp (name, "check_never_continue") == 0 || strcmp (name, "never") == 0 || strcmp (name, "abort") == 0) return CCS_NEVER; if (strcmp (name, "check_maybe_continue") == 0 || strcmp (name, "maybe") == 0) return CCS_MAYBE; error ("'%s' is not a valid explicit concrete semantic", name); return CCS_INVALID; } /* Compare role and name up to either the NUL terminator or the first occurrence of colon. */ static bool role_name_equal (const char *role, const char *name) { size_t role_len = strcspn (role, ":"); size_t name_len = strcspn (name, ":"); if (role_len != name_len) return false; return strncmp (role, name, role_len) == 0; } static bool role_name_equal (contract_role *role, const char *name) { if (role->name == NULL) return false; return role_name_equal (role->name, name); } contract_role * get_contract_role (const char *name) { for (int i = 0; i < max_custom_roles; ++i) { contract_role *potential = contract_build_roles + i; if (role_name_equal (potential, name)) return potential; } if (role_name_equal (name, "default") || role_name_equal (name, "review")) { setup_default_contract_role (false); return get_contract_role (name); } return NULL; } contract_role * add_contract_role (const char *name, contract_semantic des, contract_semantic aus, contract_semantic axs, bool update) { for (int i = 0; i < max_custom_roles; ++i) { contract_role *potential = contract_build_roles + i; if (potential->name != NULL && !role_name_equal (potential, name)) continue; if (potential->name != NULL && !update) return potential; potential->name = name; potential->default_semantic = des; potential->audit_semantic = aus; potential->axiom_semantic = axs; return potential; } return NULL; } enum contract_build_level { OFF, DEFAULT, AUDIT }; static bool flag_contract_continuation_mode = false; static bool flag_contract_assumption_mode = true; static int flag_contract_build_level = DEFAULT; static bool contracts_p1332_default = false, contracts_p1332_review = false, contracts_std = false, contracts_p1429 = false; static contract_semantic get_concrete_check () { return flag_contract_continuation_mode ? CCS_MAYBE : CCS_NEVER; } static contract_semantic get_concrete_axiom_semantic () { return flag_contract_assumption_mode ? CCS_ASSUME : CCS_IGNORE; } void setup_default_contract_role (bool update) { contract_semantic check = get_concrete_check (); contract_semantic axiom = get_concrete_axiom_semantic (); switch (flag_contract_build_level) { case OFF: add_contract_role ("default", CCS_IGNORE, CCS_IGNORE, axiom, update); add_contract_role ("review", CCS_IGNORE, CCS_IGNORE, CCS_IGNORE, update); break; case DEFAULT: add_contract_role ("default", check, CCS_IGNORE, axiom, update); add_contract_role ("review", check, CCS_IGNORE, CCS_IGNORE, update); break; case AUDIT: add_contract_role ("default", check, check, axiom, update); add_contract_role ("review", check, check, CCS_IGNORE, update); break; } } contract_semantic map_contract_semantic (const char *ident) { if (strcmp (ident, "ignore") == 0) return CCS_IGNORE; else if (strcmp (ident, "assume") == 0) return CCS_ASSUME; else if (strcmp (ident, "check_never_continue") == 0) return CCS_NEVER; else if (strcmp (ident, "check_maybe_continue") == 0) return CCS_MAYBE; return CCS_INVALID; } contract_level map_contract_level (const char *ident) { if (strcmp (ident, "default") == 0) return CONTRACT_DEFAULT; else if (strcmp (ident, "audit") == 0) return CONTRACT_AUDIT; else if (strcmp (ident, "axiom") == 0) return CONTRACT_AXIOM; return CONTRACT_INVALID; } void handle_OPT_fcontract_build_level_ (const char *arg) { if (contracts_p1332_default || contracts_p1332_review || contracts_p1429) { error ("%<-fcontract-build-level=%> cannot be mixed with p1332/p1429"); return; } else contracts_std = true; if (strcmp (arg, "off") == 0) flag_contract_build_level = OFF; else if (strcmp (arg, "default") == 0) flag_contract_build_level = DEFAULT; else if (strcmp (arg, "audit") == 0) flag_contract_build_level = AUDIT; else error ("%<-fcontract-build-level=%> must be off|default|audit"); setup_default_contract_role (); } void handle_OPT_fcontract_assumption_mode_ (const char *arg) { if (contracts_p1332_default || contracts_p1332_review || contracts_p1429) { error ("%<-fcontract-assumption-mode=%> cannot be mixed with p1332/p1429"); return; } else contracts_std = true; if (strcmp (arg, "on") == 0) flag_contract_assumption_mode = true; else if (strcmp (arg, "off") == 0) flag_contract_assumption_mode = false; else error ("%<-fcontract-assumption-mode=%> must be % or %"); setup_default_contract_role (); } void handle_OPT_fcontract_continuation_mode_ (const char *arg) { if (contracts_p1332_default || contracts_p1332_review || contracts_p1429) { error ("%<-fcontract-continuation-mode=%> cannot be mixed with p1332/p1429"); return; } else contracts_std = true; if (strcmp (arg, "on") == 0) flag_contract_continuation_mode = true; else if (strcmp (arg, "off") == 0) flag_contract_continuation_mode = false; else error ("%<-fcontract-continuation-mode=%> must be % or %"); setup_default_contract_role (); } void handle_OPT_fcontract_role_ (const char *arg) { const char *name = arg; const char *vals = strchr (name, ':'); if (vals == NULL) { error ("%<-fcontract-role=%> must be in the form role:semantics"); return; } contract_semantic dess = CCS_INVALID, auss = CCS_INVALID, axss = CCS_INVALID; char *des = NULL, *aus = NULL, *axs = NULL; des = xstrdup (vals + 1); aus = strchr (des, ','); if (aus == NULL) { error ("%<-fcontract-role=%> semantics must include default,audit,axiom values"); goto validate; } *aus = '\0'; // null terminate des aus = aus + 1; // move past null axs = strchr (aus, ','); if (axs == NULL) { error ("%<-fcontract-role=%> semantics must include default,audit,axiom values"); goto validate; } *axs = '\0'; // null terminate aus axs = axs + 1; // move past null dess = lookup_concrete_semantic (des); auss = lookup_concrete_semantic (aus); axss = lookup_concrete_semantic (axs); validate: free (des); if (dess == CCS_INVALID || auss == CCS_INVALID || axss == CCS_INVALID) return; bool is_defalult_role = role_name_equal (name, "default"); bool is_review_role = role_name_equal (name, "review"); bool is_std_role = is_defalult_role || is_review_role; if ((contracts_std && is_std_role) || (contracts_p1429 && is_defalult_role)) { error ("%<-fcontract-role=%> cannot be mixed with std/p1429 contract flags"); return; } else if (is_std_role) { contracts_p1332_default |= is_defalult_role; contracts_p1332_review |= is_review_role; } contract_role *role = add_contract_role (name, dess, auss, axss); if (role == NULL) { // TODO: not enough space? error ("%<-fcontract-level=%> too many custom roles"); return; } else validate_contract_role (role); } void handle_OPT_fcontract_semantic_ (const char *arg) { if (!strchr (arg, ':')) { error ("%<-fcontract-semantic=%> must be in the form level:semantic"); return; } if (contracts_std || contracts_p1332_default) { error ("%<-fcontract-semantic=%> cannot be mixed with std/p1332 contract flags"); return; } contracts_p1429 = true; contract_role *role = get_contract_role ("default"); if (!role) { error ("%<-fcontract-semantic=%> cannot find default role"); return; } const char *semantic = strchr (arg, ':') + 1; contract_semantic sem = lookup_concrete_semantic (semantic); if (sem == CCS_INVALID) return; if (strncmp ("default:", arg, 8) == 0) role->default_semantic = sem; else if (strncmp ("audit:", arg, 6) == 0) role->audit_semantic = sem; else if (strncmp ("axiom:", arg, 6) == 0) role->axiom_semantic = sem; else error ("%<-fcontract-semantic=%> level must be default, audit, or axiom"); validate_contract_role (role); } /* Convert a contract CONFIG into a contract_mode. */ static contract_mode contract_config_to_mode (tree config) { if (config == NULL_TREE) return contract_mode (CONTRACT_DEFAULT, get_default_contract_role ()); /* TREE_LIST has TREE_VALUE is a level and TREE_PURPOSE is role. */ if (TREE_CODE (config) == TREE_LIST) { contract_role *role = NULL; if (TREE_PURPOSE (config)) role = get_contract_role (IDENTIFIER_POINTER (TREE_PURPOSE (config))); if (!role) role = get_default_contract_role (); contract_level level = map_contract_level (IDENTIFIER_POINTER (TREE_VALUE (config))); return contract_mode (level, role); } /* Literal semantic. */ gcc_assert (TREE_CODE (config) == IDENTIFIER_NODE); contract_semantic semantic = map_contract_semantic (IDENTIFIER_POINTER (config)); return contract_mode (semantic); } /* Convert a contract's config into a concrete semantic using the current contract semantic mapping. */ static contract_semantic compute_concrete_semantic (tree contract) { contract_mode mode = contract_config_to_mode (CONTRACT_MODE (contract)); /* Compute the concrete semantic for the contract. */ if (!flag_contract_mode) /* If contracts are off, treat all contracts as ignore. */ return CCS_IGNORE; else if (mode.kind == contract_mode::cm_invalid) return CCS_INVALID; else if (mode.kind == contract_mode::cm_explicit) return mode.get_semantic (); else { gcc_assert (mode.get_role ()); gcc_assert (mode.get_level () != CONTRACT_INVALID); contract_level level = mode.get_level (); contract_role *role = mode.get_role (); if (level == CONTRACT_DEFAULT) return role->default_semantic; else if (level == CONTRACT_AUDIT) return role->audit_semantic; else if (level == CONTRACT_AXIOM) return role->axiom_semantic; } gcc_assert (false); } /* Return true if any contract in CONTRACT_ATTRs is not yet parsed. */ bool contract_any_deferred_p (tree contract_attr) { for (; contract_attr; contract_attr = CONTRACT_CHAIN (contract_attr)) if (CONTRACT_CONDITION_DEFERRED_P (CONTRACT_STATEMENT (contract_attr))) return true; return false; } /* Returns true if all attributes are contracts. */ bool all_attributes_are_contracts_p (tree attributes) { for (; attributes; attributes = TREE_CHAIN (attributes)) if (!cxx_contract_attribute_p (attributes)) return false; return true; } /* Mark most of a contract as being invalid. */ tree invalidate_contract (tree t) { if (TREE_CODE (t) == POSTCONDITION_STMT && POSTCONDITION_IDENTIFIER (t)) POSTCONDITION_IDENTIFIER (t) = error_mark_node; CONTRACT_CONDITION (t) = error_mark_node; CONTRACT_COMMENT (t) = error_mark_node; return t; } /* Returns an invented parameter declration of the form 'TYPE ID' for the purpose of parsing the postcondition. We use a PARM_DECL instead of a VAR_DECL so that tsubst forces a lookup in local specializations when we instantiate these things later. */ tree make_postcondition_variable (cp_expr id, tree type) { if (id == error_mark_node) return id; tree decl = build_lang_decl (PARM_DECL, id, type); DECL_ARTIFICIAL (decl) = true; DECL_SOURCE_LOCATION (decl) = id.get_location (); pushdecl (decl); return decl; } /* As above, except that the type is unknown. */ tree make_postcondition_variable (cp_expr id) { return make_postcondition_variable (id, make_auto ()); } /* Check that the TYPE is valid for a named postcondition variable. Emit a diagnostic if it is not. Returns TRUE if the result is OK and false otherwise. */ bool check_postcondition_result (tree decl, tree type, location_t loc) { /* Do not be confused by targetm.cxx.cdtor_return_this (); conceptually, cdtors have no return value. */ if (VOID_TYPE_P (type) || DECL_CONSTRUCTOR_P (decl) || DECL_DESTRUCTOR_P (decl)) { error_at (loc, DECL_CONSTRUCTOR_P (decl) ? G_("constructor does not return a value to test") : DECL_DESTRUCTOR_P (decl) ? G_("destructor does not return a value to test") : G_("function does not return a value to test")); return false; } return true; } /* Instantiate each postcondition with the return type to finalize the attribute. */ void rebuild_postconditions (tree decl) { tree type = TREE_TYPE (TREE_TYPE (decl)); tree attributes = DECL_CONTRACTS (decl); for (; attributes ; attributes = TREE_CHAIN (attributes)) { if (!cxx_contract_attribute_p (attributes)) continue; tree contract = TREE_VALUE (TREE_VALUE (attributes)); if (TREE_CODE (contract) != POSTCONDITION_STMT) continue; tree condition = CONTRACT_CONDITION (contract); /* If any conditions are deferred, they're all deferred. Note that we don't have to instantiate postconditions in that case because the type is available through the declaration. */ if (TREE_CODE (condition) == DEFERRED_PARSE) return; tree oldvar = POSTCONDITION_IDENTIFIER (contract); if (!oldvar) continue; /* Always update the context of the result variable so that it can be remapped by remap_contracts. */ DECL_CONTEXT (oldvar) = decl; /* If the return type is undeduced, defer until later. */ if (TREE_CODE (type) == TEMPLATE_TYPE_PARM) return; /* Check the postcondition variable. */ location_t loc = DECL_SOURCE_LOCATION (oldvar); if (!check_postcondition_result (decl, type, loc)) { invalidate_contract (contract); continue; } /* "Instantiate" the result variable using the known type. Also update the context so the inliner will actually remap this the parameter when generating contract checks. */ tree newvar = copy_node (oldvar); TREE_TYPE (newvar) = type; /* Make parameters and result available for substitution. */ local_specialization_stack stack (lss_copy); for (tree t = DECL_ARGUMENTS (decl); t != NULL_TREE; t = TREE_CHAIN (t)) register_local_identity (t); register_local_specialization (newvar, oldvar); ++processing_contract_condition; condition = tsubst_expr (condition, make_tree_vec (0), tf_warning_or_error, decl); --processing_contract_condition; /* Update the contract condition and result. */ POSTCONDITION_IDENTIFIER (contract) = newvar; CONTRACT_CONDITION (contract) = finish_contract_condition (condition); } } static tree build_comment (cp_expr condition) { /* Try to get the actual source text for the condition; if that fails pretty print the resulting tree. */ char *str = get_source_text_between (global_dc->get_file_cache (), condition.get_start (), condition.get_finish ()); if (!str) { /* FIXME cases where we end up here #line macro usage (oof) contracts10.C contracts11.C */ const char *str = expr_to_string (condition); return build_string_literal (strlen (str) + 1, str); } tree t = build_string_literal (strlen (str) + 1, str); free (str); return t; } /* Build a contract statement. */ tree grok_contract (tree attribute, tree mode, tree result, cp_expr condition, location_t loc) { tree_code code; if (is_attribute_p ("assert", attribute)) code = ASSERTION_STMT; else if (is_attribute_p ("pre", attribute)) code = PRECONDITION_STMT; else if (is_attribute_p ("post", attribute)) code = POSTCONDITION_STMT; else gcc_unreachable (); /* Build the contract. The condition is added later. In the case that the contract is deferred, result an plain identifier, not a result variable. */ tree contract; tree type = void_type_node; if (code != POSTCONDITION_STMT) contract = build3_loc (loc, code, type, mode, NULL_TREE, NULL_TREE); else contract = build4_loc (loc, code, type, mode, NULL_TREE, NULL_TREE, result); /* Determine the concrete semantic. */ set_contract_semantic (contract, compute_concrete_semantic (contract)); /* If the contract is deferred, don't do anything with the condition. */ if (TREE_CODE (condition) == DEFERRED_PARSE) { CONTRACT_CONDITION (contract) = condition; return contract; } /* Generate the comment from the original condition. */ CONTRACT_COMMENT (contract) = build_comment (condition); /* The condition is converted to bool. */ condition = finish_contract_condition (condition); CONTRACT_CONDITION (contract) = condition; return contract; } /* Build the contract attribute specifier where IDENTIFIER is one of 'pre', 'post' or 'assert' and CONTRACT is the underlying statement. */ tree finish_contract_attribute (tree identifier, tree contract) { if (contract == error_mark_node) return error_mark_node; tree attribute = build_tree_list (build_tree_list (NULL_TREE, identifier), build_tree_list (NULL_TREE, contract)); /* Mark the attribute as dependent if the condition is dependent. TODO: I'm not sure this is strictly necessary. It's going to be marked as such by a subroutine of cplus_decl_attributes. */ tree condition = CONTRACT_CONDITION (contract); if (TREE_CODE (condition) == DEFERRED_PARSE || value_dependent_expression_p (condition)) ATTR_IS_DEPENDENT (attribute) = true; return attribute; } /* Update condition of a late-parsed contract and postcondition variable, if any. */ void update_late_contract (tree contract, tree result, tree condition) { if (TREE_CODE (contract) == POSTCONDITION_STMT) POSTCONDITION_IDENTIFIER (contract) = result; /* Generate the comment from the original condition. */ CONTRACT_COMMENT (contract) = build_comment (condition); /* The condition is converted to bool. */ condition = finish_contract_condition (condition); CONTRACT_CONDITION (contract) = condition; } /* Return TRUE iff ATTR has been parsed by the front-end as a c++2a contract attribute. */ bool cxx_contract_attribute_p (const_tree attr) { if (attr == NULL_TREE || TREE_CODE (attr) != TREE_LIST) return false; if (!TREE_PURPOSE (attr) || TREE_CODE (TREE_PURPOSE (attr)) != TREE_LIST) return false; if (!TREE_VALUE (attr) || TREE_CODE (TREE_VALUE (attr)) != TREE_LIST) return false; if (!TREE_VALUE (TREE_VALUE (attr))) return false; return (TREE_CODE (TREE_VALUE (TREE_VALUE (attr))) == PRECONDITION_STMT || TREE_CODE (TREE_VALUE (TREE_VALUE (attr))) == POSTCONDITION_STMT || TREE_CODE (TREE_VALUE (TREE_VALUE (attr))) == ASSERTION_STMT); } /* True if ATTR is an assertion. */ bool cp_contract_assertion_p (const_tree attr) { /* This is only an assertion if it is a valid cxx contract attribute and the statement is an ASSERTION_STMT. */ return cxx_contract_attribute_p (attr) && TREE_CODE (CONTRACT_STATEMENT (attr)) == ASSERTION_STMT; } /* Remove all c++2a style contract attributes from the DECL_ATTRIBUTEs of the FUNCTION_DECL FNDECL. */ void remove_contract_attributes (tree fndecl) { tree list = NULL_TREE; for (tree p = DECL_ATTRIBUTES (fndecl); p; p = TREE_CHAIN (p)) if (!cxx_contract_attribute_p (p)) list = tree_cons (TREE_PURPOSE (p), TREE_VALUE (p), list); DECL_ATTRIBUTES (fndecl) = nreverse (list); } static tree find_first_non_contract (tree attributes) { tree head = attributes; tree p = find_contract (attributes); /* There are no contracts. */ if (!p) return head; /* There are leading contracts. */ if (p == head) { while (cxx_contract_attribute_p (p)) p = TREE_CHAIN (p); head = p; } return head; } /* Remove contracts from ATTRIBUTES. */ tree splice_out_contracts (tree attributes) { tree head = find_first_non_contract (attributes); if (!head) return NULL_TREE; /* Splice out remaining contracts. */ tree p = TREE_CHAIN (head); tree q = head; while (p) { if (cxx_contract_attribute_p (p)) { /* Skip a sequence of contracts and then link q to the next non-contract attribute. */ do p = TREE_CHAIN (p); while (cxx_contract_attribute_p (p)); TREE_CHAIN (q) = p; } else p = TREE_CHAIN (p); } return head; } /* Copy contract attributes from NEWDECL onto the attribute list of OLDDECL. */ void copy_contract_attributes (tree olddecl, tree newdecl) { tree attrs = NULL_TREE; for (tree c = DECL_CONTRACTS (newdecl); c; c = TREE_CHAIN (c)) { if (!cxx_contract_attribute_p (c)) continue; attrs = tree_cons (TREE_PURPOSE (c), TREE_VALUE (c), attrs); } attrs = chainon (DECL_ATTRIBUTES (olddecl), nreverse (attrs)); DECL_ATTRIBUTES (olddecl) = attrs; /* And update DECL_CONTEXT of the postcondition result identifier. */ rebuild_postconditions (olddecl); } /* Returns the parameter corresponding to the return value of a guarded function D. Returns NULL_TREE if D has no postconditions or is void. */ static tree get_postcondition_result_parameter (tree d) { if (!d || d == error_mark_node) return NULL_TREE; if (VOID_TYPE_P (TREE_TYPE (TREE_TYPE (d)))) return NULL_TREE; tree post = DECL_POST_FN (d); if (!post || post == error_mark_node) return NULL_TREE; for (tree arg = DECL_ARGUMENTS (post); arg; arg = TREE_CHAIN (arg)) if (!TREE_CHAIN (arg)) return arg; return NULL_TREE; } /* For use with the tree inliner. This preserves non-mapped local variables, such as postcondition result variables, during remapping. */ static tree retain_decl (tree decl, copy_body_data *) { return decl; } /* Rewrite the condition of contract in place, so that references to SRC's parameters are updated to refer to DST's parameters. The postcondition result variable is left unchanged. This, along with remap_contracts, are subroutines of duplicate_decls. When declarations are merged, we sometimes need to update contracts to refer to new parameters. If DUPLICATE_P is true, this is called by duplicate_decls to rewrite contacts in terms of a new set of parameters. In this case, we can retain local variables appearing in the contract because the contract is not being prepared for insertion into a new function. Importantly, this preserves the references to postcondition results, which are not replaced during merging. If false, we're preparing to emit the contract condition into the body of a new function, so we need to make copies of all local variables appearing in the contract (e.g., if it includes a lambda expression). Note that in this case, postcondition results are mapped to the last parameter of DST. This is also used to reuse a parent type's contracts on virtual methods. */ static void remap_contract (tree src, tree dst, tree contract, bool duplicate_p) { copy_body_data id; hash_map decl_map; memset (&id, 0, sizeof (id)); id.src_fn = src; id.dst_fn = dst; id.src_cfun = DECL_STRUCT_FUNCTION (src); id.decl_map = &decl_map; /* If we're merging contracts, don't copy local variables. */ id.copy_decl = duplicate_p ? retain_decl : copy_decl_no_change; id.transform_call_graph_edges = CB_CGE_DUPLICATE; id.transform_new_cfg = false; id.transform_return_to_modify = false; id.transform_parameter = true; /* Make sure not to unshare trees behind the front-end's back since front-end specific mechanisms may rely on sharing. */ id.regimplify = false; id.do_not_unshare = true; id.do_not_fold = true; /* We're not inside any EH region. */ id.eh_lp_nr = 0; bool do_remap = false; /* Insert parameter remappings. */ if (TREE_CODE (src) == FUNCTION_DECL) src = DECL_ARGUMENTS (src); if (TREE_CODE (dst) == FUNCTION_DECL) dst = DECL_ARGUMENTS (dst); for (tree sp = src, dp = dst; sp || dp; sp = DECL_CHAIN (sp), dp = DECL_CHAIN (dp)) { if (!sp && dp && TREE_CODE (contract) == POSTCONDITION_STMT && DECL_CHAIN (dp) == NULL_TREE) { gcc_assert (!duplicate_p); if (tree result = POSTCONDITION_IDENTIFIER (contract)) { gcc_assert (DECL_P (result)); insert_decl_map (&id, result, dp); do_remap = true; } break; } gcc_assert (sp && dp); if (sp == dp) continue; insert_decl_map (&id, sp, dp); do_remap = true; } if (!do_remap) return; walk_tree (&CONTRACT_CONDITION (contract), copy_tree_body_r, &id, NULL); } /* Rewrite any references to SRC's PARM_DECLs to the corresponding PARM_DECL in DST in all of the contract attributes in CONTRACTS by calling remap_contract on each. This is used for two purposes: to rewrite contract attributes during duplicate_decls, and to prepare contracts for emission into a function's respective precondition and postcondition functions. DUPLICATE_P is used to determine the context in which this function is called. See above for the behavior described by this flag. */ void remap_contracts (tree src, tree dst, tree contracts, bool duplicate_p) { for (tree attr = contracts; attr; attr = CONTRACT_CHAIN (attr)) { if (!cxx_contract_attribute_p (attr)) continue; tree contract = CONTRACT_STATEMENT (attr); if (TREE_CODE (CONTRACT_CONDITION (contract)) != DEFERRED_PARSE) remap_contract (src, dst, contract, duplicate_p); } } /* Helper to replace references to dummy this parameters with references to the first argument of the FUNCTION_DECL DATA. */ static tree remap_dummy_this_1 (tree *tp, int *, void *data) { if (!is_this_parameter (*tp)) return NULL_TREE; tree fn = (tree)data; *tp = DECL_ARGUMENTS (fn); return NULL_TREE; } /* Replace all references to dummy this parameters in EXPR with references to the first argument of the FUNCTION_DECL FN. */ static void remap_dummy_this (tree fn, tree *expr) { walk_tree (expr, remap_dummy_this_1, fn, NULL); } /* Contract matching. */ /* True if the contract is valid. */ static bool contract_valid_p (tree contract) { return CONTRACT_CONDITION (contract) != error_mark_node; } /* True if the contract attribute is valid. */ static bool contract_attribute_valid_p (tree attribute) { return contract_valid_p (TREE_VALUE (TREE_VALUE (attribute))); } /* Compare the contract conditions of OLD_ATTR and NEW_ATTR. Returns false if the conditions are equivalent, and true otherwise. */ static bool check_for_mismatched_contracts (tree old_attr, tree new_attr, contract_matching_context ctx) { tree old_contract = CONTRACT_STATEMENT (old_attr); tree new_contract = CONTRACT_STATEMENT (new_attr); /* Different kinds of contracts do not match. */ if (TREE_CODE (old_contract) != TREE_CODE (new_contract)) { auto_diagnostic_group d; error_at (EXPR_LOCATION (new_contract), ctx == cmc_declaration ? "mismatched contract attribute in declaration" : "mismatched contract attribute in override"); inform (EXPR_LOCATION (old_contract), "previous contract here"); return true; } /* A deferred contract tentatively matches. */ if (CONTRACT_CONDITION_DEFERRED_P (new_contract)) return false; /* Compare the conditions of the contracts. We fold immediately to avoid issues comparing contracts on overrides that use parameters -- see contracts-pre3. */ tree t1 = cp_fully_fold_init (CONTRACT_CONDITION (old_contract)); tree t2 = cp_fully_fold_init (CONTRACT_CONDITION (new_contract)); /* Compare the contracts. The fold doesn't eliminate conversions to members. Set the comparing_override_contracts flag to ensure that references through 'this' are equal if they designate the same member, regardless of the path those members. */ bool saved_comparing_contracts = comparing_override_contracts; comparing_override_contracts = (ctx == cmc_override); bool matching_p = cp_tree_equal (t1, t2); comparing_override_contracts = saved_comparing_contracts; if (!matching_p) { auto_diagnostic_group d; error_at (EXPR_LOCATION (CONTRACT_CONDITION (new_contract)), ctx == cmc_declaration ? "mismatched contract condition in declaration" : "mismatched contract condition in override"); inform (EXPR_LOCATION (CONTRACT_CONDITION (old_contract)), "previous contract here"); return true; } return false; } /* Compare the contract attributes of OLDDECL and NEWDECL. Returns true if the contracts match, and false if they differ. */ bool match_contract_conditions (location_t oldloc, tree old_attrs, location_t newloc, tree new_attrs, contract_matching_context ctx) { /* Contracts only match if they are both specified. */ if (!old_attrs || !new_attrs) return true; /* Compare each contract in turn. */ while (old_attrs && new_attrs) { /* If either contract is ill-formed, skip the rest of the comparison, since we've already diagnosed an error. */ if (!contract_attribute_valid_p (new_attrs) || !contract_attribute_valid_p (old_attrs)) return false; if (check_for_mismatched_contracts (old_attrs, new_attrs, ctx)) return false; old_attrs = CONTRACT_CHAIN (old_attrs); new_attrs = CONTRACT_CHAIN (new_attrs); } /* If we didn't compare all attributes, the contracts don't match. */ if (old_attrs || new_attrs) { auto_diagnostic_group d; error_at (newloc, ctx == cmc_declaration ? "declaration has a different number of contracts than " "previously declared" : "override has a different number of contracts than " "previously declared"); inform (oldloc, new_attrs ? "original declaration with fewer contracts here" : "original declaration with more contracts here"); return false; } return true; } /* Deferred contract mapping. This is used to compare late-parsed contracts on overrides with their base class functions. TODO: It seems like this could be replaced by a simple list that maps from overrides to their base functions. It's not clear that we really need a map to a function + a list of contracts. */ /* Map from FNDECL to a tree list of contracts that have not been matched or diagnosed yet. The TREE_PURPOSE is the basefn we're overriding, and the TREE_VALUE is the list of contract attrs for BASEFN. */ static hash_map pending_guarded_decls; void defer_guarded_contract_match (tree fndecl, tree fn, tree contracts) { if (!pending_guarded_decls.get (fndecl)) { pending_guarded_decls.put (fndecl, build_tree_list (fn, contracts)); return; } for (tree pending = *pending_guarded_decls.get (fndecl); pending; pending = TREE_CHAIN (pending)) { if (TREE_VALUE (pending) == contracts) return; if (TREE_CHAIN (pending) == NULL_TREE) TREE_CHAIN (pending) = build_tree_list (fn, contracts); } } /* If the FUNCTION_DECL DECL has any contracts that had their matching deferred earlier, do that checking now. */ void match_deferred_contracts (tree decl) { tree *tp = pending_guarded_decls.get (decl); if (!tp) return; gcc_assert(!contract_any_deferred_p (DECL_CONTRACTS (decl))); processing_template_decl_sentinel ptds; processing_template_decl = uses_template_parms (decl); /* Do late contract matching. */ for (tree pending = *tp; pending; pending = TREE_CHAIN (pending)) { tree new_contracts = TREE_VALUE (pending); location_t new_loc = CONTRACT_SOURCE_LOCATION (new_contracts); tree old_contracts = DECL_CONTRACTS (decl); location_t old_loc = CONTRACT_SOURCE_LOCATION (old_contracts); tree base = TREE_PURPOSE (pending); match_contract_conditions (new_loc, new_contracts, old_loc, old_contracts, base ? cmc_override : cmc_declaration); } /* Clear out deferred match list so we don't check it twice. */ pending_guarded_decls.remove (decl); } /* 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 *decl_pre_fn; static GTY(()) hash_map *decl_post_fn; /* Returns the precondition funtion for D, or null if not set. */ tree get_precondition_function (tree d) { hash_map_maybe_create (decl_pre_fn); tree *result = decl_pre_fn->get (d); return result ? *result : NULL_TREE; } /* Returns the postcondition funtion for D, or null if not set. */ tree get_postcondition_function (tree d) { hash_map_maybe_create (decl_post_fn); tree *result = decl_post_fn->get (d); return result ? *result : NULL_TREE; } /* Makes PRE the precondition function for D. */ void set_precondition_function (tree d, tree pre) { gcc_assert (pre); hash_map_maybe_create (decl_pre_fn); gcc_assert (!decl_pre_fn->get (d)); decl_pre_fn->put (d, pre); } /* Makes POST the postcondition function for D. */ void set_postcondition_function (tree d, tree post) { gcc_assert (post); hash_map_maybe_create (decl_post_fn); gcc_assert (!decl_post_fn->get (d)); decl_post_fn->put (d, post); } /* Set the PRE and POST functions for D. Note that PRE and POST can be null in this case. If so the functions are not recorded. */ void set_contract_functions (tree d, tree pre, tree post) { if (pre) set_precondition_function (d, pre); if (post) set_postcondition_function (d, post); } /* Return a copy of the FUNCTION_DECL IDECL with its own unshared PARM_DECL and DECL_ATTRIBUTEs. */ static tree copy_fn_decl (tree idecl) { tree decl = copy_decl (idecl); DECL_ATTRIBUTES (decl) = copy_list (DECL_ATTRIBUTES (idecl)); if (DECL_RESULT (idecl)) { DECL_RESULT (decl) = copy_decl (DECL_RESULT (idecl)); DECL_CONTEXT (DECL_RESULT (decl)) = decl; } if (!DECL_ARGUMENTS (idecl) || VOID_TYPE_P (DECL_ARGUMENTS (idecl))) return decl; tree last = DECL_ARGUMENTS (decl) = copy_decl (DECL_ARGUMENTS (decl)); DECL_CONTEXT (last) = decl; for (tree p = TREE_CHAIN (DECL_ARGUMENTS (idecl)); p; p = TREE_CHAIN (p)) { if (VOID_TYPE_P (p)) { TREE_CHAIN (last) = void_list_node; break; } last = TREE_CHAIN (last) = copy_decl (p); DECL_CONTEXT (last) = decl; } return decl; } /* Build a declaration for the pre- or postcondition of a guarded FNDECL. */ static tree build_contract_condition_function (tree fndecl, bool pre) { if (TREE_TYPE (fndecl) == error_mark_node) return error_mark_node; if (DECL_IOBJ_MEMBER_FUNCTION_P (fndecl) && !TYPE_METHOD_BASETYPE (TREE_TYPE (fndecl))) return error_mark_node; /* Create and rename the unchecked function and give an internal name. */ tree fn = copy_fn_decl (fndecl); DECL_RESULT (fn) = NULL_TREE; tree value_type = pre ? void_type_node : TREE_TYPE (TREE_TYPE (fn)); /* Don't propagate declaration attributes to the checking function, including the original contracts. */ DECL_ATTRIBUTES (fn) = NULL_TREE; tree arg_types = NULL_TREE; tree *last = &arg_types; /* FIXME will later optimizations delete unused args to prevent extra arg passing? do we care? */ tree class_type = NULL_TREE; 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) { class_type = TREE_TYPE (TREE_VALUE (arg_type)); continue; } *last = build_tree_list (TREE_PURPOSE (arg_type), TREE_VALUE (arg_type)); last = &TREE_CHAIN (*last); } if (pre || VOID_TYPE_P (value_type)) *last = void_list_node; else { tree name = get_identifier ("__r"); tree parm = build_lang_decl (PARM_DECL, name, value_type); DECL_CONTEXT (parm) = fn; DECL_ARTIFICIAL (parm) = true; DECL_ARGUMENTS (fn) = chainon (DECL_ARGUMENTS (fn), parm); *last = build_tree_list (NULL_TREE, value_type); TREE_CHAIN (*last) = void_list_node; if (aggregate_value_p (value_type, fndecl)) /* If FNDECL returns in memory, don't return the value from the postcondition. */ value_type = void_type_node; } TREE_TYPE (fn) = build_function_type (value_type, arg_types); if (DECL_IOBJ_MEMBER_FUNCTION_P (fndecl)) TREE_TYPE (fn) = build_method_type (class_type, TREE_TYPE (fn)); DECL_NAME (fn) = copy_node (DECL_NAME (fn)); DECL_INITIAL (fn) = error_mark_node; DECL_ABSTRACT_ORIGIN (fn) = fndecl; IDENTIFIER_VIRTUAL_P (DECL_NAME (fn)) = false; 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 haven't set the comdat group on the guarded function yet, we'll add this to the same group in comdat_linkage later. */ gcc_assert (!DECL_ONE_ONLY (fndecl)); DECL_INTERFACE_KNOWN (fn) = true; } DECL_ARTIFICIAL (fn) = true; /* Update various inline related declaration properties. */ //DECL_DECLARED_INLINE_P (fn) = true; DECL_DISREGARD_INLINE_LIMITS (fn) = true; TREE_NO_WARNING (fn) = 1; return fn; } /* Return true if CONTRACT is checked or assumed under the current build configuration. */ bool contract_active_p (tree contract) { return get_contract_semantic (contract) != CCS_IGNORE; } static bool has_active_contract_condition (tree d, tree_code c) { for (tree as = DECL_CONTRACTS (d) ; as != NULL_TREE; as = TREE_CHAIN (as)) { tree contract = TREE_VALUE (TREE_VALUE (as)); if (TREE_CODE (contract) == c && contract_active_p (contract)) return true; } return false; } /* True if D has any checked or assumed preconditions. */ static bool has_active_preconditions (tree d) { return has_active_contract_condition (d, PRECONDITION_STMT); } /* True if D has any checked or assumed postconditions. */ static bool has_active_postconditions (tree d) { return has_active_contract_condition (d, POSTCONDITION_STMT); } /* Return true if any contract in the CONTRACT list is checked or assumed under the current build configuration. */ bool contract_any_active_p (tree contract) { for (; contract != NULL_TREE; contract = CONTRACT_CHAIN (contract)) if (contract_active_p (TREE_VALUE (TREE_VALUE (contract)))) return true; return false; } /* Do we need to mess with contracts for DECL1? */ static bool handle_contracts_p (tree decl1) { return (flag_contracts && !processing_template_decl && DECL_ABSTRACT_ORIGIN (decl1) == NULL_TREE && contract_any_active_p (DECL_CONTRACTS (decl1))); } /* Should we break out DECL1's pre/post contracts into separate functions? FIXME I'd like this to default to 0, but that will need an overhaul to the return identifier handling to just refer to the RESULT_DECL. */ static bool outline_contracts_p (tree decl1) { return (!DECL_CONSTRUCTOR_P (decl1) && !DECL_DESTRUCTOR_P (decl1)); } /* Build the precondition checking function for D. */ static tree build_precondition_function (tree d) { if (!has_active_preconditions (d)) return NULL_TREE; return build_contract_condition_function (d, /*pre=*/true); } /* Build the postcondition checking function for D. 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 d) { if (!has_active_postconditions (d)) return NULL_TREE; tree type = TREE_TYPE (TREE_TYPE (d)); if (is_auto (type)) return NULL_TREE; return build_contract_condition_function (d, /*pre=*/false); } static void build_contract_function_decls (tree d) { /* Constructors and destructors have their contracts inserted inline. */ if (!outline_contracts_p (d)) return; /* Build the pre/post functions (or not). */ tree pre = build_precondition_function (d); tree post = build_postcondition_function (d); set_contract_functions (d, pre, post); } static const char * get_contract_level_name (tree contract) { if (CONTRACT_LITERAL_MODE_P (contract)) return ""; if (tree mode = CONTRACT_MODE (contract)) if (tree level = TREE_VALUE (mode)) return IDENTIFIER_POINTER (level); return "default"; } static const char * get_contract_role_name (tree contract) { if (CONTRACT_LITERAL_MODE_P (contract)) return ""; if (tree mode = CONTRACT_MODE (contract)) if (tree role = TREE_PURPOSE (mode)) return IDENTIFIER_POINTER (role); return "default"; } /* Build a layout-compatible internal version of std::contract_violation. */ static tree get_pseudo_contract_violation_type () { if (!pseudo_contract_violation_type) { /* Must match : class contract_violation { const char* _M_file; const char* _M_function; const char* _M_comment; const char* _M_level; const char* _M_role; uint_least32_t _M_line; signed char _M_continue; If this changes, also update the initializer in build_contract_violation. */ const tree types[] = { const_string_type_node, const_string_type_node, const_string_type_node, const_string_type_node, const_string_type_node, uint_least32_type_node, signed_char_type_node }; tree fields = NULL_TREE; for (tree type : types) { /* finish_builtin_struct wants fieldss chained in reverse. */ tree next = build_decl (BUILTINS_LOCATION, FIELD_DECL, NULL_TREE, type); DECL_CHAIN (next) = fields; fields = next; } iloc_sentinel ils (input_location); input_location = BUILTINS_LOCATION; pseudo_contract_violation_type = make_class_type (RECORD_TYPE); finish_builtin_struct (pseudo_contract_violation_type, "__pseudo_contract_violation", fields, NULL_TREE); CLASSTYPE_AS_BASE (pseudo_contract_violation_type) = pseudo_contract_violation_type; DECL_CONTEXT (TYPE_NAME (pseudo_contract_violation_type)) = FROB_CONTEXT (global_namespace); TREE_PUBLIC (TYPE_NAME (pseudo_contract_violation_type)) = true; CLASSTYPE_LITERAL_P (pseudo_contract_violation_type) = true; CLASSTYPE_LAZY_COPY_CTOR (pseudo_contract_violation_type) = true; xref_basetypes (pseudo_contract_violation_type, /*bases=*/NULL_TREE); pseudo_contract_violation_type = cp_build_qualified_type (pseudo_contract_violation_type, TYPE_QUAL_CONST); } return pseudo_contract_violation_type; } /* Return a VAR_DECL to pass to handle_contract_violation. */ static tree build_contract_violation (tree contract, contract_continuation cmode) { expanded_location loc = expand_location (EXPR_LOCATION (contract)); const char *function = fndecl_name (DECL_ORIGIN (current_function_decl)); const char *level = get_contract_level_name (contract); const char *role = get_contract_role_name (contract); /* Must match the type layout in get_pseudo_contract_violation_type. */ tree ctor = build_constructor_va (init_list_type_node, 7, NULL_TREE, build_string_literal (loc.file), NULL_TREE, build_string_literal (function), NULL_TREE, CONTRACT_COMMENT (contract), NULL_TREE, build_string_literal (level), NULL_TREE, build_string_literal (role), NULL_TREE, build_int_cst (uint_least32_type_node, loc.line), NULL_TREE, build_int_cst (signed_char_type_node, cmode)); ctor = finish_compound_literal (get_pseudo_contract_violation_type (), ctor, tf_none); protected_set_expr_location (ctor, EXPR_LOCATION (contract)); return ctor; } /* Return handle_contract_violation(), declaring it if needed. */ static tree declare_handle_contract_violation () { tree fnname = get_identifier ("handle_contract_violation"); tree viol_name = get_identifier ("contract_violation"); tree l = lookup_qualified_name (global_namespace, fnname, LOOK_want::HIDDEN_FRIEND); for (tree f: lkp_range (l)) if (TREE_CODE (f) == FUNCTION_DECL) { tree parms = TYPE_ARG_TYPES (TREE_TYPE (f)); if (remaining_arguments (parms) != 1) continue; tree parmtype = non_reference (TREE_VALUE (parms)); if (CLASS_TYPE_P (parmtype) && TYPE_IDENTIFIER (parmtype) == viol_name) return f; } tree id_exp = get_identifier ("experimental"); tree ns_exp = lookup_qualified_name (std_node, id_exp); tree violation = error_mark_node; if (TREE_CODE (ns_exp) == NAMESPACE_DECL) violation = lookup_qualified_name (ns_exp, viol_name, LOOK_want::TYPE |LOOK_want::HIDDEN_FRIEND); if (TREE_CODE (violation) == TYPE_DECL) violation = TREE_TYPE (violation); else { push_nested_namespace (std_node); push_namespace (id_exp, /*inline*/false); violation = make_class_type (RECORD_TYPE); create_implicit_typedef (viol_name, violation); DECL_SOURCE_LOCATION (TYPE_NAME (violation)) = BUILTINS_LOCATION; DECL_CONTEXT (TYPE_NAME (violation)) = current_namespace; pushdecl_namespace_level (TYPE_NAME (violation), /*hidden*/true); pop_namespace (); pop_nested_namespace (std_node); } tree argtype = cp_build_qualified_type (violation, TYPE_QUAL_CONST); argtype = cp_build_reference_type (argtype, /*rval*/false); tree fntype = build_function_type_list (void_type_node, argtype, NULL_TREE); push_nested_namespace (global_namespace); tree fn = build_cp_library_fn_ptr ("handle_contract_violation", fntype, ECF_COLD); pushdecl_namespace_level (fn, /*hiding*/true); pop_nested_namespace (global_namespace); return fn; } /* Build the call to handle_contract_violation for CONTRACT. */ static void build_contract_handler_call (tree contract, contract_continuation cmode) { tree violation = build_contract_violation (contract, cmode); tree violation_fn = declare_handle_contract_violation (); tree call = build_call_n (violation_fn, 1, build_address (violation)); finish_expr_stmt (call); } /* Generate the code that checks or assumes a contract, but do not attach it to the current context. This is called during genericization. */ tree build_contract_check (tree contract) { contract_semantic semantic = get_contract_semantic (contract); if (semantic == CCS_INVALID) return NULL_TREE; /* Ignored contracts are never checked or assumed. */ if (semantic == CCS_IGNORE) return void_node; remap_dummy_this (current_function_decl, &CONTRACT_CONDITION (contract)); tree condition = CONTRACT_CONDITION (contract); if (condition == error_mark_node) return NULL_TREE; location_t loc = EXPR_LOCATION (contract); if (semantic == CCS_ASSUME) return build_assume_call (loc, condition); tree if_stmt = begin_if_stmt (); tree cond = build_x_unary_op (loc, TRUTH_NOT_EXPR, condition, NULL_TREE, tf_warning_or_error); finish_if_stmt_cond (cond, if_stmt); /* Get the continuation mode. */ contract_continuation cmode; switch (semantic) { case CCS_NEVER: cmode = NEVER_CONTINUE; break; case CCS_MAYBE: cmode = MAYBE_CONTINUE; break; default: gcc_unreachable (); } build_contract_handler_call (contract, cmode); if (cmode == NEVER_CONTINUE) finish_expr_stmt (build_call_a (terminate_fn, 0, nullptr)); finish_then_clause (if_stmt); tree scope = IF_SCOPE (if_stmt); IF_SCOPE (if_stmt) = NULL; return do_poplevel (scope); } /* Add the contract statement CONTRACT to the current block if valid. */ static void emit_contract_statement (tree contract) { /* Only add valid contracts. */ if (get_contract_semantic (contract) != CCS_INVALID && CONTRACT_CONDITION (contract) != error_mark_node) add_stmt (contract); } /* Generate the statement for the given contract attribute by adding the statement to the current block. Returns the next contract in the chain. */ static tree emit_contract_attr (tree attr) { gcc_assert (TREE_CODE (attr) == TREE_LIST); emit_contract_statement (CONTRACT_STATEMENT (attr)); return CONTRACT_CHAIN (attr); } /* Add the statements of contract attributes ATTRS to the current block. */ static void emit_contract_conditions (tree attrs, tree_code code) { if (!attrs) return; gcc_assert (TREE_CODE (attrs) == TREE_LIST); gcc_assert (code == PRECONDITION_STMT || code == POSTCONDITION_STMT); while (attrs) { tree contract = CONTRACT_STATEMENT (attrs); if (TREE_CODE (contract) == code) attrs = emit_contract_attr (attrs); else attrs = CONTRACT_CHAIN (attrs); } } /* Emit the statement for an assertion attribute. */ void emit_assertion (tree attr) { emit_contract_attr (attr); } /* Emit statements for precondition attributes. */ static void emit_preconditions (tree attr) { return emit_contract_conditions (attr, PRECONDITION_STMT); } /* Emit statements for postcondition attributes. */ static void emit_postconditions_cleanup (tree contracts) { tree stmts = push_stmt_list (); emit_contract_conditions (contracts, POSTCONDITION_STMT); stmts = pop_stmt_list (stmts); push_cleanup (NULL_TREE, stmts, /*eh_only*/false); } /* We're compiling the pre/postcondition function CONDFN; remap any FN attributes 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); for (tree attr = DECL_CONTRACTS (fn); attr; attr = CONTRACT_CHAIN (attr)) { tree contract = CONTRACT_STATEMENT (attr); if (TREE_CODE (contract) == code) { contract = copy_node (contract); remap_contract (fn, condfn, contract, /*duplicate_p=*/false); emit_contract_statement (contract); } } } /* Converts a contract condition to bool and ensures it has a locaiton. */ tree finish_contract_condition (cp_expr condition) { /* Ensure we have the condition location saved in case we later need to emit a conversion error during template instantiation and wouldn't otherwise have it. */ if (!CAN_HAVE_LOCATION_P (condition) || EXCEPTIONAL_CLASS_P (condition)) { condition = build1_loc (condition.get_location (), VIEW_CONVERT_EXPR, TREE_TYPE (condition), condition); EXPR_LOCATION_WRAPPER_P (condition) = 1; } if (condition == error_mark_node || type_dependent_expression_p (condition)) return condition; return condition_conversion (condition); } void maybe_update_postconditions (tree fco) { /* 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 (fco)) { rebuild_postconditions (fco); tree post = build_postcondition_function (fco); set_postcondition_function (fco, post); } } /* Called on attribute lists that must not contain contracts. If any contracts are present, issue an error diagnostic and return true. */ bool diagnose_misapplied_contracts (tree attributes) { if (attributes == NULL_TREE) return false; tree contract_attr = find_contract (attributes); if (!contract_attr) return false; error_at (EXPR_LOCATION (CONTRACT_STATEMENT (contract_attr)), "contracts must appertain to a function type"); /* Invalidate the contract so we don't treat it as valid later on. */ invalidate_contract (TREE_VALUE (TREE_VALUE (contract_attr))); return true; } /* Build and return an argument list containing all the parameters of the (presumably guarded) FUNCTION_DECL FN. This can be used to forward all of FN's arguments to a function taking the same list of arguments -- namely the unchecked form of FN. We use CALL_FROM_THUNK_P instead of forward_parm for forwarding semantics. */ static vec * build_arg_list (tree fn) { vec *args = make_tree_vector (); for (tree t = DECL_ARGUMENTS (fn); t; t = DECL_CHAIN (t)) vec_safe_push (args, t); return args; } void start_function_contracts (tree decl1) { if (!handle_contracts_p (decl1)) return; if (!outline_contracts_p (decl1)) { emit_preconditions (DECL_CONTRACTS (current_function_decl)); emit_postconditions_cleanup (DECL_CONTRACTS (current_function_decl)); 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. */ if (!DECL_PRE_FN (decl1)) build_contract_function_decls (decl1); /* If we're starting a guarded function with valid contracts, we need to insert a call to the pre function. */ if (DECL_PRE_FN (decl1) && DECL_PRE_FN (decl1) != error_mark_node) { releasing_vec args = build_arg_list (decl1); tree call = build_call_a (DECL_PRE_FN (decl1), args->length (), args->address ()); CALL_FROM_THUNK_P (call) = true; finish_expr_stmt (call); } } /* 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_contracts (tree fndecl) { if (!handle_contracts_p (fndecl) || !outline_contracts_p (fndecl)) return; for (tree ca = DECL_CONTRACTS (fndecl); ca; ca = CONTRACT_CHAIN (ca)) { tree contract = CONTRACT_STATEMENT (ca); if (!CONTRACT_CONDITION (contract) || CONTRACT_CONDITION_DEFERRED_P (contract) || CONTRACT_CONDITION (contract) == error_mark_node) return; } int flags = SF_DEFAULT | SF_PRE_PARSED; /* 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; if (pre && DECL_INITIAL (fndecl) != error_mark_node) { DECL_PENDING_INLINE_P (pre) = false; start_preparsed_function (pre, DECL_ATTRIBUTES (pre), flags); remap_and_emit_conditions (fndecl, pre, PRECONDITION_STMT); tree finished_pre = finish_function (false); expand_or_defer_fn (finished_pre); } if (post && DECL_INITIAL (fndecl) != error_mark_node) { DECL_PENDING_INLINE_P (post) = false; start_preparsed_function (post, DECL_ATTRIBUTES (post), flags); remap_and_emit_conditions (fndecl, post, POSTCONDITION_STMT); if (!VOID_TYPE_P (TREE_TYPE (TREE_TYPE (post)))) finish_return_stmt (get_postcondition_result_parameter (fndecl)); tree finished_post = finish_function (false); expand_or_defer_fn (finished_post); } } /* Rewrite the expression of a returned expression so that it invokes the postcondition function as needed. */ tree apply_postcondition_to_return (tree expr) { tree fn = current_function_decl; tree post = DECL_POST_FN (fn); if (!post) return NULL_TREE; /* If FN returns in memory, POST has a void return type and we call it when EXPR is DECL_RESULT (fn). If FN returns a scalar, POST has the same return type and we call it when EXPR is the value being returned. */ if (VOID_TYPE_P (TREE_TYPE (TREE_TYPE (post))) != (expr == DECL_RESULT (fn))) return NULL_TREE; releasing_vec args = build_arg_list (fn); if (get_postcondition_result_parameter (fn)) vec_safe_push (args, expr); tree call = build_call_a (post, args->length (), args->address ()); CALL_FROM_THUNK_P (call) = true; return call; } /* A subroutine of duplicate_decls. Diagnose issues in the redeclaration of guarded functions. */ void duplicate_contracts (tree newdecl, tree olddecl) { if (TREE_CODE (newdecl) == TEMPLATE_DECL) newdecl = DECL_TEMPLATE_RESULT (newdecl); if (TREE_CODE (olddecl) == TEMPLATE_DECL) olddecl = DECL_TEMPLATE_RESULT (olddecl); /* Compare contracts to see if they match. */ tree old_contracts = DECL_CONTRACTS (olddecl); tree new_contracts = DECL_CONTRACTS (newdecl); if (!old_contracts && !new_contracts) return; location_t old_loc = DECL_SOURCE_LOCATION (olddecl); location_t new_loc = DECL_SOURCE_LOCATION (newdecl); /* If both declarations specify contracts, ensure they match. TODO: This handles a potential error a little oddly. Consider: struct B { virtual void f(int n) [[pre: n == 0]]; }; struct D : B { void f(int n) override; // inherits contracts }; void D::f(int n) [[pre: n == 0]] // OK { } It's okay because we're explicitly restating the inherited contract. Changing the precondition on the definition D::f causes match_contracts to complain about the mismatch. This would previously have been diagnosed as adding contracts to an override, but this seems like it should be well-formed. */ if (old_contracts && new_contracts) { if (!match_contract_conditions (old_loc, old_contracts, new_loc, new_contracts, cmc_declaration)) return; if (DECL_UNIQUE_FRIEND_P (newdecl)) /* Newdecl's contracts are still DEFERRED_PARSE, and we're about to collapse it into olddecl, so stash away olddecl's contracts for later comparison. */ defer_guarded_contract_match (olddecl, olddecl, old_contracts); } /* Handle cases where contracts are omitted in one or the other declaration. */ if (old_contracts) { /* Contracts have been previously specified by are no omitted. The new declaration inherits the existing contracts. */ if (!new_contracts) copy_contract_attributes (newdecl, olddecl); /* In all cases, remove existing contracts from OLDDECL to prevent the attribute merging function from adding excess contracts. */ remove_contract_attributes (olddecl); } else if (!old_contracts) { /* We are adding contracts to a declaration. */ if (new_contracts) { /* We can't add to a previously defined function. */ if (DECL_INITIAL (olddecl)) { auto_diagnostic_group d; error_at (new_loc, "cannot add contracts after definition"); inform (DECL_SOURCE_LOCATION (olddecl), "original definition here"); return; } /* We can't add to an unguarded virtual function declaration. */ if (DECL_VIRTUAL_P (olddecl) && new_contracts) { auto_diagnostic_group d; error_at (new_loc, "cannot add contracts to a virtual function"); inform (DECL_SOURCE_LOCATION (olddecl), "original declaration here"); return; } /* Depending on the "first declaration" rule, we may not be able to add contracts to a function after the fact. */ if (flag_contract_strict_declarations) { warning_at (new_loc, OPT_fcontract_strict_declarations_, "declaration adds contracts to %q#D", olddecl); return; } /* Copy the contracts from NEWDECL to OLDDECL. We shouldn't need to remap them because NEWDECL's parameters will replace those of OLDDECL. Remove the contracts from NEWDECL so they aren't cloned when merging. */ copy_contract_attributes (olddecl, newdecl); remove_contract_attributes (newdecl); } } } /* Replace the any contract attributes on OVERRIDER with a copy where any references to BASEFN's PARM_DECLs have been rewritten to the corresponding PARM_DECL in OVERRIDER. */ void inherit_base_contracts (tree overrider, tree basefn) { tree last = NULL_TREE, contract_attrs = NULL_TREE; for (tree a = DECL_CONTRACTS (basefn); a != NULL_TREE; a = CONTRACT_CHAIN (a)) { tree c = copy_node (a); TREE_VALUE (c) = build_tree_list (TREE_PURPOSE (TREE_VALUE (c)), copy_node (CONTRACT_STATEMENT (c))); tree src = basefn; tree dst = overrider; remap_contract (src, dst, CONTRACT_STATEMENT (c), /*duplicate_p=*/true); CONTRACT_COMMENT (CONTRACT_STATEMENT (c)) = copy_node (CONTRACT_COMMENT (CONTRACT_STATEMENT (c))); chainon (last, c); last = c; if (!contract_attrs) contract_attrs = c; } set_decl_contracts (overrider, contract_attrs); } #include "gt-cp-contracts.h"