diff options
author | Jeff Chapman II <jchapman@lock3software.com> | 2021-06-29 16:52:56 -0400 |
---|---|---|
committer | Jason Merrill <jason@redhat.com> | 2022-11-18 21:41:14 -0500 |
commit | 2efb237ffc68ec9bb17982434f5941bfa14f8b50 (patch) | |
tree | 1c4cc161c09a6cb9a87cd38f752ac9846e86fc54 /gcc/cp/contracts.cc | |
parent | ea63396f6b08f88f1cde827e6cab94cd488f7fa7 (diff) | |
download | gcc-2efb237ffc68ec9bb17982434f5941bfa14f8b50.zip gcc-2efb237ffc68ec9bb17982434f5941bfa14f8b50.tar.gz gcc-2efb237ffc68ec9bb17982434f5941bfa14f8b50.tar.bz2 |
c++: implement P1492 contracts
Implement the P1492 versions of contracts, along with extensions that
support the emulation of N4820 and other proposals. This implementation
assigns a concrete semantic (one of: ignore, assume, enforce, or
observe) to each contract attribute depending on its labels and
configuration options.
Co-authored-by: Andrew Sutton <asutton@lock3software.com>
Co-authored-by: Andrew Marmaduke <amarmaduke@lock3software.com>
Co-authored-by: Michael Lopez <mlopez@lock3software.com>
Co-authored-by: Jason Merrill <jason@redhat.com>
gcc/ChangeLog:
* doc/invoke.texi: Document contracts flags.
gcc/c-family/ChangeLog:
* c.opt: Add contracts flags.
* c-cppbuiltin.cc (c_cpp_builtins): Add contracts feature-test
macros.
gcc/cp/ChangeLog:
* cp-tree.h (enum cp_tree_index): Add
CPTI_PSEUDO_CONTRACT_VIOLATION.
(pseudo_contract_violation_type): New macro.
(struct saved_scope): Add x_processing_contract_condition.
(processing_contract_condition): New macro.
(comparing_override_contracts): New variable decl.
(find_contract): New inline.
(set_decl_contracts): New inline.
(get_contract_semantic): New inline.
(set_contract_semantic): New inline.
* constexpr.cc (cxx_eval_assert): Split out from...
(cxx_eval_internal_function): ...here.
(cxx_eval_constant_expression): Use it for contracts.
(potential_constant_expression_1): Handle contracts.
* cp-gimplify.cc (cp_genericize_r): Handle contracts.
* cp-objcp-common.cc (cp_tree_size): Handle contracts.
(cp_common_init_ts): Handle contracts.
(cp_handle_option): Handle contracts.
* decl.cc (duplicate_decls): Handle contracts.
(check_tag_decl): Check for bogus contracts.
(start_decl): Check flag_contracts.
(grokfndecl): Call rebuild_postconditions.
(grokdeclarator): Handle contract attributes.
(start_preparsed_function): Call start_function_contracts.
(finish_function): Call finish_function_contracts.
* decl2.cc (cp_check_const_attributes): Skip contracts.
(comdat_linkage): Handle outlined contracts.
* error.cc (dump_type): Handle null TYPE_IDENTIFIER.
* g++spec.cc (EXPERIMENTAL): New macro.
(lang_specific_driver): Add -lstdc++exp if -fcontracts.
* mangle.cc (write_encoding): Handle outlined contracts.
* module.cc (trees_out::fn_parms_init): Handle outlined contracts.
(trees_in::fn_parms_init): Likewise.
(check_mergeable_decl): Likewise.
(module_state_config::get_dialect): Record -fcontracts.
* parser.h (struct cp_unparsed_functions_entry): Add contracts.
* parser.cc (unparsed_contracts): New macro.
(push_unparsed_function_queues): Adjust.
(contract_attribute_p): New.
(cp_parser_statement): Check contracts.
(cp_parser_decl_specifier_seq): Handle contracts.
(cp_parser_skip_to_closing_square_bracket): Split out...
(cp_parser_skip_up_to_closing_square_bracket): ...this fn.
(cp_parser_class_specifier): Do contract late parsing.
(cp_parser_class_head): Check contracts.
(cp_parser_contract_role): New.
(cp_parser_contract_mode_opt): New.
(find_error, contains_error_p): New.
(cp_parser_contract_attribute_spec): New.
(cp_parser_late_contract_condition): New.
(cp_parser_std_attribute_spec): Handle contracts.
(cp_parser_save_default_args): Also save contracts.
* pt.cc (register_parameter_specializations): No longer static.
(register_local_identity): New.
(check_explicit_specialization): Call remove_contract_attributes.
(tsubst_contract, tsubst_contract_attribute): New.
(tsubst_contract_attributes): New.
(tsubst_attribute): Add comment.
(tsubst_copy): Also allow parm when processing_contract_condition.
(tsubst_expr): Handle contracts.
(regenerate_decl_from_template): Handle contracts.
* search.cc (check_final_overrider): Compare contracts.
* semantics.cc (set_cleanup_locs): Skip POSTCONDITION_STMT.
(finish_non_static_data_member): Check contracts.
(finish_this_expr): Check contracts.
(process_outer_var_ref): Handle contracts.
(finish_id_expression_1): Handle contracts.
(apply_deduced_return_type): Adjust contracts.
* tree.cc (handle_contract_attribute): New.
(get_innermost_component, is_this_expression): New.
(comparing_this_references): New.
(equivalent_member_references): New.
(cp_tree_equal): Check it.
* typeck.cc (check_return_expr): Apply contracts.
* Make-lang.in: Add contracts.o.
* config-lang.in: Add contracts.cc.
* cp-tree.def (ASSERTION_STMT, PRECONDITION_STMT)
(POSTCONDITION_STMT): New.
* contracts.h: New file.
* contracts.cc: New file.
gcc/testsuite/ChangeLog:
* g++.dg/modules/modules.exp: Pass dg-options to link command.
* lib/g++.exp: Add -L for libstdc++exp.a.
* g++.dg/contracts/backtrace_handler/assert_fail.cpp: New test.
* g++.dg/contracts/backtrace_handler/handle_contract_violation.cpp: New test.
* g++.dg/contracts/contracts-access1.C: New test.
* g++.dg/contracts/contracts-assume1.C: New test.
* g++.dg/contracts/contracts-assume2.C: New test.
* g++.dg/contracts/contracts-assume3.C: New test.
* g++.dg/contracts/contracts-assume4.C: New test.
* g++.dg/contracts/contracts-assume5.C: New test.
* g++.dg/contracts/contracts-assume6.C: New test.
* g++.dg/contracts/contracts-comdat1.C: New test.
* g++.dg/contracts/contracts-config1.C: New test.
* g++.dg/contracts/contracts-constexpr1.C: New test.
* g++.dg/contracts/contracts-constexpr2.C: New test.
* g++.dg/contracts/contracts-constexpr3.C: New test.
* g++.dg/contracts/contracts-conversion1.C: New test.
* g++.dg/contracts/contracts-ctor-dtor1.C: New test.
* g++.dg/contracts/contracts-ctor-dtor2.C: New test.
* g++.dg/contracts/contracts-cv1.C: New test.
* g++.dg/contracts/contracts-deduced1.C: New test.
* g++.dg/contracts/contracts-deduced2.C: New test.
* g++.dg/contracts/contracts-friend1.C: New test.
* g++.dg/contracts/contracts-ft1.C: New test.
* g++.dg/contracts/contracts-ignore1.C: New test.
* g++.dg/contracts/contracts-ignore2.C: New test.
* g++.dg/contracts/contracts-large-return.C: New test.
* g++.dg/contracts/contracts-multiline1.C: New test.
* g++.dg/contracts/contracts-multiple-inheritance1.C: New test.
* g++.dg/contracts/contracts-multiple-inheritance2.C: New test.
* g++.dg/contracts/contracts-nested-class1.C: New test.
* g++.dg/contracts/contracts-nested-class2.C: New test.
* g++.dg/contracts/contracts-nocopy1.C: New test.
* g++.dg/contracts/contracts-override.C: New test.
* g++.dg/contracts/contracts-post1.C: New test.
* g++.dg/contracts/contracts-post2.C: New test.
* g++.dg/contracts/contracts-post3.C: New test.
* g++.dg/contracts/contracts-post4.C: New test.
* g++.dg/contracts/contracts-post5.C: New test.
* g++.dg/contracts/contracts-post6.C: New test.
* g++.dg/contracts/contracts-pre1.C: New test.
* g++.dg/contracts/contracts-pre10.C: New test.
* g++.dg/contracts/contracts-pre2.C: New test.
* g++.dg/contracts/contracts-pre2a1.C: New test.
* g++.dg/contracts/contracts-pre2a2.C: New test.
* g++.dg/contracts/contracts-pre3.C: New test.
* g++.dg/contracts/contracts-pre4.C: New test.
* g++.dg/contracts/contracts-pre5.C: New test.
* g++.dg/contracts/contracts-pre6.C: New test.
* g++.dg/contracts/contracts-pre7.C: New test.
* g++.dg/contracts/contracts-pre9.C: New test.
* g++.dg/contracts/contracts-redecl1.C: New test.
* g++.dg/contracts/contracts-redecl2.C: New test.
* g++.dg/contracts/contracts-redecl3.C: New test.
* g++.dg/contracts/contracts-redecl4.C: New test.
* g++.dg/contracts/contracts-redecl5.C: New test.
* g++.dg/contracts/contracts-redecl6.C: New test.
* g++.dg/contracts/contracts-redecl7.C: New test.
* g++.dg/contracts/contracts-redecl8.C: New test.
* g++.dg/contracts/contracts-tmpl-attr1.C: New test.
* g++.dg/contracts/contracts-tmpl-spec1.C: New test.
* g++.dg/contracts/contracts-tmpl-spec2.C: New test.
* g++.dg/contracts/contracts-tmpl-spec3.C: New test.
* g++.dg/contracts/contracts1.C: New test.
* g++.dg/contracts/contracts10.C: New test.
* g++.dg/contracts/contracts11.C: New test.
* g++.dg/contracts/contracts12.C: New test.
* g++.dg/contracts/contracts13.C: New test.
* g++.dg/contracts/contracts14.C: New test.
* g++.dg/contracts/contracts15.C: New test.
* g++.dg/contracts/contracts16.C: New test.
* g++.dg/contracts/contracts17.C: New test.
* g++.dg/contracts/contracts18.C: New test.
* g++.dg/contracts/contracts19.C: New test.
* g++.dg/contracts/contracts2.C: New test.
* g++.dg/contracts/contracts20.C: New test.
* g++.dg/contracts/contracts22.C: New test.
* g++.dg/contracts/contracts24.C: New test.
* g++.dg/contracts/contracts25.C: New test.
* g++.dg/contracts/contracts3.C: New test.
* g++.dg/contracts/contracts35.C: New test.
* g++.dg/contracts/contracts4.C: New test.
* g++.dg/contracts/contracts5.C: New test.
* g++.dg/contracts/contracts6.C: New test.
* g++.dg/contracts/contracts7.C: New test.
* g++.dg/contracts/contracts8.C: New test.
* g++.dg/contracts/contracts9.C: New test.
* g++.dg/modules/contracts-1_a.C: New test.
* g++.dg/modules/contracts-1_b.C: New test.
* g++.dg/modules/contracts-2_a.C: New test.
* g++.dg/modules/contracts-2_b.C: New test.
* g++.dg/modules/contracts-2_c.C: New test.
* g++.dg/modules/contracts-3_a.C: New test.
* g++.dg/modules/contracts-3_b.C: New test.
* g++.dg/modules/contracts-4_a.C: New test.
* g++.dg/modules/contracts-4_b.C: New test.
* g++.dg/modules/contracts-4_c.C: New test.
* g++.dg/modules/contracts-4_d.C: New test.
* g++.dg/modules/contracts-tpl-friend-1_a.C: New test.
* g++.dg/modules/contracts-tpl-friend-1_b.C: New test.
* g++.dg/contracts/backtrace_handler/Makefile: New test.
* g++.dg/contracts/backtrace_handler/README: New test.
* g++.dg/contracts/backtrace_handler/example_out.txt: New test.
* g++.dg/contracts/backtrace_handler/example_pretty.txt: New test.
* g++.dg/contracts/backtrace_handler/prettytrace.sh: New test.
* g++.dg/contracts/except_preload_handler/Makefile: New test.
* g++.dg/contracts/except_preload_handler/README: New test.
* g++.dg/contracts/except_preload_handler/assert_fail.cpp: New test.
* g++.dg/contracts/except_preload_handler/handle_contract_violation.cpp: New test.
* g++.dg/contracts/noexcept_preload_handler/Makefile: New test.
* g++.dg/contracts/noexcept_preload_handler/README: New test.
* g++.dg/contracts/noexcept_preload_handler/assert_fail.cpp: New test.
* g++.dg/contracts/noexcept_preload_handler/handle_contract_violation.cpp: New test.
* g++.dg/contracts/preload_handler/Makefile: New test.
* g++.dg/contracts/preload_handler/README: New test.
* g++.dg/contracts/preload_handler/assert_fail.cpp: New test.
* g++.dg/contracts/preload_handler/handle_contract_violation.cpp: New test.
* g++.dg/contracts/preload_nocontinue_handler/Makefile: New test.
* g++.dg/contracts/preload_nocontinue_handler/README: New test.
* g++.dg/contracts/preload_nocontinue_handler/assert_fail.cpp: New test.
* g++.dg/contracts/preload_nocontinue_handler/handle_contract_violation.cpp: New test.
* g++.dg/contracts/preload_nocontinue_handler/nocontinue.cpp: New test.
Diffstat (limited to 'gcc/cp/contracts.cc')
-rw-r--r-- | gcc/cp/contracts.cc | 2240 |
1 files changed, 2240 insertions, 0 deletions
diff --git a/gcc/cp/contracts.cc b/gcc/cp/contracts.cc new file mode 100644 index 0000000..2639643 --- /dev/null +++ b/gcc/cp/contracts.cc @@ -0,0 +1,2240 @@ +/* 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); + --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 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 <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" |