| /* Definitions for C++ contract levels |
| Copyright (C) 2020-2022 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 |
| <http://www.gnu.org/licenses/>. */ |
| |
| /* 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 <contract>. |
| |
| ??? 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" |
| |
| 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 %<assume%> or %<ignore%>"); |
| |
| if (!valid_configs[role->default_semantic][role->audit_semantic] ) |
| warning (0, "the %<audit%> semantic should be at least as strong as " |
| "the %<default%> 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 = strchrnul (role, ':') - role; |
| size_t name_len = strchrnul (name, ':') - 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 %<on%> or %<off%>"); |
| |
| 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 %<on%> or %<off%>"); |
| |
| 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) |
| { |
| if (VOID_TYPE_P (type)) |
| { |
| const char* what; |
| if (DECL_CONSTRUCTOR_P (decl)) |
| what = "constructor"; |
| else if (DECL_DESTRUCTOR_P (decl)) |
| what = "destructor"; |
| else |
| what = "function"; |
| error_at (loc, "%s does not return a value to test", what); |
| 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, false); |
| --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 (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), NULL_TREE); |
| 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<tree, tree> 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<tree_decl_hash, tree> 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<tree, tree> *decl_pre_fn; |
| static GTY(()) hash_map<tree, tree> *decl_post_fn; |
| |
| /* Returns the precondition funtion for D, or null if not set. */ |
| |
| tree |
| get_precondition_function (tree d) |
| { |
| hash_map_maybe_create<hm_ggc> (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<hm_ggc> (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<hm_ggc> (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<hm_ggc> (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_NONSTATIC_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_NONSTATIC_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_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_NONSTATIC_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 refor 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 <contract>: |
| 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<tree, va_gc> * |
| build_arg_list (tree fn) |
| { |
| vec<tree, va_gc> *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" |