From: Iain Sandoe Date: Mon, 30 Oct 2023 11:12:56 +0000 (+0200) Subject: c++, contracts: C++26 base implementation as per P2900R14. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c928dc51966dc089e18b1010417a2d41f72a6ba9;p=thirdparty%2Fgcc.git c++, contracts: C++26 base implementation as per P2900R14. This implementation performs the contracts application in three phases: 1. Parsing and semantic checks. Most of the code for this is in the parser, with helpers provided here. 2. Emitting contract assertion AST nodes into function bodies. This is initiated from "finish_function ()" 3. Lowering the contract assertion AST nodes to control flow, constant data and calls to the violation handler. This is initiated from "cp_genericize ()". Contract Assertion State ======================== contract_assert () does not require any special handling and can be represented directly by AST inserted in the function body. 'pre' and 'post' function contract specifiers require most of the special handling, since they must be tracked across re-declarations of functions and there are contraints on how such specifiers may change in these cases. The contracts specification identifies a "first declaration" of any given function - which is the first encountered when parsing a given TU. Subsequent re-declarations may not add or change the function contract specifiers from any introduced on this first declaration. It is, however, permitted to omit specifiers on re-declarations. Since the implementation of GCC's (re-)declarations is a destructive merge we need to keep some state on the side to determine whether the re-declaration rules are met. In this current design we have chosen not to add another tree to each function decl but, instead, keep a map from function decl to contract specifier state. In this state we record the 'first declaration' specifiers which are used to validate re-declaration(s) and to report the initial state in diagnostics. We need (for example) to compare pre ( x > 2 ) equal to pre ( z > 2 ) when x and z refer to the same function parameter in a re-declaration. The mechanism used to determine if two contracts are the same is to compare the folded trees. This makes use of current compiler machinery, rather than constructing some new AST comparison scheme. However, it does introduce an additional complexity in that we need to defer such comparison until parsing is complete - and function contract specifiers in class declarations must be deferred parses, since it is also permitted for specifiers to refer to class members. When we encounter a definition, the parameter names in a function decl are re-written to match those of the definition (thus the expected names will appear in debug information etc). At this point, we also need to re-map any function parameter names that appear in function contract specifiers to agree with those of the definition - although we intend to keep the 'first declaration' record consistent for diagnostics. Since we shared some code from the C++2a contracts implementation, pre and post specifiers are represented by chains of attributes, where the payload of the attribute is an AST node. However during the parse, these are not inserted into the function bodies, but kept in the decl-keyed state described above. A future improvement planned here is to store the specifiers using a tree vec instead of the attribute list. Emitting contract AST ===================== When we reach `finish_function ()` and therefore are committed to potentially emitting code for an instance, we build a new variant of the function body with the pre-condition AST inserted before the user's function body, and the post condition AST (if any) linked into the function return. Lowering the contract assertion AST =================================== In all cases (pre, post, contract_assert) the AST node is lowered to control flow and (potentially) calls to the violation handler and/or termination. This is done during `cp_genericize ()`. In the current implementation, the decision on the control flow is made on the basis of the setting of a command- line flag that determines a TU-wide contract evaluation semantic, which has the following initial set of behaviours: 'ignore' : contract assertion AST is lowered to 'nothing', i.e. omitted. 'enforce' : contract assertion AST is lowered to a check, if this fails a violation handler is called, followed by std::terminate(). 'quick_enforce' : contract assertion AST is lowered to a check, if this fails, std::terminate () is called. 'observe' : contract assertion AST is lowered to a check, if this fails, a violation handler is called, the code then continues. In each case, the "check" might be a simple 'if' (when it is determined that the assertion condition does not throw) or the condition evaluation will be wrapped in a try-catch block that treats any exception thrown when evaluating the check as equivalent to a failed check. It is noted in the violation data object whether a check failed because of an exception raised in evaluation. At present, a simple (but potentially space-inefficient) scheme is used to store constant data objects that represent the read-only data for the violation. The exact form of this is subject to revision as it represents ABI that must be agreed between implementations (as of this point, that discussion is not yet concluded). gcc/c-family/ChangeLog: * c-common.cc: Add contract_assert keyword. * c-common.h (enum rid): Likewise. * c-cppbuiltin.cc (c_cpp_builtins): Add C++26 contracts feature test macro. * c.opt: Add a flag to control the TU-wide evaluation semantic. Add a flag to control whether P1494 observable_checkpoints are inserted to separate contract checks. * c.opt.urls: Regenerate. gcc/ChangeLog: * config/darwin.h (ASM_GENERATE_INTERNAL_LABEL): Add cases for contract constant data that need to be in independent link-time 'atoms'. * doc/invoke.texi: Document -fcontracts and -fcontract-evaluation-semantic=. gcc/cp/ChangeLog: * class.cc (check_for_override): Diagnose attemtps to add contracts to virtual functions. * constexpr.cc (cxx_eval_builtin_function_call): Handle observable_checkpoints emitted for contracts. (cxx_eval_constant_expression): Check we do not see contract assertions here... (potential_constant_expression_1): ... but that we do here. * contracts.cc: Implement base C++26 P2600R14 contracts. * contracts.h: Likewise. * cp-gimplify.cc (cp_genericize_r): Lower contract assertions to control flow and calls (where required) to the violation handler. (fold_builtin_source_location): Use revised source_location impl. constructor. (build_source_location_impl): Split out the core of the constructor of source_location so that it can be re-used from the contracts code. * cp-tree.def (ASSERTION_STMT, PRECONDITION_STMT, POSTCONDITION_STMT): Revise to allow space for specifying a semantic, an assertion kind, and (where required) a source location. * cp-tree.h (enum cp_tree_index, builtin_contract_violation_type): Add the contract violation object type. (struct saved_scope): Add a contracts class pointer. (processing_postcondition, contract_class_ptr): New. (struct cp_declarator): Add contract_specifiers. (build_call_a_1): New. (build_source_location_impl): New. * decl.cc (duplicate_decls): Check function contract specifiers on redeclarations. Handle contract specifiers on instantiations. (cxx_init_decl_processing): Initialise the terminate function since this can be called from contracts even when exception processing is disabled. Build the contract violation object layout. (start_decl): Handle checking contract postcondition identifiers. (grokfndecl): Handle function contract specifiers. (grokdeclarator): Likewise. (start_preparsed_function): Start function contracts where needed. (finish_function): Emit contract specifier AST corresponding to the checked contracts. * decl2.cc (c_parse_final_cleanups): Emit helpers for contract violation handler calls. * lex.cc (cxx_init): Add keyword warning for contract_assert. * parser.cc (make_call_declarator): Add contract specifiers. (unparsed_contracts): New. (cp_parser_lambda_introducer): Allow contract specifier lambdas to have a capture default. (cp_parser_lambda_declarator_opt): Parse function contract specifiers. (make_dummy_lambda_op): Account for contract specifiers in declarator. (cp_parser_lambda_body): We have to ensure that deferred contracts are parsed before we finish the lambda. (cp_parser_statement): Handle contract_assert and diagnostics for misplaced pre and post conditions. (attr_chainon): Make this defensive against being passed a missing attribute list. (cp_parser_using_directive): Use attr_chainon instead of chainon. (contract_attribute_p): New. (cp_parser_init_declarator): Handle contract specifier names in attributes. (cp_parser_direct_declarator): Rename attrs to std_attrs for these. (cp_parser_type_specifier_seq): Allow for function contract specifiers. (cp_parser_class_specifier): Handle deferred parsing for function contract specifiers (cp_next_tokens_can_be_contract_attribute_p): True if this can be a function contract specifier (which appear in the same position as the attribute variant). (cp_next_tokens_can_be_std_attribute_p): Allow for contracts. (cp_nth_tokens_can_be_std_attribute_p): Likewise. (cp_next_tokens_can_be_attribute_p): Likewise. (cp_parser_late_contract_condition, cp_parser_late_contracts, cp_parser_contract_assert, cp_parser_function_contract_specifier, cp_parser_function_contract_specifier_seq): New. (cp_parser_skip_std_attribute_spec_seq): Handle contracts case. (cp_parser_skip_attributes_opt): Likewise. (cp_parser_save_default_args): Handle unparsed contract specs. * pt.cc (check_explicit_specialization): Handle removing contract specifiers from instances. (tsubst_contract, tsubst_contract_attribute, tsubst_contract_attributes): New. (tsubst_function_decl): Set contract specs. on substituted func. (tsubst_stmt): Handle contract_assert. (tsubst_expr): Allow for uses of postcondition uses of parm names. (regenerate_decl_from_template): Handle function contract specs. * semantics.cc (set_one_cleanup_loc): Add a location to postcondition specifiers. (finish_non_static_data_member): Diagnose bad uses of members in contract specifiers. (finish_this_expr): Diagnose invalid use of this in contract specifiers. (process_outer_var_ref): Allow use of params in contract specs. (finish_id_expression_1): Likewise. (apply_deduced_return_type): Maybe update postconditions when the return type is changed. * tree.cc (cp_tree_equal): Handle special cases when comparing contracts. gcc/testsuite/ChangeLog: * g++.dg/warn/Wkeyword-macro-1.C: Update for contract_assert. * g++.dg/warn/Wkeyword-macro-2.C: Likewise. * g++.dg/warn/Wkeyword-macro-4.C: Likewise. * g++.dg/warn/Wkeyword-macro-5.C: Likewise. * g++.dg/warn/Wkeyword-macro-7.C: Likewise. * g++.dg/warn/Wkeyword-macro-8.C: Likewise. Co-Authored-by: Nina Ranns Co-Authored-by: Ville Voutilainen Signed-off-by: Iain Sandoe --- diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc index bf1cdaf34d3..7f891e1ef49 100644 --- a/gcc/c-family/c-common.cc +++ b/gcc/c-family/c-common.cc @@ -103,7 +103,7 @@ machine_mode c_default_pointer_mode = VOIDmode; tree dfloat32_type_node; tree dfloat64_type_node; tree dfloat128_type_node; - tree dfloat64x_type_node; + tree dfloat64x_type_node; tree intQI_type_node; tree intHI_type_node; @@ -472,6 +472,7 @@ const struct c_common_resword c_common_reswords[] = { "__const", RID_CONST, 0 }, { "__const__", RID_CONST, 0 }, { "__constinit", RID_CONSTINIT, D_CXXONLY }, + { "__contract_assert", RID_CONTASSERT, D_CXXONLY | D_CXXWARN }, { "__decltype", RID_DECLTYPE, D_CXXONLY }, { "__extension__", RID_EXTENSION, 0 }, { "__func__", RID_C99_FUNCTION_NAME, 0 }, @@ -519,6 +520,7 @@ const struct c_common_resword c_common_reswords[] = { "constinit", RID_CONSTINIT, D_CXXONLY | D_CXX20 | D_CXXWARN }, { "const_cast", RID_CONSTCAST, D_CXXONLY | D_CXXWARN }, { "continue", RID_CONTINUE, 0 }, + { "contract_assert", RID_CONTASSERT, D_CXXONLY | D_CXXWARN | D_CXX26 }, { "decltype", RID_DECLTYPE, D_CXXONLY | D_CXX11 | D_CXXWARN }, { "default", RID_DEFAULT, 0 }, { "delete", RID_DELETE, D_CXXONLY | D_CXXWARN }, diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h index 537b9acb068..89517e2a80c 100644 --- a/gcc/c-family/c-common.h +++ b/gcc/c-family/c-common.h @@ -189,6 +189,9 @@ enum rid /* C++ coroutines */ RID_CO_AWAIT, RID_CO_YIELD, RID_CO_RETURN, + /* C++26 */ + RID_CONTASSERT, + /* C++ transactional memory. */ RID_ATOMIC_NOEXCEPT, RID_ATOMIC_CANCEL, RID_SYNCHRONIZED, @@ -258,6 +261,8 @@ enum rid RID_LAST_CXX11 = RID_STATIC_ASSERT, RID_FIRST_CXX20 = RID_CONSTINIT, RID_LAST_CXX20 = RID_CO_RETURN, + RID_FIRST_CXX26 = RID_CONTASSERT, + RID_LAST_CXX26 = RID_CONTASSERT, RID_FIRST_AT = RID_AT_ENCODE, RID_LAST_AT = RID_AT_IMPLEMENTATION, RID_FIRST_PQ = RID_IN, diff --git a/gcc/c-family/c-cppbuiltin.cc b/gcc/c-family/c-cppbuiltin.cc index de768c527e1..12ddfa22074 100644 --- a/gcc/c-family/c-cppbuiltin.cc +++ b/gcc/c-family/c-cppbuiltin.cc @@ -1126,8 +1126,7 @@ c_cpp_builtins (cpp_reader *pfile) else if (cxx_dialect >= cxx20) cpp_warn (pfile, "__cpp_concepts"); if (flag_contracts) - { - } + cpp_define (pfile, "__cpp_contracts=202502L"); else if (cxx_dialect >= cxx26) cpp_warn (pfile, "__cpp_contracts"); if (flag_modules) diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt index 186d3b14792..1e8a4683f04 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -1902,7 +1902,28 @@ C++ ObjC++ Joined RejectNegative Host_Wide_Int Var(constexpr_ops_limit) Init(335 fcontracts C++ ObjC++ Var(flag_contracts) Init(0) -Enable certain features present in drafts of C++ Contracts. +Enable features proposed for C++26 Contracts. + +; Keep these in step with the values in cp/contracts.h + +Enum +Name(contract_semantic) Type(int) UnknownError(unrecognized contract evaluation semantic %qs) + +EnumValue +Enum(contract_semantic) String(ignore) Value(1) + +EnumValue +Enum(contract_semantic) String(observe) Value(2) + +EnumValue +Enum(contract_semantic) String(enforce) Value(3) + +EnumValue +Enum(contract_semantic) String(quick_enforce) Value(4) + +fcontract-evaluation-semantic= +C++ ObjC++ Joined RejectNegative Enum(contract_semantic) Var(flag_contract_evaluation_semantic) Init(3) +-fcontract-evaluation-semantic=[ignore|observe|enforce|quick_enforce] Select the contract evaluation semantic (defaults to enforce). fcoroutines C++ ObjC++ LTO Var(flag_coroutines) diff --git a/gcc/c-family/c.opt.urls b/gcc/c-family/c.opt.urls index cf186466ccf..d214b1a7d9e 100644 --- a/gcc/c-family/c.opt.urls +++ b/gcc/c-family/c.opt.urls @@ -1095,26 +1095,8 @@ UrlSuffix(gcc/C_002b_002b-Dialect-Options.html#index-fconstexpr-ops-limit) fcontracts UrlSuffix(gcc/C_002b_002b-Dialect-Options.html#index-fcontracts) -fcontract-assumption-mode= -UrlSuffix(gcc/C_002b_002b-Dialect-Options.html#index-fcontract-assumption-mode) - -fcontract-build-level= -UrlSuffix(gcc/C_002b_002b-Dialect-Options.html#index-fcontract-build-level) - -fcontract-strict-declarations= -UrlSuffix(gcc/C_002b_002b-Dialect-Options.html#index-fcontract-strict-declarations) - -fcontract-mode= -UrlSuffix(gcc/C_002b_002b-Dialect-Options.html#index-fcontract-mode) - -fcontract-continuation-mode= -UrlSuffix(gcc/C_002b_002b-Dialect-Options.html#index-fcontract-continuation-mode) - -fcontract-role= -UrlSuffix(gcc/C_002b_002b-Dialect-Options.html#index-fcontract-role) - -fcontract-semantic= -UrlSuffix(gcc/C_002b_002b-Dialect-Options.html#index-fcontract-semantic) +fcontract-evaluation-semantic= +UrlSuffix(gcc/C_002b_002b-Dialect-Options.html#index-fcontract-evaluation-semantic) fcoroutines UrlSuffix(gcc/C_002b_002b-Dialect-Options.html#index-fcoroutines) diff --git a/gcc/config/darwin.h b/gcc/config/darwin.h index b7379c8e099..53a7b5980e6 100644 --- a/gcc/config/darwin.h +++ b/gcc/config/darwin.h @@ -1022,6 +1022,12 @@ extern GTY(()) section * darwin_sections[NUM_DARWIN_SECTIONS]; sprintf (LABEL, "*%s%ld", "lTRAMP", (long)(NUM));\ else if (strncmp ("LANCHOR", PREFIX, 7) == 0) \ sprintf (LABEL, "*%s%ld", "lANCHOR", (long)(NUM));\ + else if (strlen (PREFIX) == 19 \ + && strncmp ("Lcontract_violation", PREFIX, 19) == 0) \ + sprintf (LABEL, "*%s%ld", "lcontract_violation", (long)(NUM));\ + else if (strlen (PREFIX) == 13 \ + && strncmp ("Lsrc_loc_impl", PREFIX, 13) == 0) \ + sprintf (LABEL, "*%s%ld", "lsrc_loc_impl", (long)(NUM));\ else \ sprintf (LABEL, "*%s%ld", PREFIX, (long)(NUM)); \ } while (0) diff --git a/gcc/cp/class.cc b/gcc/cp/class.cc index 6f00ca671e4..315ffd316ff 100644 --- a/gcc/cp/class.cc +++ b/gcc/cp/class.cc @@ -37,6 +37,7 @@ along with GCC; see the file COPYING3. If not see #include "gimplify.h" #include "intl.h" #include "asan.h" +#include "contracts.h" /* Id for dumping the class hierarchy. */ int class_dump_id; @@ -3254,6 +3255,10 @@ check_for_override (tree decl, tree ctype) if (DECL_DESTRUCTOR_P (decl)) TYPE_HAS_NONTRIVIAL_DESTRUCTOR (ctype) = true; + + if (DECL_HAS_CONTRACTS_P (decl)) + error_at (DECL_SOURCE_LOCATION (decl), + "contracts cannot be added to virtual functions"); } else if (DECL_FINAL_P (decl)) error ("%q+#D marked %, but is not virtual", decl); diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc index 8d9bd551402..1527e9dcbac 100644 --- a/gcc/cp/constexpr.cc +++ b/gcc/cp/constexpr.cc @@ -41,6 +41,7 @@ along with GCC; see the file COPYING3. If not see #include "fold-const.h" #include "intl.h" #include "toplev.h" +#include "contracts.h" static bool verify_constant (tree, bool, bool *, bool *); #define VERIFY_CONSTANT(X) \ @@ -1206,6 +1207,12 @@ public: unsigned heap_dealloc_count; /* Number of uncaught exceptions. */ unsigned uncaught_exceptions; + /* A contract statement that failed or was not constant, we only store the + first one that fails. */ + tree contract_statement; + /* [basic.contract.eval]/7.3 if this expression would otherwise be constant + then a non-const contract makes the program ill-formed. */ + bool contract_condition_non_const; /* Some metafunctions aren't dependent just on their arguments, but also on various other dependencies, e.g. has_identifier on a function parameter reflection can change depending on further declarations of corresponding @@ -1221,7 +1228,8 @@ public: constexpr_global_ctx () : constexpr_ops_count (0), cleanups (NULL), modifiable (nullptr), consteval_block (NULL_TREE), heap_dealloc_count (0), - uncaught_exceptions (0), metafns_called (false) {} + uncaught_exceptions (0), contract_statement (NULL_TREE), + contract_condition_non_const (false), metafns_called (false) {} bool is_outside_lifetime (tree t) { @@ -2392,6 +2400,7 @@ cxx_eval_builtin_function_call (const constexpr_ctx *ctx, tree t, tree fun, break; case BUILT_IN_ASAN_POINTER_COMPARE: case BUILT_IN_ASAN_POINTER_SUBTRACT: + case BUILT_IN_OBSERVABLE_CHKPT: /* These builtins shall be ignored during constant expression evaluation. */ return void_node; @@ -10324,7 +10333,47 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t, case ASSERTION_STMT: case PRECONDITION_STMT: case POSTCONDITION_STMT: - gcc_checking_assert (false && "hoo..."); + { + r = void_node; + /* Only record the first fail, and do not go further is the semantic + is 'ignore'. */ + if (*non_constant_p || ctx->global->contract_statement + || contract_ignored_p (t)) + break; + + tree cond = CONTRACT_CONDITION (t); + if (!potential_rvalue_constant_expression (cond)) + { + ctx->global->contract_statement = t; + ctx->global->contract_condition_non_const = true; + break; + } + + /* We need to evaluate and stash the result of this here, since whether + it needs to be reported (and how) depends on whether the containing + expression is otherwise const. */ + bool ctrct_non_const_p = false; + bool ctrct_overflow_p = false; + tree jmp_target = NULL_TREE; + constexpr_ctx new_ctx = *ctx; + new_ctx.quiet = true; + /* Avoid modification of existing values. */ + modifiable_tracker ms (new_ctx.global); + tree eval = + cxx_eval_constant_expression (&new_ctx, cond, vc_prvalue, + &ctrct_non_const_p, + &ctrct_overflow_p, &jmp_target); + /* Not a constant. */ + if (ctrct_non_const_p) + { + ctx->global->contract_statement = t; + ctx->global->contract_condition_non_const = true; + break; + } + /* Constant, but check failed. */ + if (integer_zerop (eval)) + ctx->global->contract_statement = t; + } break; case TEMPLATE_ID_EXPR: @@ -10549,6 +10598,41 @@ mark_non_constant (tree t) return t; } +/* If we have a successful constant evaluation, now check whether there is + a failed or non-constant contract that would invalidate this. */ + +static bool +check_for_failed_contracts (constexpr_global_ctx *global_ctx) +{ + if (!flag_contracts || !global_ctx->contract_statement) + return false; + + location_t loc = EXPR_LOCATION (global_ctx->contract_statement); + enum diagnostics::kind kind; + bool error = false; + /* [intro.compliance.general]/2.3.4. */ + /* [basic.contract.eval]/8. */ + if (contract_terminating_p (global_ctx->contract_statement)) + { + kind = diagnostics::kind::error; + error = true; + } + else + kind = diagnostics::kind::warning; + + /* [basic.contract.eval]/7.3 */ + if (global_ctx->contract_condition_non_const) + { + emit_diagnostic (kind, loc, 0, "contract condition is not constant"); + return error; + } + + /* Otherwise, the evaluation was const, but determined to be false. */ + emit_diagnostic (kind, loc, 0, + "contract predicate is false in constant expression"); + return error; +} + /* ALLOW_NON_CONSTANT is false if T is required to be a constant expression. STRICT has the same sense as for constant_value_1: true if we only allow conforming C++ constant expressions, or false if we want a constant value @@ -10908,7 +10992,10 @@ cxx_eval_outermost_constant_expr (tree t, bool allow_non_constant, if (constexpr_dtor) { - DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (object) = true; + if (check_for_failed_contracts (&global_ctx)) + r = mark_non_constant (r); + else + DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (object) = true; return r; } @@ -10958,6 +11045,8 @@ cxx_eval_outermost_constant_expr (tree t, bool allow_non_constant, if (location_t loc = EXPR_LOCATION (t)) protected_set_expr_location (r, loc); + if (check_for_failed_contracts (&global_ctx)) + r = mark_non_constant (r); return r; } @@ -12720,7 +12809,9 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, case ASSERTION_STMT: case PRECONDITION_STMT: case POSTCONDITION_STMT: - gcc_checking_assert (false && "hmm..."); + /* Contracts are not supposed to alter this; we have to check that this + is not violated at a later time. */ + return true; case LABEL_EXPR: t = LABEL_EXPR_LABEL (t); diff --git a/gcc/cp/contracts.cc b/gcc/cp/contracts.cc index 7da5f2114cf..205ea52774b 100644 --- a/gcc/cp/contracts.cc +++ b/gcc/cp/contracts.cc @@ -1,6 +1,12 @@ -/* Definitions for C++ contract levels +/* C++ contracts. + Copyright (C) 2020-2026 Free Software Foundation, Inc. - Contributed by Jeff Chapman II (jchapman@lock3software.com) + Originally by Jeff Chapman II (jchapman@lock3software.com) for proposed + C++20 contracts. + Rewritten for C++26 contracts by: + Nina Ranns (dinka.ranns@googlemail.com) + Iain Sandoe (iain@sandoe.co.uk) + Ville Voutilainen (ville.voutilainen@gmail.com). This file is part of GCC. @@ -17,3 +23,2052 @@ 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 . */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "cp-tree.h" +#include "stringpool.h" +#include "diagnostic.h" +#include "options.h" +#include "contracts.h" +#include "tree.h" +#include "tree-inline.h" +#include "attribs.h" +#include "tree-iterator.h" +#include "print-tree.h" +#include "stor-layout.h" +#include "intl.h" +#include "cgraph.h" +#include "opts.h" +#include "output.h" + +/* Design notes. + + There are three phases: + 1. Parsing and semantic checks. + Most of the code for this is in the parser, with helpers provided here. + 2. Emitting contract assertion AST nodes into function bodies. + This is initiated from "finish_function ()" + 3. Lowering the contract assertion AST nodes to control flow, constant + data and calls to the violation handler. + This is initiated from "cp_genericize ()". + + The organisation of the code in this file is intended to follow those three + phases where possible. + + Contract Assertion State + ======================== + + contract_assert () does not require any special handling and can be + represented directly by AST inserted in the function body. + + 'pre' and 'post' function contract specifiers require most of the special + handling, since they must be tracked across re-declarations of functions and + there are contraints on how such specifiers may change in these cases. + + The contracts specification identifies a "first declaration" of any given + function - which is the first encountered when parsing a given TU. + Subsequent re-declarations may not add or change the function contract + specifiers from any introduced on this first declaration. It is, however, + permitted to omit specifiers on re-declarations. + + Since the implementation of GCC's (re-)declarations is a destructive merge + we need to keep some state on the side to determine whether the re-declaration + rules are met. In this current design we have chosen not to add another tree + to each function decl but, instead, keep a map from function decl to contract + specifier state. In this state we record the 'first declaration' specifiers + which are used to validate re-declaration(s) and to report the initial state + in diagnostics. + + We need (for example) to compare + pre ( x > 2 ) equal to + pre ( z > 2 ) when x and z refer to the same function parameter in a + re-declaration. + + The mechanism used to determine if two contracts are the same is to compare + the folded trees. This makes use of current compiler machinery, rather than + constructing some new AST comparison scheme. However, it does introduce an + additional complexity in that we need to defer such comparison until parsing + is complete - and function contract specifiers in class declarations must be + deferred parses, since it is also permitted for specifiers to refer to class + members. + + When we encounter a definition, the parameter names in a function decl are + re-written to match those of the definition (thus the expected names will + appear in debug information etc). At this point, we also need to re-map + any function parameter names that appear in function contract specifiers + to agree with those of the definition - although we intend to keep the + 'first declaration' record consistent for diagnostics. + + Since we shared some code from the C++2a contracts implementation, pre and + post specifiers are represented by chains of attributes, where the payload + of the attribute is an AST node. However during the parse, these are not + inserted into the function bodies, but kept in the decl-keyed state described + above. A future improvement planned here is to store the specifiers using a + tree vec instead of the attribute list. + + Emitting contract AST + ===================== + + When we reach `finish_function ()` and therefore are committed to potentially + emitting code for an instance, we build a new variant of the function body + with the pre-condition AST inserted before the user's function body, and the + post condition AST (if any) linked into the function return. + + Lowering the contract assertion AST + =================================== + + In all cases (pre, post, contract_assert) the AST node is lowered to control + flow and (potentially) calls to the violation handler and/or termination. + This is done during `cp_genericize ()`. In the current implementation, the + decision on the control flow is made on the basis of the setting of a command- + line flag that determines a TU-wide contract evaluation semantic, which has + the following initial set of behaviours: + + 'ignore' : contract assertion AST is lowered to 'nothing', + i.e. omitted. + 'enforce' : contract assertion AST is lowered to a check, if this + fails a violation handler is called, followed by + std::terminate(). + 'quick_enforce' : contract assertion AST is lowered to a check, if this + fails, std::terminate () is called. + 'observe' : contract assertion AST is lowered to a check, if this + fails, a violation handler is called, the code then + continues. + + In each case, the "check" might be a simple 'if' (when it is determined that + the assertion condition does not throw) or the condition evaluation will be + wrapped in a try-catch block that treats any exception thrown when evaluating + the check as equivalent to a failed check. It is noted in the violation data + object whether a check failed because of an exception raised in evaluation. + + At present, a simple (but potentially space-inefficient) scheme is used to + store constant data objects that represent the read-only data for the + violation. The exact form of this is subject to revision as it represents + ABI that must be agreed between implementations (as of this point, that + discussion is not yet concluded). */ + +/* Contract matching. */ + +bool comparing_contracts; + +/* True if the contract is valid. */ + +static bool +contract_valid_p (tree contract) +{ + return CONTRACT_CONDITION (contract) != error_mark_node; +} + +/* True if the contract specifier is valid. */ + +static bool +contract_specifier_valid_p (tree specifier) +{ + return contract_valid_p (TREE_VALUE (TREE_VALUE (specifier))); +} + +/* Compare the contract conditions of OLD_CONTRACT and NEW_CONTRACT. + Returns false if the conditions are equivalent, and true otherwise. */ + +static bool +mismatched_contracts_p (tree old_contract, tree new_contract) +{ + /* 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), + "mismatched contract specifier in declaration"); + 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. */ + tree t1 = cp_fully_fold_init (CONTRACT_CONDITION (old_contract)); + tree t2 = cp_fully_fold_init (CONTRACT_CONDITION (new_contract)); + + /* Compare the contracts. */ + + bool saved_comparing_contracts = comparing_contracts; + comparing_contracts = true; + bool matching_p = cp_tree_equal (t1, t2); + comparing_contracts = saved_comparing_contracts; + + if (!matching_p) + { + auto_diagnostic_group d; + error_at (EXPR_LOCATION (CONTRACT_CONDITION (new_contract)), + "mismatched contract condition in declaration"); + inform (EXPR_LOCATION (CONTRACT_CONDITION (old_contract)), + "previous contract here"); + return true; + } + + return false; +} + +/* Compare the contract specifiers of OLDDECL and NEWDECL. Returns true + if the contracts match, and false if they differ. */ + +static bool +match_contract_specifiers (location_t oldloc, tree old_contracts, + location_t newloc, tree new_contracts) +{ + /* Contracts only match if they are both specified. */ + if (!old_contracts || !new_contracts) + return true; + + /* Compare each contract in turn. */ + while (old_contracts && new_contracts) + { + /* If either contract is ill-formed, skip the rest of the comparison, + since we've already diagnosed an error. */ + if (!contract_specifier_valid_p (new_contracts) + || !contract_specifier_valid_p (old_contracts)) + return false; + + if (mismatched_contracts_p (CONTRACT_STATEMENT (old_contracts), + CONTRACT_STATEMENT (new_contracts))) + return false; + old_contracts = TREE_CHAIN (old_contracts); + new_contracts = TREE_CHAIN (new_contracts); + } + + /* If we didn't compare all specifiers, the contracts don't match. */ + if (old_contracts || new_contracts) + { + auto_diagnostic_group d; + error_at (newloc, + "declaration has a different number of contracts than " + "previously declared"); + inform (oldloc, + new_contracts + ? "previous declaration with fewer contracts here" + : "previous declaration with more contracts here"); + return false; + } + + return true; +} + +/* Return true if CONTRACT is checked or assumed under the current build + configuration. */ + +static bool +contract_active_p (tree contract) +{ + return get_evaluation_semantic (contract) != CES_IGNORE; +} + +/* True if FNDECL has any checked or assumed contracts whose TREE_CODE is + C. */ + +static bool +has_active_contract_condition (tree fndecl, tree_code c) +{ + tree as = get_fn_contract_specifiers (fndecl); + for (; 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 FNDECL has any checked or assumed preconditions. */ + +static bool +has_active_preconditions (tree fndecl) +{ + return has_active_contract_condition (fndecl, PRECONDITION_STMT); +} + +/* True if FNDECL has any checked or assumed postconditions. */ + +static bool +has_active_postconditions (tree fndecl) +{ + return has_active_contract_condition (fndecl, POSTCONDITION_STMT); +} + +/* Return true if any contract in the CONTRACT list is checked or assumed + under the current build configuration. */ + +static bool +contract_any_active_p (tree fndecl) +{ + tree as = get_fn_contract_specifiers (fndecl); + for (; as; as = TREE_CHAIN (as)) + if (contract_active_p (TREE_VALUE (TREE_VALUE (as)))) + return true; + return false; +} + +/* Return true if any contract in CONTRACTS is not yet parsed. */ + +bool +contract_any_deferred_p (tree contracts) +{ + for (; contracts; contracts = TREE_CHAIN (contracts)) + if (CONTRACT_CONDITION_DEFERRED_P (CONTRACT_STATEMENT (contracts))) + return true; + return false; +} + +/* Returns true if function decl FNDECL has contracts and we need to + process them for the purposes of either building caller or definition + contract checks. + This function does not take into account whether caller or definition + side checking is enabled. Those checks will be done from the calling + function which will be able to determine whether it is doing caller + or definition contract handling. */ + +static bool +handle_contracts_p (tree fndecl) +{ + return (flag_contracts + && !processing_template_decl + && contract_any_active_p (fndecl)); +} + + +/* 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; +} + +/* Lookup a name in std::, or inject it. */ + +static tree +lookup_std_type (tree name_id) +{ + tree res_type = lookup_qualified_name + (std_node, name_id, LOOK_want::TYPE | LOOK_want::HIDDEN_FRIEND); + + if (TREE_CODE (res_type) == TYPE_DECL) + res_type = TREE_TYPE (res_type); + else + { + push_nested_namespace (std_node); + res_type = make_class_type (RECORD_TYPE); + create_implicit_typedef (name_id, res_type); + DECL_SOURCE_LOCATION (TYPE_NAME (res_type)) = BUILTINS_LOCATION; + DECL_CONTEXT (TYPE_NAME (res_type)) = current_namespace; + pushdecl_namespace_level (TYPE_NAME (res_type), /*hidden*/true); + pop_nested_namespace (std_node); + } + return res_type; +} + +/* Get constract_assertion_kind of the specified contract. Used when building + contract_violation object. */ + +static contract_assertion_kind +get_contract_assertion_kind (tree contract) +{ + if (CONTRACT_ASSERTION_KIND (contract)) + { + tree s = CONTRACT_ASSERTION_KIND (contract); + tree i = (TREE_CODE (s) == INTEGER_CST) ? s + : DECL_INITIAL (STRIP_NOPS (s)); + gcc_checking_assert (!type_dependent_expression_p (s) && i); + return (contract_assertion_kind) tree_to_uhwi (i); + } + + switch (TREE_CODE (contract)) + { + case ASSERTION_STMT: return CAK_ASSERT; + case PRECONDITION_STMT: return CAK_PRE; + case POSTCONDITION_STMT: return CAK_POST; + default: break; + } + + gcc_unreachable (); +} + +/* Get contract_evaluation_semantic of the specified contract. */ + +contract_evaluation_semantic +get_evaluation_semantic (const_tree contract) +{ + if (CONTRACT_EVALUATION_SEMANTIC (contract)) + { + tree s = CONTRACT_EVALUATION_SEMANTIC (contract); + tree i = (TREE_CODE (s) == INTEGER_CST) ? s + : DECL_INITIAL (STRIP_NOPS (s)); + gcc_checking_assert (!type_dependent_expression_p (s) && i); + switch (contract_evaluation_semantic ev = + (contract_evaluation_semantic) tree_to_uhwi (i)) + { + /* This needs to be kept in step with any added semantics. */ + case CES_IGNORE: + case CES_OBSERVE: + case CES_ENFORCE: + case CES_QUICK: + return ev; + default: + break; + } + } + + gcc_unreachable (); +} + +/* Get location of the last contract in the CONTRACTS tree chain. */ + +static location_t +get_contract_end_loc (tree contracts) +{ + tree last = NULL_TREE; + for (tree a = contracts; a; a = TREE_CHAIN (a)) + last = a; + gcc_checking_assert (last); + last = CONTRACT_STATEMENT (last); + return EXPR_LOCATION (last); +} + +struct GTY(()) contract_decl +{ + tree contract_specifiers; + location_t note_loc; +}; + +static GTY(()) hash_map *contract_decl_map; + +/* Converts a contract condition to bool and ensures it has a location. */ + +tree +finish_contract_condition (cp_expr condition) +{ + if (!condition || error_operand_p (condition)) + return 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. This differs from maybe_wrap_with_location in that + it allows wrappers on EXCEPTIONAL_CLASS_P which includes CONSTRUCTORs. */ + if (!CAN_HAVE_LOCATION_P (condition) + && condition.get_location () != UNKNOWN_LOCATION) + { + tree_code code + = (((CONSTANT_CLASS_P (condition) && TREE_CODE (condition) != STRING_CST) + || (TREE_CODE (condition) == CONST_DECL && !TREE_STATIC (condition))) + ? NON_LVALUE_EXPR : VIEW_CONVERT_EXPR); + condition = build1_loc (condition.get_location (), code, + TREE_TYPE (condition), condition); + EXPR_LOCATION_WRAPPER_P (condition) = true; + } + + if (type_dependent_expression_p (condition)) + return condition; + + return condition_conversion (condition); +} + +void +maybe_update_postconditions (tree fndecl) +{ + /* Update any postconditions and the postcondition checking function + as needed. If there are postconditions, we'll use those to rewrite + return statements to check postconditions. */ + if (has_active_postconditions (fndecl)) + rebuild_postconditions (fndecl); +} + +void +start_function_contracts (tree fndecl) +{ + if (error_operand_p (fndecl)) + return; + + if (!handle_contracts_p (fndecl)) + return; + + /* Check that the user did not try to shadow a function parameter with the + specified postcondition result name. */ + for (tree ca = get_fn_contract_specifiers (fndecl); ca; ca = TREE_CHAIN (ca)) + if (POSTCONDITION_P (CONTRACT_STATEMENT (ca))) + if (tree id = POSTCONDITION_IDENTIFIER (CONTRACT_STATEMENT (ca))) + { + if (id == error_mark_node) + { + CONTRACT_CONDITION (CONTRACT_STATEMENT (ca)) = error_mark_node; + continue; + } + tree r_name = tree_strip_any_location_wrapper (id); + if (TREE_CODE (id) == PARM_DECL) + r_name = DECL_NAME (id); + gcc_checking_assert (r_name && TREE_CODE (r_name) == IDENTIFIER_NODE); + tree seen = lookup_name (r_name); + if (seen + && TREE_CODE (seen) == PARM_DECL + && DECL_CONTEXT (seen) == fndecl) + { + auto_diagnostic_group d; + location_t id_l = location_wrapper_p (id) + ? EXPR_LOCATION (id) + : DECL_SOURCE_LOCATION (id); + location_t co_l = EXPR_LOCATION (CONTRACT_STATEMENT (ca)); + if (id_l != UNKNOWN_LOCATION) + co_l = make_location (id_l, co_l, co_l); + error_at (co_l, + "contract postcondition result name shadows a" + " function parameter"); + inform (DECL_SOURCE_LOCATION (seen), + "parameter declared here"); + POSTCONDITION_IDENTIFIER (CONTRACT_STATEMENT (ca)) + = error_mark_node; + CONTRACT_CONDITION (CONTRACT_STATEMENT (ca)) = error_mark_node; + } + } +} + +/* Allow specifying a sub-set of contract kinds to copy. */ + +enum contract_match_kind +{ + cmk_pre, + cmk_post, + cmk_all +}; + +/* Copy (possibly a sub-set of) contracts from CONTRACTS on FNDECL. */ + +static tree +copy_contracts_list (tree contracts, tree fndecl, + contract_match_kind remap_kind = cmk_all) +{ + tree last = NULL_TREE, new_contracts = NULL_TREE; + for (; contracts; contracts = TREE_CHAIN (contracts)) + { + if ((remap_kind == cmk_pre + && (TREE_CODE (CONTRACT_STATEMENT (contracts)) + == POSTCONDITION_STMT)) + || (remap_kind == cmk_post + && (TREE_CODE (CONTRACT_STATEMENT (contracts)) + == PRECONDITION_STMT))) + continue; + + tree c = copy_node (contracts); + TREE_VALUE (c) = build_tree_list (TREE_PURPOSE (TREE_VALUE (c)), + copy_node (CONTRACT_STATEMENT (c))); + + copy_body_data id; + hash_map decl_map; + + memset (&id, 0, sizeof (id)); + + id.src_fn = fndecl; + id.dst_fn = fndecl; + id.src_cfun = DECL_STRUCT_FUNCTION (fndecl); + id.decl_map = &decl_map; + + id.copy_decl = retain_decl; + + 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; + walk_tree (&CONTRACT_CONDITION (CONTRACT_STATEMENT (c)), + copy_tree_body_r, &id, NULL); + + + CONTRACT_COMMENT (CONTRACT_STATEMENT (c)) + = copy_node (CONTRACT_COMMENT (CONTRACT_STATEMENT (c))); + + chainon (last, c); + last = c; + if (!new_contracts) + new_contracts = c; + } + return new_contracts; +} + +/* Returns a copy of FNDECL contracts. This is used when emiting a contract. + If we were to emit the original contract tree, any folding of the contract + condition would affect the original contract too. The original contract + tree needs to be preserved in case it is used to apply to a different + function (for inheritance or wrapping reasons). */ + +static tree +copy_contracts (tree fndecl, contract_match_kind remap_kind = cmk_all) +{ + tree contracts = get_fn_contract_specifiers (fndecl); + return copy_contracts_list (contracts, fndecl, remap_kind); +} + +/* Add the contract statement CONTRACT to the current block if valid. */ + +static bool +emit_contract_statement (tree contract) +{ + /* Only add valid contracts. */ + if (contract == error_mark_node + || CONTRACT_CONDITION (contract) == error_mark_node) + return false; + + if (get_evaluation_semantic (contract) == CES_INVALID) + return false; + + add_stmt (contract); + return true; +} + +/* Generate the statement for the given contract by adding the + statement to the current block. Returns the next contract in the chain. */ + +static tree +emit_contract (tree contract) +{ + gcc_assert (TREE_CODE (contract) == TREE_LIST); + + emit_contract_statement (CONTRACT_STATEMENT (contract)); + + return TREE_CHAIN (contract); +} + +/* Add a call or a direct evaluation of the pre checks. */ + +static void +apply_preconditions (tree fndecl) +{ + tree contract_copy = copy_contracts (fndecl, cmk_pre); + for (; contract_copy; contract_copy = TREE_CHAIN (contract_copy)) + emit_contract (contract_copy); +} + +/* Add a call or a direct evaluation of the post checks. */ + +static void +apply_postconditions (tree fndecl) +{ + tree contract_copy = copy_contracts (fndecl, cmk_post); + for (; contract_copy; contract_copy = TREE_CHAIN (contract_copy)) + emit_contract (contract_copy); +} + +/* Add contract handling to the function in FNDECL. + + When we have only pre-conditions, this simply prepends a call (or a direct + evaluation, for cdtors) to the existing function body. + + When we have post conditions we build a try-finally block. + If the function might throw then the handler in the try-finally is an + EH_ELSE expression, where the post condition check is applied to the + non-exceptional path, and an empty statement is added to the EH path. If + the function has a non-throwing eh spec, then the handler is simply the + post-condition checker. */ + +void +maybe_apply_function_contracts (tree fndecl) +{ + if (!handle_contracts_p (fndecl)) + /* We did nothing and the original function body statement list will be + popped by our caller. */ + return; + + bool do_pre = has_active_preconditions (fndecl); + bool do_post = has_active_postconditions (fndecl); + /* We should not have reached here with nothing to do... */ + gcc_checking_assert (do_pre || do_post); + + /* If the function is noexcept, the user's written body will be wrapped in a + MUST_NOT_THROW expression. In that case we leave the MUST_NOT_THROW in + place and do our replacement inside it. */ + tree fnbody; + if (TYPE_NOEXCEPT_P (TREE_TYPE (fndecl))) + { + tree m_n_t_expr = expr_first (DECL_SAVED_TREE (fndecl)); + gcc_checking_assert (TREE_CODE (m_n_t_expr) == MUST_NOT_THROW_EXPR); + fnbody = TREE_OPERAND (m_n_t_expr, 0); + TREE_OPERAND (m_n_t_expr, 0) = push_stmt_list (); + } + else + { + fnbody = DECL_SAVED_TREE (fndecl); + DECL_SAVED_TREE (fndecl) = push_stmt_list (); + } + + /* Now add the pre and post conditions to the existing function body. + This copies the approach used for function try blocks. */ + tree compound_stmt = begin_compound_stmt (0); + current_binding_level->artificial = true; + + /* Do not add locations for the synthesised code. */ + location_t loc = UNKNOWN_LOCATION; + + /* For other cases, we call a function to process the check. */ + + /* If we have a pre, but not a post, then just emit that and we are done. */ + if (!do_post) + { + apply_preconditions (fndecl); + add_stmt (fnbody); + finish_compound_stmt (compound_stmt); + return; + } + + if (do_pre) + /* Add a precondition call, if we have one. */ + apply_preconditions (fndecl); + tree try_fin = build_stmt (loc, TRY_FINALLY_EXPR, fnbody, NULL_TREE); + add_stmt (try_fin); + TREE_OPERAND (try_fin, 1) = push_stmt_list (); + /* If we have exceptions, and a function that might throw, then add + an EH_ELSE clause that allows the exception to propagate upwards + without encountering the post-condition checks. */ + if (flag_exceptions && !type_noexcept_p (TREE_TYPE (fndecl))) + { + tree eh_else = build_stmt (loc, EH_ELSE_EXPR, NULL_TREE, NULL_TREE); + add_stmt (eh_else); + TREE_OPERAND (eh_else, 0) = push_stmt_list (); + apply_postconditions (fndecl); + TREE_OPERAND (eh_else, 0) = pop_stmt_list (TREE_OPERAND (eh_else, 0)); + TREE_OPERAND (eh_else, 1) = void_node; + } + else + apply_postconditions (fndecl); + TREE_OPERAND (try_fin, 1) = pop_stmt_list (TREE_OPERAND (try_fin, 1)); + finish_compound_stmt (compound_stmt); + /* The DECL_SAVED_TREE stmt list will be popped by our caller. */ +} + +/* 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. + + 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 + contracts in terms of a new set of parameters. This also preserves the + references to postcondition results, which are not replaced during + merging. */ + +static void +remap_contract (tree src, tree dst, tree contract, bool duplicate_p) +{ + copy_body_data id; + hash_map decl_map; + + memset (&id, 0, sizeof (id)); + id.src_fn = src; + id.dst_fn = dst; + id.src_cfun = DECL_STRUCT_FUNCTION (src); + id.decl_map = &decl_map; + + /* If we're merging contracts, don't copy local variables. */ + id.copy_decl = duplicate_p ? retain_decl : copy_decl_no_change; + + id.transform_call_graph_edges = CB_CGE_DUPLICATE; + id.transform_new_cfg = false; + id.transform_return_to_modify = false; + id.transform_parameter = true; + + /* Make sure not to unshare trees behind the front-end's back + since front-end specific mechanisms may rely on sharing. */ + id.regimplify = false; + id.do_not_unshare = true; + id.do_not_fold = true; + + /* We're not inside any EH region. */ + id.eh_lp_nr = 0; + + bool do_remap = false; + + /* Insert parameter remappings. */ + gcc_checking_assert (TREE_CODE (src) == FUNCTION_DECL); + gcc_checking_assert (TREE_CODE (dst) == FUNCTION_DECL); + + int src_num_artificial_args = num_artificial_parms_for (src); + int dst_num_artificial_args = num_artificial_parms_for (dst); + + for (tree sp = DECL_ARGUMENTS (src), dp = DECL_ARGUMENTS (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; + + /* First artificial arg is *this. We want to remap that. However, we + want to skip _in_charge param and __vtt_parm. Do so now. */ + if (src_num_artificial_args > 0) + { + while (--src_num_artificial_args,src_num_artificial_args > 0) + sp = DECL_CHAIN (sp); + } + if (dst_num_artificial_args > 0) + { + while (--dst_num_artificial_args,dst_num_artificial_args > 0) + dp = DECL_CHAIN (dp); + } + } + + if (!do_remap) + return; + + walk_tree (&CONTRACT_CONDITION (contract), copy_tree_body_r, &id, NULL); +} + +/* Returns a copy of SOURCE contracts where any references to SOURCE's + PARM_DECLs have been rewritten to the corresponding PARM_DECL in DEST. */ + +tree +copy_and_remap_contracts (tree dest, tree source) +{ + tree last = NULL_TREE, contracts_copy= NULL_TREE; + tree contracts = get_fn_contract_specifiers (source); + for (; contracts; contracts = TREE_CHAIN (contracts)) + { + /* The first part is copying of the legacy attribute layout - eventually + this will go away. */ + tree c = copy_node (contracts); + TREE_VALUE (c) = build_tree_list (TREE_PURPOSE (TREE_VALUE (c)), + copy_node (CONTRACT_STATEMENT (c))); + /* This is the copied contract statement. */ + tree stmt = CONTRACT_STATEMENT (c); + + /* If we have an erroneous postcondition identifier, we also mark the + condition as invalid so only need to check that. */ + if (CONTRACT_CONDITION (stmt) != error_mark_node) + remap_contract (source, dest, stmt, /*duplicate_p=*/true); + + if (TREE_CODE (stmt) == POSTCONDITION_STMT) + { + /* If we have a postcondition return value placeholder, then + ensure the copied one has the correct context. */ + tree var = POSTCONDITION_IDENTIFIER (stmt); + if (var && var != error_mark_node) + DECL_CONTEXT (var) = dest; + } + + if (CONTRACT_COMMENT (stmt) != error_mark_node) + CONTRACT_COMMENT (stmt) = copy_node (CONTRACT_COMMENT (stmt)); + + chainon (last, c); + last = c; + if (!contracts_copy) + contracts_copy = c; + } + + return contracts_copy; +} + +/* Set the (maybe) parsed contract specifier LIST for DECL. */ + +void +set_fn_contract_specifiers (tree decl, tree list) +{ + if (!decl || error_operand_p (decl)) + return; + + bool existed = false; + contract_decl& rd + = hash_map_safe_get_or_insert (contract_decl_map, decl, &existed); + if (!existed) + { + /* This is the first time we encountered this decl, save the location + for error messages. This will ensure all error messages refer to the + contracts used for the function. */ + location_t decl_loc = DECL_SOURCE_LOCATION (decl); + location_t cont_end = decl_loc; + if (list) + cont_end = get_contract_end_loc (list); + rd.note_loc = make_location (decl_loc, decl_loc, cont_end); + } + rd.contract_specifiers = list; +} + +/* Update the entry for DECL in the map of contract specifiers with the + contracts in LIST. */ + +void +update_fn_contract_specifiers (tree decl, tree list) +{ + if (!decl || error_operand_p (decl)) + return; + + bool existed = false; + contract_decl& rd + = hash_map_safe_get_or_insert (contract_decl_map, decl, &existed); + gcc_checking_assert (existed); + + /* We should only get here when we parse deferred contracts. */ + gcc_checking_assert (!contract_any_deferred_p (list)); + + rd.contract_specifiers = list; +} + +/* When a decl is about to be removed, then we need to release its content and + then take it out of the map. */ + +void +remove_decl_with_fn_contracts_specifiers (tree decl) +{ + if (contract_decl *p = hash_map_safe_get (contract_decl_map, decl)) + { + p->contract_specifiers = NULL_TREE; + contract_decl_map->remove (decl); + } +} + +/* If this function has contract specifiers, then remove them, but leave the + function registered. */ + +void +remove_fn_contract_specifiers (tree decl) +{ + if (contract_decl *p = hash_map_safe_get (contract_decl_map, decl)) + { + p->contract_specifiers = NULL_TREE; + } +} + +/* Get the contract specifier list for this DECL if there is one. */ + +tree +get_fn_contract_specifiers (tree decl) +{ + if (contract_decl *p = hash_map_safe_get (contract_decl_map, decl)) + return p->contract_specifiers; + return NULL_TREE; +} + +/* A subroutine of duplicate_decls. Diagnose issues in the redeclaration of + guarded functions. */ + +void +check_redecl_contract (tree newdecl, tree olddecl) +{ + if (!flag_contracts) + return; + + if (TREE_CODE (newdecl) == TEMPLATE_DECL) + newdecl = DECL_TEMPLATE_RESULT (newdecl); + if (TREE_CODE (olddecl) == TEMPLATE_DECL) + olddecl = DECL_TEMPLATE_RESULT (olddecl); + + tree new_contracts = get_fn_contract_specifiers (newdecl); + tree old_contracts = get_fn_contract_specifiers (olddecl); + + if (!old_contracts && !new_contracts) + return; + + /* We should always be comparing with the 'first' declaration which should + have been recorded already (if it has contract specifiers). However + if the new decl is trying to add contracts, that is an error and we do + not want to create a map entry yet. */ + contract_decl *rdp = hash_map_safe_get (contract_decl_map, olddecl); + gcc_checking_assert(rdp || !old_contracts); + + location_t new_loc = DECL_SOURCE_LOCATION (newdecl); + if (new_contracts && !old_contracts) + { + auto_diagnostic_group d; + /* If a re-declaration has contracts, they must be the same as those + that appear on the first declaration seen (they cannot be added). */ + location_t cont_end = get_contract_end_loc (new_contracts); + cont_end = make_location (new_loc, new_loc, cont_end); + error_at (cont_end, "declaration adds contracts to %q#D", olddecl); + inform (DECL_SOURCE_LOCATION (olddecl), "first declared here"); + return; + } + + if (old_contracts && !new_contracts) + return; + else if (old_contracts && new_contracts + && !contract_any_deferred_p (old_contracts) + && contract_any_deferred_p (new_contracts) + && DECL_UNIQUE_FRIEND_P (newdecl)) + { + /* Put the defered contracts on the olddecl so we parse it when + we can. */ + set_fn_contract_specifiers (olddecl, old_contracts); + } + else if (contract_any_deferred_p (old_contracts) + || contract_any_deferred_p (new_contracts)) + { + /* TODO: ignore these and figure out how to process them later. */ + /* Note that a friend declaration has deferred contracts, but the + declaration of the same function outside the class definition + doesn't. */ + } + else + { + gcc_checking_assert (old_contracts); + location_t cont_end = get_contract_end_loc (new_contracts); + cont_end = make_location (new_loc, new_loc, cont_end); + /* We have two sets - they should match or we issue a diagnostic. */ + match_contract_specifiers (rdp->note_loc, old_contracts, + cont_end, new_contracts); + } + + return; +} + +/* Update the contracts of DEST to match the argument names from contracts + of SRC. When we merge two declarations in duplicate_decls, we preserve the + arguments from the new declaration, if the new declaration is a + definition. We need to update the contracts accordingly. */ + +void +update_contract_arguments (tree srcdecl, tree destdecl) +{ + tree src_contracts = get_fn_contract_specifiers (srcdecl); + tree dest_contracts = get_fn_contract_specifiers (destdecl); + + if (!src_contracts && !dest_contracts) + return; + + /* Check if src even has contracts. It is possible that a redeclaration + does not have contracts. Is this is the case, first apply contracts + to src. */ + if (!src_contracts) + { + if (contract_any_deferred_p (dest_contracts)) + { + set_fn_contract_specifiers (srcdecl, dest_contracts); + /* Nothing more to do here. */ + return; + } + else + set_fn_contract_specifiers + (srcdecl, copy_and_remap_contracts (srcdecl, destdecl)); + } + + /* For deferred contracts, we currently copy the tokens from the redeclaration + onto the decl that will be preserved. This is not ideal because the + redeclaration may have erroneous contracts. + For non deferred contracts we currently do copy and remap, which is doing + more than we need. */ + if (contract_any_deferred_p (src_contracts)) + set_fn_contract_specifiers (destdecl, src_contracts); + else + { + /* Temporarily rename the arguments to get the right mapping. */ + tree tmp_arguments = DECL_ARGUMENTS (destdecl); + DECL_ARGUMENTS (destdecl) = DECL_ARGUMENTS (srcdecl); + set_fn_contract_specifiers (destdecl, + copy_and_remap_contracts (destdecl, srcdecl)); + DECL_ARGUMENTS (destdecl) = tmp_arguments; + } +} + +/* Mark most of a contract as being invalid. */ + +tree +invalidate_contract (tree contract) +{ + if (TREE_CODE (contract) == POSTCONDITION_STMT + && POSTCONDITION_IDENTIFIER (contract)) + POSTCONDITION_IDENTIFIER (contract) = error_mark_node; + CONTRACT_CONDITION (contract) = error_mark_node; + CONTRACT_COMMENT (contract) = error_mark_node; + return contract; +} + +/* Returns an invented parameter declaration 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; + gcc_checking_assert (scope_chain && scope_chain->bindings + && scope_chain->bindings->kind == sk_contract); + + tree decl = build_lang_decl (PARM_DECL, id, type); + DECL_ARTIFICIAL (decl) = true; + DECL_SOURCE_LOCATION (decl) = id.get_location (); + return pushdecl (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 on + function decl FNDECL. Emit a diagnostic if it is not. Returns TRUE if + the result is OK and false otherwise. */ + +bool +check_postcondition_result (tree fndecl, tree type, location_t loc) +{ + /* Do not be confused by targetm.cxx.cdtor_return_this (); + conceptually, cdtors have no return value. */ + if (VOID_TYPE_P (type) + || DECL_CONSTRUCTOR_P (fndecl) + || DECL_DESTRUCTOR_P (fndecl)) + { + error_at (loc, + DECL_CONSTRUCTOR_P (fndecl) + ? G_("constructor does not return a value to test") + : DECL_DESTRUCTOR_P (fndecl) + ? G_("destructor does not return a value to test") + : G_("function does not return a value to test")); + return false; + } + + return true; +} + +/* Instantiate each postcondition with the return type to finalize the + contract specifiers on a function decl FNDECL. */ + +void +rebuild_postconditions (tree fndecl) +{ + if (!fndecl || fndecl == error_mark_node) + return; + + tree type = TREE_TYPE (TREE_TYPE (fndecl)); + + /* If the return type is undeduced, defer until later. */ + if (TREE_CODE (type) == TEMPLATE_TYPE_PARM) + return; + + tree contract_spec = get_fn_contract_specifiers (fndecl); + for (; contract_spec ; contract_spec = TREE_CHAIN (contract_spec)) + { + tree contract = TREE_VALUE (TREE_VALUE (contract_spec)); + if (TREE_CODE (contract) != POSTCONDITION_STMT) + continue; + tree condition = CONTRACT_CONDITION (contract); + if (!condition || condition == error_mark_node) + continue; + + /* 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; + + gcc_checking_assert (!DECL_CONTEXT (oldvar) + || DECL_CONTEXT (oldvar) == fndecl); + DECL_CONTEXT (oldvar) = fndecl; + + /* Check the postcondition variable. */ + location_t loc = DECL_SOURCE_LOCATION (oldvar); + if (!check_postcondition_result (fndecl, type, loc)) + { + invalidate_contract (contract); + continue; + } + + /* "Instantiate" the result variable using the known type. */ + 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 (fndecl); t != NULL_TREE; t = TREE_CHAIN (t)) + register_local_identity (t); + register_local_specialization (newvar, oldvar); + + begin_scope (sk_contract, fndecl); + bool old_pc = processing_postcondition; + processing_postcondition = true; + + condition = tsubst_expr (condition, make_tree_vec (0), + tf_warning_or_error, fndecl); + + /* Update the contract condition and result. */ + POSTCONDITION_IDENTIFIER (contract) = newvar; + CONTRACT_CONDITION (contract) = finish_contract_condition (condition); + processing_postcondition = old_pc; + gcc_checking_assert (scope_chain && scope_chain->bindings + && scope_chain->bindings->kind == sk_contract); + pop_bindings_and_leave_scope (); + } +} + +/* Make a string of the contract condition, if it is available. */ + +static tree +build_comment (cp_expr condition) +{ + /* Try to get the actual source text for the condition; if that fails pretty + print the resulting tree. */ + char *str = get_source_text_between (global_dc->get_file_cache (), + condition.get_start (), + condition.get_finish ()); + if (!str) + { + 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 contract_spec, tree mode, tree result, cp_expr condition, + location_t loc) +{ + if (condition == error_mark_node) + return error_mark_node; + + tree_code code; + contract_assertion_kind kind = CAK_INVALID; + if (id_equal (contract_spec, "contract_assert")) + { + code = ASSERTION_STMT; + kind = CAK_ASSERT; + } + else if (id_equal (contract_spec, "pre")) + { + code = PRECONDITION_STMT; + kind = CAK_PRE; + } + else if (id_equal (contract_spec,"post")) + { + code = POSTCONDITION_STMT; + kind = CAK_POST; + } + 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; + if (code != POSTCONDITION_STMT) + contract = build5_loc (loc, code, void_type_node, mode, + NULL_TREE, NULL_TREE, NULL_TREE, NULL_TREE); + else + { + contract = build_nt (code, mode, NULL_TREE, NULL_TREE, + NULL_TREE, NULL_TREE, result); + TREE_TYPE (contract) = void_type_node; + SET_EXPR_LOCATION (contract, loc); + } + + /* Determine the assertion kind. */ + CONTRACT_ASSERTION_KIND (contract) = build_int_cst (uint16_type_node, kind); + + /* Determine the evaluation semantic. This is now an override, so that if + not set we will get the default (currently enforce). */ + CONTRACT_EVALUATION_SEMANTIC (contract) + = build_int_cst (uint16_type_node, (uint16_t) + flag_contract_evaluation_semantic); + + /* 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); + + if (condition == error_mark_node) + return error_mark_node; + + CONTRACT_CONDITION (contract) = condition; + + return contract; +} + +/* Build the contract specifier where IDENTIFIER is one of 'pre', + 'post' or 'assert' and CONTRACT is the underlying statement. */ + +tree +finish_contract_specifier (tree identifier, tree contract) +{ + if (contract == error_mark_node) + return error_mark_node; + + tree contract_spec = build_tree_list (build_tree_list (NULL_TREE, identifier), + build_tree_list (NULL_TREE, contract)); + + /* Mark the contract as dependent if the condition is dependent. */ + tree condition = CONTRACT_CONDITION (contract); + if (TREE_CODE (condition) != DEFERRED_PARSE + && value_dependent_expression_p (condition)) + ATTR_IS_DEPENDENT (contract_spec) = true; + + return contract_spec; +} + +/* Update condition of a late-parsed contract and postcondition variable, + if any. */ + +void +update_late_contract (tree contract, tree result, cp_expr 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; +} + +/* ===== Code generation ===== */ + +/* Insert a BUILT_IN_OBSERVABLE_CHECKPOINT epoch marker. */ + +static void +emit_builtin_observable_checkpoint () +{ + tree fn = builtin_decl_explicit (BUILT_IN_OBSERVABLE_CHKPT); + releasing_vec vec; + fn = finish_call_expr (fn, &vec, false, false, tf_warning_or_error); + finish_expr_stmt (fn); +} + +/* Shared code between TU-local wrappers for the violation handler. */ + +static tree +declare_one_violation_handler_wrapper (tree fn_name, tree fn_type, + tree p1_type, tree p2_type) +{ + location_t loc = BUILTINS_LOCATION; + tree fn_decl = build_lang_decl_loc (loc, FUNCTION_DECL, fn_name, fn_type); + DECL_CONTEXT (fn_decl) = FROB_CONTEXT (global_namespace); + DECL_ARTIFICIAL (fn_decl) = true; + DECL_INITIAL (fn_decl) = error_mark_node; + /* Let the start function code fill in the result decl. */ + DECL_RESULT (fn_decl) = NULL_TREE; + /* Two args violation ref, dynamic info. */ + tree parms = cp_build_parm_decl (fn_decl, NULL_TREE, p1_type); + TREE_USED (parms) = true; + DECL_READ_P (parms) = true; + tree p2 = cp_build_parm_decl (fn_decl, NULL_TREE, p2_type); + TREE_USED (p2) = true; + DECL_READ_P (p2) = true; + DECL_CHAIN (parms) = p2; + DECL_ARGUMENTS (fn_decl) = parms; + /* Make this function internal. */ + TREE_PUBLIC (fn_decl) = false; + DECL_EXTERNAL (fn_decl) = false; + DECL_WEAK (fn_decl) = false; + return fn_decl; +} + +static GTY(()) tree tu_has_violation = NULL_TREE; +static GTY(()) tree tu_has_violation_exception = NULL_TREE; + +static void +maybe_declare_violation_handler_wrappers () +{ + if (tu_has_violation && tu_has_violation_exception) + return; + + iloc_sentinel ils (input_location); + input_location = BUILTINS_LOCATION; + tree v_obj_type = builtin_contract_violation_type; + v_obj_type = cp_build_qualified_type (v_obj_type, TYPE_QUAL_CONST); + v_obj_type = cp_build_reference_type (v_obj_type, /*rval*/false); + tree fn_type = build_function_type_list (void_type_node, v_obj_type, + uint16_type_node, NULL_TREE); + tree fn_name = get_identifier ("__tu_has_violation_exception"); + tu_has_violation_exception + = declare_one_violation_handler_wrapper (fn_name, fn_type, v_obj_type, + uint16_type_node); + fn_name = get_identifier ("__tu_has_violation"); + tu_has_violation + = declare_one_violation_handler_wrapper (fn_name, fn_type, v_obj_type, + uint16_type_node); +} + +/* Lookup a name in std::contracts, or inject it. */ + +static tree +lookup_std_contracts_type (tree name_id) +{ + tree id_ns = get_identifier ("contracts"); + tree ns = lookup_qualified_name (std_node, id_ns); + + tree res_type = error_mark_node; + if (TREE_CODE (ns) == NAMESPACE_DECL) + res_type = lookup_qualified_name + (ns, name_id, LOOK_want::TYPE | LOOK_want::HIDDEN_FRIEND); + + if (TREE_CODE (res_type) == TYPE_DECL) + res_type = TREE_TYPE (res_type); + else + { + push_nested_namespace (std_node); + push_namespace (id_ns, /*inline*/false); + res_type = make_class_type (RECORD_TYPE); + create_implicit_typedef (name_id, res_type); + DECL_SOURCE_LOCATION (TYPE_NAME (res_type)) = BUILTINS_LOCATION; + DECL_CONTEXT (TYPE_NAME (res_type)) = current_namespace; + pushdecl_namespace_level (TYPE_NAME (res_type), /*hidden*/true); + pop_namespace (); + pop_nested_namespace (std_node); + } + return res_type; +} + +/* Return handle_contract_violation (), declaring it if needed. */ + +static tree +declare_handle_contract_violation () +{ + /* We may need to declare new types, ensure they are not considered + attached to a named module. */ + auto module_kind_override = make_temp_override + (module_kind, module_kind & ~(MK_PURVIEW | MK_ATTACH | MK_EXPORTING)); + 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 violation = lookup_std_contracts_type (viol_name); + tree fntype = NULL_TREE; + tree v_obj_ref = cp_build_qualified_type (violation, TYPE_QUAL_CONST); + v_obj_ref = cp_build_reference_type (v_obj_ref, /*rval*/false); + fntype = build_function_type_list (void_type_node, v_obj_ref, NULL_TREE); + + push_nested_namespace (global_namespace); + tree fndecl + = build_cp_library_fn_ptr ("handle_contract_violation", fntype, ECF_COLD); + pushdecl_namespace_level (fndecl, /*hiding*/true); + pop_nested_namespace (global_namespace); + + /* Build the parameter(s). */ + tree parms = cp_build_parm_decl (fndecl, NULL_TREE, v_obj_ref); + TREE_USED (parms) = true; + DECL_READ_P (parms) = true; + DECL_ARGUMENTS (fndecl) = parms; + return fndecl; +} + +/* Build the call to handle_contract_violation for VIOLATION. */ + +static void +build_contract_handler_call (tree violation) +{ + tree violation_fn = declare_handle_contract_violation (); + tree call = build_call_n (violation_fn, 1, violation); + finish_expr_stmt (call); +} + +/* If we have emitted any contracts in this TU that will call a violation + handler, then emit the wrappers for the handler. */ + +void +maybe_emit_violation_handler_wrappers () +{ + if (!tu_has_violation && !tu_has_violation_exception) + return; + + /* tu_has_violation */ + start_preparsed_function (tu_has_violation, NULL_TREE, + SF_DEFAULT | SF_PRE_PARSED); + tree body = begin_function_body (); + tree compound_stmt = begin_compound_stmt (BCS_FN_BODY); + tree v = DECL_ARGUMENTS (tu_has_violation); + tree semantic = DECL_CHAIN (v); + + /* We are going to call the handler. */ + build_contract_handler_call (v); + + tree if_observe = begin_if_stmt (); + /* if (observe) return; */ + tree cond = build2 (EQ_EXPR, uint16_type_node, semantic, + build_int_cst (uint16_type_node, (uint16_t)CES_OBSERVE)); + finish_if_stmt_cond (cond, if_observe); + emit_builtin_observable_checkpoint (); + finish_then_clause (if_observe); + begin_else_clause (if_observe); + /* else terminate. */ + finish_expr_stmt (build_call_a (terminate_fn, 0, nullptr)); + finish_else_clause (if_observe); + finish_if_stmt (if_observe); + finish_return_stmt (NULL_TREE); + + finish_compound_stmt (compound_stmt); + finish_function_body (body); + tu_has_violation = finish_function (false); + expand_or_defer_fn (tu_has_violation); + + /* tu_has_violation_exception */ + start_preparsed_function (tu_has_violation_exception, NULL_TREE, + SF_DEFAULT | SF_PRE_PARSED); + body = begin_function_body (); + compound_stmt = begin_compound_stmt (BCS_FN_BODY); + v = DECL_ARGUMENTS (tu_has_violation_exception); + semantic = DECL_CHAIN (v); + location_t loc = DECL_SOURCE_LOCATION (tu_has_violation_exception); + + tree a_type = strip_top_quals (non_reference (TREE_TYPE (v))); + tree v2 = build_decl (loc, VAR_DECL, NULL_TREE, a_type); + DECL_SOURCE_LOCATION (v2) = loc; + DECL_CONTEXT (v2) = current_function_decl; + DECL_ARTIFICIAL (v2) = true; + layout_decl (v2, 0); + v2 = pushdecl (v2); + add_decl_expr (v2); + tree r = cp_build_init_expr (v2, convert_from_reference (v)); + finish_expr_stmt (r); + tree memb = lookup_member (a_type, get_identifier ("_M_detection_mode"), + /*protect=*/1, /*want_type=*/0, tf_warning_or_error); + r = build_class_member_access_expr (v2, memb, NULL_TREE, false, + tf_warning_or_error); + r = cp_build_modify_expr + (loc, r, NOP_EXPR, + build_int_cst (uint16_type_node, (uint16_t)CDM_EVAL_EXCEPTION), + tf_warning_or_error); + finish_expr_stmt (r); + /* We are going to call the handler. */ + build_contract_handler_call (v); + + if_observe = begin_if_stmt (); + /* if (observe) return; */ + cond = build2 (EQ_EXPR, uint16_type_node, semantic, + build_int_cst (uint16_type_node, (uint16_t)CES_OBSERVE)); + finish_if_stmt_cond (cond, if_observe); + emit_builtin_observable_checkpoint (); + finish_then_clause (if_observe); + begin_else_clause (if_observe); + /* else terminate. */ + finish_expr_stmt (build_call_a (terminate_fn, 0, nullptr)); + finish_else_clause (if_observe); + finish_if_stmt (if_observe); + finish_return_stmt (NULL_TREE); + finish_compound_stmt (compound_stmt); + finish_function_body (body); + tu_has_violation_exception = finish_function (false); + expand_or_defer_fn (tu_has_violation_exception); +} + +/* Build a layout-compatible internal version of contract_violation type. */ + +static tree +get_contract_violation_fields () +{ + tree fields = NULL_TREE; + /* Must match : + class contract_violation { + uint16_t _M_version; + assertion_kind _M_assertion_kind; + evaluation_semantic _M_evaluation_semantic; + detection_mode _M_detection_mode; + const char* _M_comment; + void *_M_src_loc_ptr; + __vendor_ext* _M_ext; + }; + If this changes, also update the initializer in + build_contract_violation. */ + const tree types[] = { uint16_type_node, + uint16_type_node, + uint16_type_node, + uint16_type_node, + const_string_type_node, + ptr_type_node, + ptr_type_node + }; + const char *names[] = { "_M_version", + "_M_assertion_kind", + "_M_evaluation_semantic", + "_M_detection_mode", + "_M_comment", + "_M_src_loc_ptr", + "_M_ext", + }; + unsigned n = 0; + for (tree type : types) + { + /* finish_builtin_struct wants fields chained in reverse. */ + tree next = build_decl (BUILTINS_LOCATION, FIELD_DECL, + get_identifier(names[n++]), type); + DECL_CHAIN (next) = fields; + fields = next; + } + return fields; +} + +/* Build a type to represent contract violation objects. */ + +static tree +init_builtin_contract_violation_type () +{ + if (builtin_contract_violation_type) + return builtin_contract_violation_type; + + tree fields = get_contract_violation_fields (); + + iloc_sentinel ils (input_location); + input_location = BUILTINS_LOCATION; + builtin_contract_violation_type = make_class_type (RECORD_TYPE); + finish_builtin_struct (builtin_contract_violation_type, + "__builtin_contract_violation_type", fields, NULL_TREE); + CLASSTYPE_AS_BASE (builtin_contract_violation_type) + = builtin_contract_violation_type; + DECL_CONTEXT (TYPE_NAME (builtin_contract_violation_type)) + = FROB_CONTEXT (global_namespace); + CLASSTYPE_LITERAL_P (builtin_contract_violation_type) = true; + CLASSTYPE_LAZY_COPY_CTOR (builtin_contract_violation_type) = true; + xref_basetypes (builtin_contract_violation_type, /*bases=*/NULL_TREE); + DECL_CONTEXT (TYPE_NAME (builtin_contract_violation_type)) + = FROB_CONTEXT (global_namespace); + DECL_ARTIFICIAL (TYPE_NAME (builtin_contract_violation_type)) = true; + TYPE_ARTIFICIAL (builtin_contract_violation_type) = true; + builtin_contract_violation_type + = cp_build_qualified_type (builtin_contract_violation_type, + TYPE_QUAL_CONST); + return builtin_contract_violation_type; +} + +/* Early initialisation of types and functions we will use. */ +void +init_contracts () +{ + init_terminate_fn (); + init_builtin_contract_violation_type (); +} + +static GTY(()) tree contracts_source_location_impl_type; + +/* Build a layout-compatible internal version of source location __impl + type. */ + +static tree +get_contracts_source_location_impl_type (tree context = NULL_TREE) +{ + if (contracts_source_location_impl_type) + return contracts_source_location_impl_type; + + /* First see if we have a declaration that we can use. */ + tree contracts_source_location_type + = lookup_std_type (get_identifier ("source_location")); + + if (contracts_source_location_type + && contracts_source_location_type != error_mark_node + && TYPE_FIELDS (contracts_source_location_type)) + { + contracts_source_location_impl_type = get_source_location_impl_type (); + return contracts_source_location_impl_type; + } + + /* We do not, so build the __impl layout equivalent type, which must + match : + struct __impl + { + const char* _M_file_name; + const char* _M_function_name; + unsigned _M_line; + unsigned _M_column; + }; */ + const tree types[] = { const_string_type_node, + const_string_type_node, + uint_least32_type_node, + uint_least32_type_node }; + + const char *names[] = { "_M_file_name", + "_M_function_name", + "_M_line", + "_M_column", + }; + tree fields = NULL_TREE; + unsigned n = 0; + for (tree type : types) + { + /* finish_builtin_struct wants fields chained in reverse. */ + tree next = build_decl (BUILTINS_LOCATION, FIELD_DECL, + get_identifier (names[n++]), type); + DECL_CHAIN (next) = fields; + fields = next; + } + + iloc_sentinel ils (input_location); + input_location = BUILTINS_LOCATION; + contracts_source_location_impl_type = cxx_make_type (RECORD_TYPE); + finish_builtin_struct (contracts_source_location_impl_type, + "__impl", fields, NULL_TREE); + DECL_CONTEXT (TYPE_NAME (contracts_source_location_impl_type)) = context; + DECL_ARTIFICIAL (TYPE_NAME (contracts_source_location_impl_type)) = true; + TYPE_ARTIFICIAL (contracts_source_location_impl_type) = true; + contracts_source_location_impl_type + = cp_build_qualified_type (contracts_source_location_impl_type, + TYPE_QUAL_CONST); + + return contracts_source_location_impl_type; +} + +static tree +get_src_loc_impl_ptr (location_t loc) +{ + if (!contracts_source_location_impl_type) + get_contracts_source_location_impl_type (); + + tree fndecl = current_function_decl; + + gcc_checking_assert (fndecl); + tree impl__ + = build_source_location_impl (loc, fndecl, + contracts_source_location_impl_type); + tree p = build_pointer_type (contracts_source_location_impl_type); + return build_fold_addr_expr_with_type_loc (loc, impl__, p); +} + +/* Build a contract_violation layout compatible object. */ + +/* Constructor. At present, this should always be constant. */ + +static tree +build_contract_violation_ctor (tree contract) +{ + bool can_be_const = true; + uint16_t version = 1; + /* Default CDM_PREDICATE_FALSE. */ + uint16_t detection_mode = CDM_PREDICATE_FALSE; + + tree assertion_kind = CONTRACT_ASSERTION_KIND (contract); + if (!assertion_kind || really_constant_p (assertion_kind)) + { + contract_assertion_kind kind = get_contract_assertion_kind (contract); + assertion_kind = build_int_cst (uint16_type_node, kind); + } + else + can_be_const = false; + + tree eval_semantic = CONTRACT_EVALUATION_SEMANTIC (contract); + gcc_checking_assert (eval_semantic); + if (!really_constant_p (eval_semantic)) + can_be_const = false; + + tree comment = CONTRACT_COMMENT (contract); + if (comment && !really_constant_p (comment)) + can_be_const = false; + + tree std_src_loc_impl_ptr = CONTRACT_STD_SOURCE_LOC (contract); + if (std_src_loc_impl_ptr) + { + std_src_loc_impl_ptr = convert_from_reference (std_src_loc_impl_ptr); + if (!really_constant_p (std_src_loc_impl_ptr)) + can_be_const = false; + } + else + std_src_loc_impl_ptr = get_src_loc_impl_ptr (EXPR_LOCATION (contract)); + + /* Must match the type layout in builtin_contract_violation_type. */ + tree f0 = next_aggregate_field (TYPE_FIELDS (builtin_contract_violation_type)); + tree f1 = next_aggregate_field (DECL_CHAIN (f0)); + tree f2 = next_aggregate_field (DECL_CHAIN (f1)); + tree f3 = next_aggregate_field (DECL_CHAIN (f2)); + tree f4 = next_aggregate_field (DECL_CHAIN (f3)); + tree f5 = next_aggregate_field (DECL_CHAIN (f4)); + tree f6 = next_aggregate_field (DECL_CHAIN (f5)); + tree ctor = build_constructor_va + (builtin_contract_violation_type, 7, + f0, build_int_cst (uint16_type_node, version), + f1, assertion_kind, + f2, eval_semantic, + f3, build_int_cst (uint16_type_node, detection_mode), + f4, comment, + f5, std_src_loc_impl_ptr, + f6, build_zero_cst (nullptr_type_node)); // __vendor_ext + + TREE_READONLY (ctor) = true; + if (can_be_const) + TREE_CONSTANT (ctor) = true; + + return ctor; +} + +/* Build a named TU-local constant of TYPE. */ + +static tree +contracts_tu_local_named_var (location_t loc, const char *name, tree type) +{ + tree var_ = build_decl (loc, VAR_DECL, NULL, type); + DECL_NAME (var_) = generate_internal_label (name); + TREE_PUBLIC (var_) = false; + DECL_EXTERNAL (var_) = false; + TREE_STATIC (var_) = true; + /* Compiler-generated. */ + DECL_ARTIFICIAL (var_) = true; + TREE_CONSTANT (var_) = true; + layout_decl (var_, 0); + return var_; +} + +/* Create a read-only violation object. */ + +static tree +build_contract_violation_constant (tree ctor, tree contract) +{ + tree viol_ = contracts_tu_local_named_var + (EXPR_LOCATION (contract), "Lcontract_violation", + builtin_contract_violation_type); + + TREE_CONSTANT (viol_) = true; + DECL_INITIAL (viol_) = ctor; + varpool_node::finalize_decl (viol_); + + return viol_; +} + +/* 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 FNDECL. */ + +static void +remap_dummy_this (tree fndecl, tree *expr) +{ + walk_tree (expr, remap_dummy_this_1, fndecl, NULL); +} + +/* Replace uses of user's placeholder var with the actual return value. */ + +struct replace_tree +{ + tree from, to; +}; + +static tree +remap_retval_1 (tree *here, int *do_subtree, void *d) +{ + replace_tree *data = (replace_tree *) d; + + if (*here == data->from) + { + *here = data->to; + *do_subtree = 0; + } + else + *do_subtree = 1; + return NULL_TREE; +} + +static void +remap_retval (tree fndecl, tree contract) +{ + struct replace_tree data; + data.from = POSTCONDITION_IDENTIFIER (contract); + gcc_checking_assert (DECL_RESULT (fndecl)); + data.to = DECL_RESULT (fndecl); + walk_tree (&CONTRACT_CONDITION (contract), remap_retval_1, &data, NULL); +} + + +/* Genericize a CONTRACT tree, but do not attach it to the current context, + the caller is responsible for that. + This is called during genericization. */ + +tree +build_contract_check (tree contract) +{ + contract_evaluation_semantic semantic = get_evaluation_semantic (contract); + bool quick = false; + bool calls_handler = false; + switch (semantic) + { + case CES_IGNORE: + return void_node; + case CES_ENFORCE: + case CES_OBSERVE: + calls_handler = true; + break; + case CES_QUICK: + quick = true; + break; + default: + gcc_unreachable (); + } + + location_t loc = EXPR_LOCATION (contract); + + remap_dummy_this (current_function_decl, &CONTRACT_CONDITION (contract)); + tree condition = CONTRACT_CONDITION (contract); + if (condition == error_mark_node) + return NULL_TREE; + + if (POSTCONDITION_P (contract)) + { + remap_retval (current_function_decl, contract); + condition = CONTRACT_CONDITION (contract); + if (condition == error_mark_node) + return NULL_TREE; + } + + if (calls_handler) + maybe_declare_violation_handler_wrappers (); + + bool check_might_throw = (flag_exceptions + && !expr_noexcept_p (condition, tf_none)); + + /* Build a statement expression to hold a contract check, with the check + potentially wrapped in a try-catch expr. */ + tree cc_bind = build3 (BIND_EXPR, void_type_node, NULL, NULL, NULL); + BIND_EXPR_BODY (cc_bind) = push_stmt_list (); + + if (TREE_CODE (contract) == ASSERTION_STMT) + emit_builtin_observable_checkpoint (); + tree cond = build_x_unary_op (loc, TRUTH_NOT_EXPR, condition, NULL_TREE, + tf_warning_or_error); + tree violation; + bool viol_is_var = false; + if (quick) + /* We will not be calling a handler. */ + violation = build_zero_cst (nullptr_type_node); + else + { + /* Build a violation object, with the contract settings. */ + tree ctor = build_contract_violation_ctor (contract); + gcc_checking_assert (TREE_CONSTANT (ctor)); + violation = build_contract_violation_constant (ctor, contract); + violation = build_address (violation); + } + + tree s_const = build_int_cst (uint16_type_node, semantic); + /* So now do we need a try-catch? */ + if (check_might_throw) + { + /* This will hold the computed condition. */ + tree check_failed = build_decl (loc, VAR_DECL, NULL, boolean_type_node); + DECL_ARTIFICIAL (check_failed) = true; + DECL_IGNORED_P (check_failed) = true; + DECL_CONTEXT (check_failed) = current_function_decl; + layout_decl (check_failed, 0); + add_decl_expr (check_failed); + DECL_CHAIN (check_failed) = BIND_EXPR_VARS (cc_bind); + BIND_EXPR_VARS (cc_bind) = check_failed; + tree check_try = begin_try_block (); + finish_expr_stmt (cp_build_init_expr (check_failed, cond)); + finish_try_block (check_try); + + tree handler = begin_handler (); + finish_handler_parms (NULL_TREE, handler); /* catch (...) */ + if (quick) + finish_expr_stmt (build_call_a (terminate_fn, 0, nullptr)); + else + { + if (viol_is_var) + { + /* We can update the detection mode here. */ + tree memb + = lookup_member (builtin_contract_violation_type, + get_identifier ("_M_detection_mode"), + 1, 0, tf_warning_or_error); + tree r = cp_build_indirect_ref (loc, violation, RO_UNARY_STAR, + tf_warning_or_error); + r = build_class_member_access_expr (r, memb, NULL_TREE, false, + tf_warning_or_error); + r = cp_build_modify_expr + (loc, r, NOP_EXPR, + build_int_cst (uint16_type_node, (uint16_t)CDM_EVAL_EXCEPTION), + tf_warning_or_error); + finish_expr_stmt (r); + finish_expr_stmt (build_call_n (tu_has_violation, 2, + violation, s_const)); + } + else + /* We need to make a copy of the violation object to update. */ + finish_expr_stmt (build_call_n (tu_has_violation_exception, 2, + violation, s_const)); + /* If we reach here, we have handled the exception thrown and do not + need further action. */ + tree e = cp_build_modify_expr (loc, check_failed, NOP_EXPR, + boolean_false_node, + tf_warning_or_error); + finish_expr_stmt (e); + } + finish_handler (handler); + finish_handler_sequence (check_try); + cond = check_failed; + BIND_EXPR_VARS (cc_bind) = nreverse (BIND_EXPR_VARS (cc_bind)); + } + + tree do_check = begin_if_stmt (); + finish_if_stmt_cond (cond, do_check); + if (quick) + finish_expr_stmt (build_call_a (terminate_fn, 0, nullptr)); + else + finish_expr_stmt (build_call_n (tu_has_violation, 2, violation, s_const)); + finish_then_clause (do_check); + finish_if_stmt (do_check); + + BIND_EXPR_BODY (cc_bind) = pop_stmt_list (BIND_EXPR_BODY (cc_bind)); + return cc_bind; +} + +#include "gt-cp-contracts.h" diff --git a/gcc/cp/contracts.h b/gcc/cp/contracts.h index 5b4b2ffce1d..67ba65a1752 100644 --- a/gcc/cp/contracts.h +++ b/gcc/cp/contracts.h @@ -1,7 +1,12 @@ -/* Definitions for C++ contract levels. Implements functionality described in - the N4820 working draft version of contracts, P1290, P1332, and P1429. +/* Definitions for C++26 contracts. + Copyright (C) 2020-2026 Free Software Foundation, Inc. - Contributed by Jeff Chapman II (jchapman@lock3software.com) + Originally by Jeff Chapman II (jchapman@lock3software.com) for proposed + C++20 contracts. + Rewritten for C++26 contracts by: + Nina Ranns (dinka.ranns@googlemail.com) + Iain Sandoe (iain@sandoe.co.uk) + Ville Voutilainen (ville.voutilainen@gmail.com). This file is part of GCC. @@ -22,5 +27,167 @@ along with GCC; see the file COPYING3. If not see #ifndef GCC_CP_CONTRACT_H #define GCC_CP_CONTRACT_H +#include + +/* Contract assertion kind */ +/* Must match relevant enums in header */ + +enum contract_assertion_kind : uint16_t { + CAK_INVALID = 0 , + CAK_PRE = 1 , + CAK_POST = 2 , + CAK_ASSERT = 3, +}; + +/* Per P2900R14 + D3290R3 + extensions. */ +enum contract_evaluation_semantic : uint16_t { + CES_INVALID = 0, + CES_IGNORE = 1, + CES_OBSERVE = 2, + CES_ENFORCE = 3, + CES_QUICK = 4, +}; + +enum detection_mode : uint16_t { + CDM_UNSPECIFIED = 0, + CDM_PREDICATE_FALSE = 1, + CDM_EVAL_EXCEPTION = 2 +}; + +/* Contract evaluation_semantic */ +#define CONTRACT_EVALUATION_SEMANTIC(NODE) \ + (TREE_OPERAND (CONTRACT_CHECK (NODE), 0)) + +#define CONTRACT_ASSERTION_KIND(NODE) \ + (TREE_OPERAND (CONTRACT_CHECK (NODE), 1)) + +#define CONTRACT_CHECK(NODE) \ + (TREE_CHECK3 (NODE, ASSERTION_STMT, PRECONDITION_STMT, POSTCONDITION_STMT)) + +/* True if NODE is any kind of contract. */ +#define CONTRACT_P(NODE) \ + (TREE_CODE (NODE) == ASSERTION_STMT \ + || TREE_CODE (NODE) == PRECONDITION_STMT \ + || TREE_CODE (NODE) == POSTCONDITION_STMT) + +/* True if NODE is a contract condition. */ +#define CONTRACT_CONDITION_P(NODE) \ + (TREE_CODE (NODE) == PRECONDITION_STMT \ + || TREE_CODE (NODE) == POSTCONDITION_STMT) + +/* True if NODE is a precondition. */ +#define PRECONDITION_P(NODE) \ + (TREE_CODE (NODE) == PRECONDITION_STMT) + +/* True if NODE is a postcondition. */ +#define POSTCONDITION_P(NODE) \ + (TREE_CODE (NODE) == POSTCONDITION_STMT) + +/* True iff the FUNCTION_DECL NODE currently has any contracts. */ +#define DECL_HAS_CONTRACTS_P(NODE) \ + (get_fn_contract_specifiers (NODE) != NULL_TREE) + +/* The wrapper of the original source location of a list of contracts. */ +#define CONTRACT_SOURCE_LOCATION_WRAPPER(NODE) \ + (TREE_PURPOSE (TREE_VALUE (NODE))) + +/* The original source location of a list of contracts. */ +#define CONTRACT_SOURCE_LOCATION(NODE) \ + (EXPR_LOCATION (CONTRACT_SOURCE_LOCATION_WRAPPER (NODE))) + +/* The actual code _STMT for a contract specifier. */ +#define CONTRACT_STATEMENT(NODE) \ + (TREE_VALUE (TREE_VALUE (NODE))) + +/* The parsed condition of the contract. */ +#define CONTRACT_CONDITION(NODE) \ + (TREE_OPERAND (CONTRACT_CHECK (NODE), 2)) + +/* True iff the condition of the contract NODE is not yet parsed. */ +#define CONTRACT_CONDITION_DEFERRED_P(NODE) \ + (TREE_CODE (CONTRACT_CONDITION (NODE)) == DEFERRED_PARSE) + +/* The raw comment of the contract. */ +#define CONTRACT_COMMENT(NODE) \ + (TREE_OPERAND (CONTRACT_CHECK (NODE), 3)) + +/* A std::source_location, if provided. */ +#define CONTRACT_STD_SOURCE_LOC(NODE) \ + (TREE_OPERAND (CONTRACT_CHECK (NODE), 4)) + +/* The VAR_DECL of a postcondition result. For deferred contracts, this + is an IDENTIFIER. */ +#define POSTCONDITION_IDENTIFIER(NODE) \ + (TREE_OPERAND (POSTCONDITION_STMT_CHECK (NODE), 5)) + +/* contracts.cc */ + +extern void init_contracts (void); + +extern tree grok_contract (tree, tree, tree, cp_expr, location_t); +extern tree finish_contract_specifier (tree, tree); +extern tree finish_contract_condition (cp_expr); +extern void update_late_contract (tree, tree, cp_expr); +extern void check_redecl_contract (tree, tree); +extern tree invalidate_contract (tree); +extern tree copy_and_remap_contracts (tree, tree); + +extern void set_fn_contract_specifiers (tree, tree); +extern void update_fn_contract_specifiers (tree, tree); +extern tree get_fn_contract_specifiers (tree); +extern void remove_decl_with_fn_contracts_specifiers (tree); +extern void remove_fn_contract_specifiers (tree); +extern void update_contract_arguments (tree, tree); + +extern tree make_postcondition_variable (cp_expr); +extern tree make_postcondition_variable (cp_expr, tree); +extern void check_param_in_postcondition (tree, location_t); +extern void check_postconditions_in_redecl (tree, tree); +extern void maybe_update_postconditions (tree); +extern void rebuild_postconditions (tree); +extern bool check_postcondition_result (tree, tree, location_t); + +extern bool contract_any_deferred_p (tree); + +extern void start_function_contracts (tree); +extern void maybe_apply_function_contracts (tree); + +extern void maybe_emit_violation_handler_wrappers (void); + +extern tree build_contract_check (tree); + +inline void +set_decl_contracts (tree decl, tree contract_attrs) +{ + set_fn_contract_specifiers (decl, contract_attrs); +} + +/* TODO : decide if we should push the tests into contracts.cc */ +extern contract_evaluation_semantic get_evaluation_semantic (const_tree); + +/* Will this contract be ignored. */ + +inline bool +contract_ignored_p (const_tree contract) +{ + return (get_evaluation_semantic (contract) <= CES_IGNORE); +} + +/* Will this contract be evaluated? */ + +inline bool +contract_evaluated_p (const_tree contract) +{ + return (get_evaluation_semantic (contract) >= CES_OBSERVE); +} + +/* Is the contract terminating? */ + +inline bool +contract_terminating_p (const_tree contract) +{ + return (get_evaluation_semantic (contract) == CES_ENFORCE + || get_evaluation_semantic (contract) == CES_QUICK); +} #endif /* ! GCC_CP_CONTRACT_H */ diff --git a/gcc/cp/cp-gimplify.cc b/gcc/cp/cp-gimplify.cc index ec8fb1fd2a7..38cee83ab76 100644 --- a/gcc/cp/cp-gimplify.cc +++ b/gcc/cp/cp-gimplify.cc @@ -43,6 +43,7 @@ along with GCC; see the file COPYING3. If not see #include "omp-general.h" #include "opts.h" #include "gcc-urlifier.h" +#include "contracts.h" // build_contract_check () /* Keep track of forward references to immediate-escalating functions in case they become consteval. This vector contains ADDR_EXPRs and @@ -1044,7 +1045,7 @@ cp_gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p) return ret; } -static inline bool +bool is_invisiref_parm (const_tree t) { return ((TREE_CODE (t) == PARM_DECL || TREE_CODE (t) == RESULT_DECL) @@ -2122,6 +2123,20 @@ cp_genericize_r (tree *stmt_p, int *walk_subtrees, void *data) wtd->bind_expr_stack.pop (); break; + case ASSERTION_STMT: + case PRECONDITION_STMT: + case POSTCONDITION_STMT: + if (tree check = build_contract_check (stmt)) + { + *stmt_p = check; + return cp_genericize_r (stmt_p, walk_subtrees, data); + } + /* If we didn't build a check, replace it with void_node so we don't + leak contracts into GENERIC. */ + *stmt_p = void_node; + *walk_subtrees = 0; + break; + case USING_STMT: { tree block = NULL_TREE; @@ -4251,20 +4266,12 @@ struct source_location_table_entry_hash static GTY(()) hash_table *source_location_table; -/* Fold the __builtin_source_location () call T. */ +/* Build a std::source_location::__impl from a location_t. */ tree -fold_builtin_source_location (const_tree t) +build_source_location_impl (location_t loc, tree fndecl, + tree source_location_impl) { - gcc_assert (TREE_CODE (t) == CALL_EXPR); - /* TREE_TYPE (t) is const std::source_location::__impl* */ - tree source_location_impl = TREE_TYPE (TREE_TYPE (t)); - if (source_location_impl == error_mark_node) - return build_zero_cst (const_ptr_type_node); - gcc_assert (CLASS_TYPE_P (source_location_impl) - && id_equal (TYPE_IDENTIFIER (source_location_impl), "__impl")); - - location_t loc = EXPR_LOCATION (t); if (source_location_table == NULL) source_location_table = hash_table ::create_ggc (64); @@ -4273,78 +4280,94 @@ fold_builtin_source_location (const_tree t) entry.loc = linemap_resolve_location (line_table, loc, LRK_MACRO_EXPANSION_POINT, &map); - entry.uid = current_function_decl ? DECL_UID (current_function_decl) : -1; + entry.uid = fndecl ? DECL_UID (fndecl) : -1; entry.var = error_mark_node; source_location_table_entry *entryp = source_location_table->find_slot (entry, INSERT); - tree var; + if (entryp->var) - var = entryp->var; - else + return entryp->var; + + tree var = build_decl (loc, VAR_DECL, generate_internal_label ("Lsrc_loc"), + source_location_impl); + TREE_STATIC (var) = 1; + TREE_PUBLIC (var) = 0; + DECL_ARTIFICIAL (var) = 1; + DECL_IGNORED_P (var) = 1; + DECL_EXTERNAL (var) = 0; + DECL_DECLARED_CONSTEXPR_P (var) = 1; + DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (var) = 1; + layout_decl (var, 0); + + vec *v = NULL; + vec_alloc (v, 4); + for (tree field = TYPE_FIELDS (source_location_impl); + (field = next_aggregate_field (field)) != NULL_TREE; + field = DECL_CHAIN (field)) { - var = build_decl (loc, VAR_DECL, generate_internal_label ("Lsrc_loc"), - source_location_impl); - TREE_STATIC (var) = 1; - TREE_PUBLIC (var) = 0; - DECL_ARTIFICIAL (var) = 1; - DECL_IGNORED_P (var) = 1; - DECL_EXTERNAL (var) = 0; - DECL_DECLARED_CONSTEXPR_P (var) = 1; - DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (var) = 1; - layout_decl (var, 0); - - vec *v = NULL; - vec_alloc (v, 4); - for (tree field = TYPE_FIELDS (source_location_impl); - (field = next_aggregate_field (field)) != NULL_TREE; - field = DECL_CHAIN (field)) + const char *n = IDENTIFIER_POINTER (DECL_NAME (field)); + tree val = NULL_TREE; + if (strcmp (n, "_M_file_name") == 0) { - const char *n = IDENTIFIER_POINTER (DECL_NAME (field)); - tree val = NULL_TREE; - if (strcmp (n, "_M_file_name") == 0) + if (const char *fname = LOCATION_FILE (loc)) { - if (const char *fname = LOCATION_FILE (loc)) - { - fname = remap_macro_filename (fname); - val = build_string_literal (fname); - } - else - val = build_string_literal (""); + fname = remap_macro_filename (fname); + val = build_string_literal (fname); } - else if (strcmp (n, "_M_function_name") == 0) - { - const char *name = ""; - - if (current_function_decl) - { - /* If this is a coroutine, we should get the name of the user - function rather than the actor we generate. */ - if (tree ramp = DECL_RAMP_FN (current_function_decl)) - name = cxx_printable_name (ramp, 2); - else - name = cxx_printable_name (current_function_decl, 2); - } - - val = build_string_literal (name); - } - else if (strcmp (n, "_M_line") == 0) - val = build_int_cst (TREE_TYPE (field), LOCATION_LINE (loc)); - else if (strcmp (n, "_M_column") == 0) - val = build_int_cst (TREE_TYPE (field), LOCATION_COLUMN (loc)); else - gcc_unreachable (); - CONSTRUCTOR_APPEND_ELT (v, field, val); + val = build_string_literal (""); } + else if (strcmp (n, "_M_function_name") == 0) + { + const char *name = ""; - tree ctor = build_constructor (source_location_impl, v); - TREE_CONSTANT (ctor) = 1; - TREE_STATIC (ctor) = 1; - DECL_INITIAL (var) = ctor; - varpool_node::finalize_decl (var); - *entryp = entry; - entryp->var = var; + if (fndecl) + { + /* If this is a coroutine, we should get the name of the user + function rather than the actor we generate. */ + if (tree ramp = DECL_RAMP_FN (fndecl)) + name = cxx_printable_name (ramp, 2); + else + name = cxx_printable_name (fndecl, 2); + } + + val = build_string_literal (name); + } + else if (strcmp (n, "_M_line") == 0) + val = build_int_cst (TREE_TYPE (field), LOCATION_LINE (loc)); + else if (strcmp (n, "_M_column") == 0) + val = build_int_cst (TREE_TYPE (field), LOCATION_COLUMN (loc)); + else + gcc_unreachable (); + CONSTRUCTOR_APPEND_ELT (v, field, val); } + tree ctor = build_constructor (source_location_impl, v); + TREE_CONSTANT (ctor) = 1; + TREE_STATIC (ctor) = 1; + DECL_INITIAL (var) = ctor; + varpool_node::finalize_decl (var); + *entryp = entry; + entryp->var = var; + return var; +} + +/* Fold the __builtin_source_location () call T. */ + +tree +fold_builtin_source_location (const_tree t) +{ + gcc_assert (TREE_CODE (t) == CALL_EXPR); + /* TREE_TYPE (t) is const std::source_location::__impl* */ + tree source_location_impl = TREE_TYPE (TREE_TYPE (t)); + if (source_location_impl == error_mark_node) + return build_zero_cst (const_ptr_type_node); + gcc_assert (CLASS_TYPE_P (source_location_impl) + && id_equal (TYPE_IDENTIFIER (source_location_impl), "__impl")); + + location_t loc = EXPR_LOCATION (t); + tree var = build_source_location_impl (loc, current_function_decl, + source_location_impl); return build_fold_addr_expr_with_type_loc (loc, var, TREE_TYPE (t)); } diff --git a/gcc/cp/cp-tree.def b/gcc/cp/cp-tree.def index 770a681e124..3826b143a96 100644 --- a/gcc/cp/cp-tree.def +++ b/gcc/cp/cp-tree.def @@ -576,9 +576,9 @@ DEFTREECODE (CO_RETURN_EXPR, "co_return", tcc_statement, 2) operand to store the optional name for the result value. CONTRACT_SEMANTIC has the computed behavior of the contract. */ -DEFTREECODE (ASSERTION_STMT, "assertion_stmt", tcc_statement, 3) -DEFTREECODE (PRECONDITION_STMT, "precondition_stmt", tcc_statement, 3) -DEFTREECODE (POSTCONDITION_STMT, "postcondition_stmt", tcc_statement, 4) +DEFTREECODE (ASSERTION_STMT, "assertion_stmt", tcc_statement, 5) +DEFTREECODE (PRECONDITION_STMT, "precondition_stmt", tcc_statement, 5) +DEFTREECODE (POSTCONDITION_STMT, "postcondition_stmt", tcc_statement, 6) /* A reference to a translation-unit local entity. diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 2046818301f..ef663905680 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -237,6 +237,7 @@ enum cp_tree_index CPTI_DSO_HANDLE, CPTI_DCAST, CPTI_META_INFO_TYPE, + CPTI_CONTRACT_VIOLATION_TYPE, CPTI_MAX }; @@ -268,6 +269,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX]; /* std::align_val_t */ #define align_type_node cp_global_trees[CPTI_ALIGN_TYPE] #define meta_info_type_node cp_global_trees[CPTI_META_INFO_TYPE] +#define builtin_contract_violation_type cp_global_trees[CPTI_CONTRACT_VIOLATION_TYPE] /* We cache these tree nodes so as to call get_identifier less frequently. For identifiers for functions, including special member functions such @@ -529,6 +531,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX]; LOOKUP_FOUND_P (in RECORD_TYPE, UNION_TYPE, ENUMERAL_TYPE, NAMESPACE_DECL) FNDECL_MANIFESTLY_CONST_EVALUATED (in FUNCTION_DECL) TARGET_EXPR_INTERNAL_P (in TARGET_EXPR) + CONTRACT_CONST (in ASSERTION_, PRECONDITION_, POSTCONDITION_STMT) 5: IDENTIFIER_VIRTUAL_P (in IDENTIFIER_NODE) FUNCTION_RVALUE_QUALIFIED (in FUNCTION_TYPE, METHOD_TYPE) CALL_EXPR_REVERSE_ARGS (in CALL_EXPR, AGGR_INIT_EXPR) @@ -2071,10 +2074,14 @@ struct GTY(()) saved_scope { tree x_current_class_ptr; tree x_current_class_ref; + /* Only used for uses of this in contract assertion. */ + tree x_contract_class_ptr; + int x_processing_template_decl; int x_processing_specialization; int x_processing_constraint; int suppress_location_wrappers; + BOOL_BITFIELD x_processing_postcondition : 1; BOOL_BITFIELD x_processing_explicit_instantiation : 1; BOOL_BITFIELD need_pop_function_context : 1; BOOL_BITFIELD x_processing_omp_trait_property_expr : 1; @@ -2161,12 +2168,18 @@ extern GTY(()) struct saved_scope *scope_chain; #define processing_contract_condition \ (scope_chain->bindings->kind == sk_contract) +#define processing_postcondition scope_chain->x_processing_postcondition + #define in_discarded_stmt scope_chain->discarded_stmt #define in_consteval_if_p scope_chain->consteval_if_p #define in_expansion_stmt scope_chain->expansion_stmt #define current_ref_temp_count scope_chain->ref_temp_count +/* Nonzero if we're parsing a precondition on a constructor or postcondition + on destructor. */ +#define contract_class_ptr scope_chain->x_contract_class_ptr + /* RAII sentinel to handle clearing processing_template_decl and restoring it when done. */ @@ -3196,6 +3209,7 @@ struct GTY(()) lang_decl_fn { unsigned coroutine_p : 1; unsigned implicit_constexpr : 1; unsigned escalated_p : 1; + unsigned xobj_func : 1; unsigned spare : 7; @@ -6151,6 +6165,11 @@ extern int comparing_specializations; FIXME we should always do this except during deduction/ordering. */ extern int comparing_dependent_aliases; +/* True if we are matching contracts of two functions. Depending on + whether a decl has been genericized or not, PARM_DECL may be adjusted + to be an invisible reference. */ +extern bool comparing_contracts; + /* In parser.cc. */ extern bool cp_preserve_using_decl; @@ -6937,6 +6956,9 @@ struct cp_declarator { tree late_return_type; /* The trailing requires-clause, if any. */ tree requires_clause; + /* The function-contract-specifier-seq, if any. */ + tree contract_specifiers; + /* The position of the opening brace for a function definition. */ location_t parens_loc; } function; /* For arrays. */ @@ -7126,6 +7148,7 @@ extern tree build_conditional_expr (const op_location_t &, extern tree build_addr_func (tree, tsubst_flags_t); extern void set_flags_from_callee (tree); extern tree build_call_a (tree, int, tree*); +extern tree build_call_a_1 (tree, int, tree*); extern tree build_call_n (tree, int, ...); extern bool null_ptr_cst_p (tree); extern bool null_member_pointer_value_p (tree); @@ -9055,6 +9078,7 @@ extern tree process_stmt_hotness_attribute (tree, location_t); extern tree build_assume_call (location_t, tree); extern tree process_stmt_assume_attribute (tree, tree, location_t); extern bool simple_empty_class_p (tree, tree, tree_code); +extern tree build_source_location_impl (location_t, tree, tree); extern tree fold_builtin_source_location (const_tree); extern tree get_source_location_impl_type (); extern bool immediate_escalating_function_p (tree); @@ -9062,6 +9086,7 @@ extern void promote_function_to_consteval (tree); extern tree cp_fold_immediate (tree *, mce_value, tree = current_function_decl); extern void process_and_check_pending_immediate_escalating_fns (); +extern bool is_invisiref_parm (const_tree); /* in name-lookup.cc */ extern tree strip_using_decl (tree); diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc index 885c5ce9ed8..1e353ef1ebc 100644 --- a/gcc/cp/decl.cc +++ b/gcc/cp/decl.cc @@ -60,6 +60,7 @@ along with GCC; see the file COPYING3. If not see #include "opts.h" #include "langhooks-def.h" /* For lhd_simulate_record_decl */ #include "coroutines.h" +#include "contracts.h" #include "gcc-urlifier.h" #include "diagnostic-highlight-colors.h" #include "pretty-print-markup.h" @@ -2527,6 +2528,8 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden) = DECL_OVERLOADED_OPERATOR_CODE_RAW (olddecl); new_defines_function = DECL_INITIAL (newdecl) != NULL_TREE; + check_redecl_contract (newdecl, olddecl); + /* Optionally warn about more than one declaration for the same name, but don't warn about a function declaration followed by a definition. */ @@ -2607,6 +2610,9 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden) specializations. */ gcc_assert (!DECL_TEMPLATE_SPECIALIZATIONS (newdecl)); + /* Make sure the contracts are equivalent. */ + check_redecl_contract (newdecl, olddecl); + DECL_ATTRIBUTES (old_result) = (*targetm.merge_decl_attributes) (old_result, new_result); @@ -2675,6 +2681,8 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden) DECL_INITIAL (old_result) = DECL_INITIAL (new_result); if (DECL_FUNCTION_TEMPLATE_P (newdecl)) { + update_contract_arguments (new_result, old_result); + DECL_ARGUMENTS (old_result) = DECL_ARGUMENTS (new_result); for (tree p = DECL_ARGUMENTS (old_result); p; p = DECL_CHAIN (p)) DECL_CONTEXT (p) = old_result; @@ -3202,6 +3210,8 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden) } if (! types_match || new_defines_function) { + /* Update the contracts to reflect the new parameter names. */ + update_contract_arguments (newdecl, olddecl); /* Mark the old PARM_DECLs in case std::meta::parameters_of has been called on the old declaration and reflections of those @@ -3531,6 +3541,10 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden) if (flag_concepts) remove_constraints (newdecl); + if (flag_contracts) + /* Remove the specifiers, and then remove the decl from the lookup. */ + remove_decl_with_fn_contracts_specifiers (newdecl); + /* And similarly for any module tracking data. */ if (modules_p ()) remove_defining_module (newdecl); @@ -5710,6 +5724,9 @@ cxx_init_decl_processing (void) if (flag_exceptions) init_exception_processing (); + if (flag_contracts) + init_contracts (); + if (modules_p ()) init_modules (parse_in); @@ -6516,6 +6533,20 @@ start_decl (const cp_declarator *declarator, return error_mark_node; } + if (flag_contracts + && TREE_CODE (decl) == FUNCTION_DECL + && !processing_template_decl + && DECL_RESULT (decl) + && is_auto (TREE_TYPE (DECL_RESULT (decl)))) + for (tree ca = get_fn_contract_specifiers (decl); ca; ca = TREE_CHAIN (ca)) + if (POSTCONDITION_P (CONTRACT_STATEMENT (ca)) + && POSTCONDITION_IDENTIFIER (CONTRACT_STATEMENT (ca))) + { + error_at (DECL_SOURCE_LOCATION (decl), + "postconditions with deduced result name types must only" + " appear on function definitions"); + return error_mark_node; + } /* Save the DECL_INITIAL value in case it gets clobbered to assist with attribute validation. */ initial = DECL_INITIAL (decl); @@ -12045,6 +12076,7 @@ grokfndecl (tree ctype, int template_count, tree in_namespace, tree* attrlist, + tree contract_specifiers, location_t location) { tree decl; @@ -12490,7 +12522,11 @@ grokfndecl (tree ctype, /* Caller will do the rest of this. */ if (check < 0) - return decl; + { + if (decl && decl != error_mark_node && contract_specifiers) + set_fn_contract_specifiers (decl, contract_specifiers); + return decl; + } if (ctype != NULL_TREE) grokclassfn (ctype, decl, flags); @@ -12521,6 +12557,16 @@ grokfndecl (tree ctype, *attrlist = NULL_TREE; } + /* Update now we have a decl and maybe know the return type. */ + if (contract_specifiers) + { + tree t = decl; + if (TREE_CODE (decl) == TEMPLATE_DECL) + t = DECL_TEMPLATE_RESULT (decl); + set_fn_contract_specifiers (t, contract_specifiers); + rebuild_postconditions (t); + } + /* Check main's type after attributes have been applied. */ if (ctype == NULL_TREE && DECL_MAIN_P (decl)) { @@ -13870,6 +13916,7 @@ grokdeclarator (const cp_declarator *declarator, tree raises = NULL_TREE; int template_count = 0; tree returned_attrs = NULL_TREE; + tree contract_specifiers = NULL_TREE; tree parms = NULL_TREE; const cp_declarator *id_declarator; /* The unqualified name of the declarator; either an @@ -15450,6 +15497,12 @@ grokdeclarator (const cp_declarator *declarator, returned_attrs = attr_chainon (returned_attrs, att); } + /* Actually apply the contract attributes to the declaration. */ + if (flag_contracts) + contract_specifiers + = attr_chainon (contract_specifiers, + declarator->u.function.contract_specifiers); + if (attrs) /* [dcl.fct]/2: @@ -16411,8 +16464,8 @@ grokdeclarator (const cp_declarator *declarator, is_xobj_member_function, sfk, funcdef_flag, late_return_type_p, template_count, in_namespace, - attrlist, id_loc); - decl = set_virt_specifiers (decl, virt_specifiers); + attrlist, contract_specifiers, id_loc); + decl = set_virt_specifiers (decl, virt_specifiers); if (decl == NULL_TREE) return error_mark_node; #if 0 @@ -16738,8 +16791,7 @@ grokdeclarator (const cp_declarator *declarator, || storage_class != sc_static); decl = grokfndecl (ctype, type, original_name, parms, unqualified_id, - declspecs, - reqs, virtualp, flags, memfn_quals, rqual, raises, + declspecs, reqs, virtualp, flags, memfn_quals, rqual, raises, 1, friendp, publicp, inlinep | (2 * constexpr_p) | (4 * concept_p) @@ -16749,7 +16801,7 @@ grokdeclarator (const cp_declarator *declarator, funcdef_flag, late_return_type_p, template_count, in_namespace, attrlist, - id_loc); + contract_specifiers, id_loc); if (decl == NULL_TREE) return error_mark_node; @@ -19920,6 +19972,8 @@ start_preparsed_function (tree decl1, tree attrs, int flags) store_parm_decls (current_function_parms); + start_function_contracts (decl1); + if (!processing_template_decl && flag_lifetime_dse > 1 && DECL_CONSTRUCTOR_P (decl1) @@ -20352,6 +20406,10 @@ finish_function (bool inline_p) finish_eh_spec_block (TYPE_RAISES_EXCEPTIONS (TREE_TYPE (fndecl)), current_eh_spec_block); + + /* If outlining succeeded, then add contracts handling if needed. */ + if (coroutine->cp_valid_coroutine ()) + maybe_apply_function_contracts (fndecl); } else /* For a cloned function, we've already got all the code we need; @@ -20367,6 +20425,9 @@ finish_function (bool inline_p) finish_eh_spec_block (TYPE_RAISES_EXCEPTIONS (TREE_TYPE (current_function_decl)), current_eh_spec_block); + + maybe_apply_function_contracts (current_function_decl); + } /* If we're saving up tree structure, tie off the function now. */ @@ -20630,7 +20691,7 @@ finish_function (bool inline_p) /* Clean up. */ invoke_plugin_callbacks (PLUGIN_FINISH_PARSE_FUNCTION, fndecl); - /* Build outlined functions for coroutines. */ + /* Build outlined functions for coroutines and contracts. */ if (coroutine) { diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc index 6faf4b040f2..50b4857793a 100644 --- a/gcc/cp/decl2.cc +++ b/gcc/cp/decl2.cc @@ -54,6 +54,7 @@ along with GCC; see the file COPYING3. If not see #include "escaped_string.h" #include "gcc-rich-location.h" #include "tree-pretty-print-markup.h" +#include "contracts.h" /* Id for dumping the raw trees. */ int raw_dump_id; @@ -6104,6 +6105,9 @@ c_parse_final_cleanups (void) reconsider = true; } + if (flag_contracts) + maybe_emit_violation_handler_wrappers (); + /* All templates have been instantiated. */ at_eof = 2; diff --git a/gcc/cp/lex.cc b/gcc/cp/lex.cc index 27c5192ab89..88b0b24e097 100644 --- a/gcc/cp/lex.cc +++ b/gcc/cp/lex.cc @@ -419,7 +419,11 @@ cxx_init (void) if (cxx_dialect >= cxx23) cpp_warn (parse_in, "assume"); if (cxx_dialect >= cxx26) - cpp_warn (parse_in, "indeterminate"); + { + if (flag_contracts) + cpp_warn (parse_in, "contract_assert"); + cpp_warn (parse_in, "indeterminate"); + } } if (c_common_init () == false) diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc index 79a33f3ef12..a8d373ea6b7 100644 --- a/gcc/cp/parser.cc +++ b/gcc/cp/parser.cc @@ -49,6 +49,7 @@ along with GCC; see the file COPYING3. If not see #include "c-family/known-headers.h" #include "bitmap.h" #include "builtins.h" +#include "contracts.h" #include "analyzer/analyzer-language.h" @@ -1183,6 +1184,20 @@ cp_lexer_get_preprocessor_token (unsigned flags, cp_token *token) particular identifier-turned-keyword again. */ C_SET_RID_CODE (token->u.value, RID_MAX); } + if (warn_cxx26_compat + && C_RID_CODE (token->u.value) >= RID_FIRST_CXX26 + && C_RID_CODE (token->u.value) <= RID_LAST_CXX26) + { + /* Warn about the C++26 keyword (but still treat it as + an identifier). */ + warning_at (token->location, OPT_Wc__26_compat, + "identifier %qE is a keyword in C++26", + token->u.value); + + /* Clear out the C_RID_CODE so we don't warn about this + particular identifier-turned-keyword again. */ + C_SET_RID_CODE (token->u.value, RID_MAX); + } token->keyword = RID_MAX; } @@ -1844,7 +1859,7 @@ clear_decl_specs (cp_decl_specifier_seq *decl_specs) static cp_declarator *make_call_declarator (cp_declarator *, tree, cp_cv_quals, cp_virt_specifiers, cp_ref_qualifier, - tree, tree, tree, tree, tree, location_t); + tree, tree, tree, tree, tree, tree, location_t); static cp_declarator *make_array_declarator (cp_declarator *, tree, tree); static cp_declarator *make_pointer_declarator @@ -2030,6 +2045,7 @@ make_call_declarator (cp_declarator *target, tree exception_specification, tree late_return_type, tree requires_clause, + tree contract_specifiers, tree std_attrs, location_t parens_loc) { @@ -2045,6 +2061,7 @@ make_call_declarator (cp_declarator *target, declarator->u.function.exception_specification = exception_specification; declarator->u.function.late_return_type = late_return_type; declarator->u.function.requires_clause = requires_clause; + declarator->u.function.contract_specifiers = contract_specifiers; declarator->u.function.parens_loc = parens_loc; if (target) { @@ -2480,6 +2497,8 @@ cp_parser_context_new (cp_parser_context* next) parser->unparsed_queues->last ().nsdmis #define unparsed_noexcepts \ parser->unparsed_queues->last ().noexcepts +#define unparsed_contracts \ + parser->unparsed_queues->last ().contracts static void push_unparsed_function_queues (cp_parser *parser) @@ -2978,6 +2997,21 @@ static tree cp_parser_transaction_cancel static tree cp_parser_yield_expression (cp_parser *); +/* Contracts */ + +static tree cp_parser_contract_assert + (cp_parser *parser, cp_token *token); + +static tree cp_maybe_function_contract_specifier + (cp_parser *parser); + +static tree cp_parser_function_contract_specifier + (cp_parser *); +static tree cp_parser_function_contract_specifier_seq + (cp_parser *); +static void cp_parser_late_contracts + (cp_parser *, tree); + enum pragma_context { pragma_external, pragma_member, @@ -12909,7 +12943,9 @@ cp_parser_lambda_introducer (cp_parser* parser, tree lambda_expr) first = false; tree scope = current_nonlambda_scope (/*only_skip_closures_p=*/true); - if (TREE_CODE (scope) != FUNCTION_DECL && !parsing_nsdmi ()) + if (TREE_CODE (scope) != FUNCTION_DECL + && !parsing_nsdmi () + && current_binding_level->kind != sk_contract) error ("non-local lambda expression cannot have a capture-default"); } @@ -13471,6 +13507,10 @@ cp_parser_lambda_declarator_opt (cp_parser* parser, tree lambda_expr, return_type = cp_parser_trailing_type_id (parser); } + tree contract_specifiers = NULL_TREE; + if (flag_contracts) + contract_specifiers = cp_parser_function_contract_specifier_seq (parser); + /* Also allow GNU attributes at the very end of the declaration, the usual place for GNU attributes. */ if (cp_next_tokens_can_be_gnu_attribute_p (parser)) @@ -13539,6 +13579,7 @@ cp_parser_lambda_declarator_opt (cp_parser* parser, tree lambda_expr, exception_spec, return_type, trailing_requires_clause, + contract_specifiers, std_attrs, UNKNOWN_LOCATION); @@ -13595,7 +13636,7 @@ make_dummy_lambda_op () VIRT_SPEC_UNSPECIFIED, REF_QUAL_NONE, NULL_TREE, NULL_TREE, NULL_TREE, NULL_TREE, - NULL_TREE, UNKNOWN_LOCATION); + NULL_TREE, NULL_TREE, UNKNOWN_LOCATION); tree fco = grokmethod (&return_type_specs, declarator, NULL_TREE); obstack_free (&declarator_obstack, p); @@ -13690,6 +13731,10 @@ cp_parser_lambda_body (cp_parser* parser, tree lambda_expr) removed the need for that. */ cp_parser_function_body (parser, false); + /* We need to parse deferred contract conditions before we try to call + finish_function (which will try to emit the contracts). */ + cp_parser_late_contracts (parser, fco); + finish_lambda_function (body); } @@ -14280,10 +14325,12 @@ cp_parser_statement (cp_parser* parser, tree in_statement_expr, std_attrs = process_stmt_hotness_attribute (std_attrs, attrs_loc); statement = cp_parser_transaction_cancel (parser); break; - + case RID_CONTASSERT: + statement = cp_parser_contract_assert (parser, token); + break; default: /* It might be a keyword like `int' that can start a - declaration-statement. */ + declaration-statement. */ break; } } @@ -26009,6 +26056,7 @@ cp_parser_init_declarator (cp_parser* parser, { /* If the init-declarator isn't initialized and isn't followed by a `,' or `;', it's not a valid init-declarator. */ + if (token->type != CPP_COMMA && token->type != CPP_SEMICOLON) { @@ -26614,6 +26662,11 @@ cp_parser_direct_declarator (cp_parser* parser, /* Parse the virt-specifier-seq. */ virt_specifiers = cp_parser_virt_specifier_seq_opt (parser); + tree contract_specifiers = NULL_TREE; + if (flag_contracts) + contract_specifiers + = cp_parser_function_contract_specifier_seq (parser); + location_t parens_loc = make_location (parens_start, parens_start, parens_end); @@ -26627,6 +26680,7 @@ cp_parser_direct_declarator (cp_parser* parser, exception_specification, late_return, requires_clause, + contract_specifiers, attrs, parens_loc); declarator->attributes = gnu_attrs; @@ -27838,6 +27892,12 @@ cp_parser_type_specifier_seq (cp_parser* parser, continue; } + /* We might have contract specifiers after a trailing return. */ + if (seen_type_specifier + && is_trailing_return + && cp_maybe_function_contract_specifier (parser)) + break; + /* record the token of the beginning of the type specifier seq, for error reporting purposes*/ if (!start_token) @@ -29865,6 +29925,42 @@ cp_parser_class_specifier (cp_parser* parser) } vec_safe_truncate (unparsed_nsdmis, 0); + /* Now contract specifiers. */ + FOR_EACH_VEC_SAFE_ELT (unparsed_contracts, ix, decl) + { + tree ctx = DECL_CONTEXT (decl); + switch_to_class (ctx); + + temp_override cfd (current_function_decl, decl); + + /* Make sure that any template parameters are in scope. */ + maybe_begin_member_template_processing (decl); + + /* Make sure that any member-function parameters are in scope. + This function doesn't expect ccp to be set. */ + current_class_ptr = current_class_ref = NULL_TREE; + inject_parm_decls (decl); + + /* 'this' is not allowed in static member functions. */ + unsigned char local_variables_forbidden_p + = parser->local_variables_forbidden_p; + if (DECL_THIS_STATIC (decl)) + parser->local_variables_forbidden_p |= THIS_FORBIDDEN; + + /* Now we can parse contract conditions. */ + cp_parser_late_contracts (parser, decl); + + /* Restore the state of local_variables_forbidden_p. */ + parser->local_variables_forbidden_p = local_variables_forbidden_p; + + /* Remove any member-function parameters from the symbol table. */ + pop_injected_parms (); + + /* Remove any template parameters from the symbol table. */ + maybe_end_member_template_processing (); + } + vec_safe_truncate (unparsed_contracts, 0); + current_class_ptr = NULL_TREE; current_class_ref = NULL_TREE; switch_to_class (NULL_TREE); @@ -32587,9 +32683,9 @@ cp_nth_tokens_can_be_std_attribute_p (cp_parser *parser, size_t n) cp_token *token = cp_lexer_peek_nth_token (parser->lexer, n); return ((token->type == CPP_KEYWORD && token->keyword == RID_ALIGNAS) - || (token->type == CPP_OPEN_SQUARE - && (token = cp_lexer_peek_nth_token (parser->lexer, n + 1)) - && token->type == CPP_OPEN_SQUARE)); + || (token->type == CPP_OPEN_SQUARE + && (token = cp_lexer_peek_nth_token (parser->lexer, n + 1)) + && token->type == CPP_OPEN_SQUARE)); } /* Return TRUE iff the next Nth tokens in the stream are possibly the @@ -33327,6 +33423,388 @@ cp_parser_annotation_list (cp_parser *parser) return attributes; } +/* Parse a contract condition for a deferred contract. */ + +void +cp_parser_late_contract_condition (cp_parser *parser, tree fn, tree contract) +{ + tree condition = CONTRACT_CONDITION (contract); + tree r_ident = NULL_TREE; + if (TREE_CODE (contract) == POSTCONDITION_STMT) + r_ident = POSTCONDITION_IDENTIFIER (contract); + + tree type = TREE_TYPE (TREE_TYPE (fn)); + location_t r_loc = UNKNOWN_LOCATION; + if (r_ident) + { + r_loc = EXPR_LOCATION (r_ident); + r_ident = tree_strip_any_location_wrapper (r_ident); + if (r_loc == UNKNOWN_LOCATION) + r_loc = cp_expr_location (contract); + if (!check_postcondition_result (fn, type, r_loc)) + { + invalidate_contract (contract); + return; + } + } + + /* Contracts allow access to members only through explicit use of 'this' + pointer. */ + tree saved_ccr = current_class_ref; + tree saved_ccp = current_class_ptr; + tree saved_contract_ccp = contract_class_ptr; + + if ((DECL_CONSTRUCTOR_P (fn) && PRECONDITION_P (contract)) + || (DECL_DESTRUCTOR_P (fn) && POSTCONDITION_P (contract))) + contract_class_ptr = current_class_ptr; + else + contract_class_ptr = NULL_TREE; + + push_unparsed_function_queues (parser); + + /* Push the saved tokens onto the parser's lexer stack. */ + cp_token_cache *tokens = DEFPARSE_TOKENS (condition); + cp_parser_push_lexer_for_tokens (parser, tokens); + + /* Parse the condition, ensuring that parameters or the return variable + aren't flagged for use outside the body of a function. */ + begin_scope (sk_contract, fn); + bool old_pc = processing_postcondition; + processing_postcondition = POSTCONDITION_P (contract); + /* Build a fake variable for the result identifier. */ + tree result = NULL_TREE; + if (r_ident) + { + cp_expr result_id (r_ident, r_loc); + result = make_postcondition_variable (result_id, type); + ++processing_template_decl; + } + cp_expr parsed_condition = cp_parser_conditional_expression (parser); + /* Commit to changes. */ + update_late_contract (contract, result, parsed_condition); + if (r_ident) + --processing_template_decl; + + /* Rebuild the postcondition since we didn't do it in grokfndecl. */ + rebuild_postconditions (fn); + + /* Leave our temporary scope for the postcondition result. */ + processing_postcondition = old_pc; + gcc_checking_assert (scope_chain && scope_chain->bindings + && scope_chain->bindings->kind == sk_contract); + pop_bindings_and_leave_scope (); + + if (cp_lexer_next_token_is_not (parser->lexer, CPP_EOF)) + error_at (input_location, "expected conditional-expression"); + + /* Revert to the main lexer. */ + cp_parser_pop_lexer (parser); + + /* Restore the queue. */ + pop_unparsed_function_queues (parser); + + current_class_ref = saved_ccr; + current_class_ptr = saved_ccp; + contract_class_ptr = saved_contract_ccp; +} + +/* Parse deferred contracts of FNDECL. */ + +void +cp_parser_late_contracts (cp_parser *parser, tree fndecl) +{ + + tree new_contracts = NULL_TREE; + tree old_contracts = get_fn_contract_specifiers (fndecl); + + if (old_contracts == NULL_TREE || !contract_any_deferred_p (old_contracts)) + return; + + for (; old_contracts; old_contracts = TREE_CHAIN (old_contracts)) + { + tree contract = TREE_VALUE (TREE_VALUE (old_contracts)); + + tree condition = CONTRACT_CONDITION (contract); + /* All contracts should be deferred if one of them is deferred */ + gcc_checking_assert (TREE_CODE (condition) == DEFERRED_PARSE); + + cp_parser_late_contract_condition (parser, fndecl, contract); + tree list = tree_cons (TREE_PURPOSE (old_contracts), + TREE_VALUE (old_contracts), NULL_TREE); + new_contracts = chainon (new_contracts, list); + } + + update_fn_contract_specifiers (fndecl, new_contracts); +} + +static tree +cp_parser_contract_assert (cp_parser *parser, cp_token *token) +{ + if (!flag_contracts) + { + error_at (token->location, "%qs is only available with %qs", + "contract_assert", "-fcontracts"); + cp_parser_skip_to_end_of_statement (parser); + return error_mark_node; + } + + tree cont_assert = token->u.value; + + token = cp_lexer_consume_token (parser->lexer); + location_t loc = token->location; + + location_t attrs_loc = cp_lexer_peek_token (parser->lexer)->location; + tree std_attrs = cp_parser_std_attribute_spec_seq (parser); + if (std_attrs) + { + attrs_loc = make_location (attrs_loc, attrs_loc, input_location); + warning_at (attrs_loc, OPT_Wattributes, "attributes are ignored on %qs", + "contract_assert"); + std_attrs = NULL_TREE; + } + + matching_parens parens; + parens.require_open (parser); + /* Enable location wrappers when parsing contracts. */ + auto suppression = make_temp_override (suppress_location_wrappers, 0); + + /* Parse the condition. */ + begin_scope (sk_contract, current_function_decl); + bool old_pc = processing_postcondition; + processing_postcondition = false; + cp_expr condition = cp_parser_conditional_expression (parser); + gcc_checking_assert (scope_chain && scope_chain->bindings + && scope_chain->bindings->kind == sk_contract); + /* Build the contract. */ + tree contract = grok_contract (cont_assert, /*mode*/NULL_TREE, + /*result*/NULL_TREE, condition, loc); + processing_postcondition = old_pc; + pop_bindings_and_leave_scope (); + + parens.require_close (parser); + + if (!contract || contract == error_mark_node) + { + cp_parser_skip_to_end_of_statement (parser); + return error_mark_node; + } + + if (!cp_parser_require (parser, CPP_SEMICOLON, RT_SEMICOLON)) + return error_mark_node; + + add_stmt (contract); + return contract; +} + +/* Can the next tokens introduce a function contract specifier. */ + +static tree +cp_function_contract_specifier_intro (cp_parser *parser) +{ + cp_token *token = cp_lexer_peek_token (parser->lexer); + + /* Look for the contextual keywords 'pre' and 'post' as an introducer. */ + if (token->type != CPP_NAME) + return NULL_TREE; + + tree contract_name = token->u.value; + + if (!id_equal (contract_name, "pre") && !id_equal (contract_name, "post")) + /* If we don't have a valid contract start, we are done. */ + return NULL_TREE; + return contract_name; +} + +/* Look ahead to see if this might introduce a function contract specifier. + If not return NULL_TREE, if successful return the name (pre or post). */ + +static tree +cp_maybe_function_contract_specifier (cp_parser *parser) +{ + tree contract_name = cp_function_contract_specifier_intro (parser); + if (!contract_name) + return NULL_TREE; + + size_t n = 2; + if (cp_nth_tokens_can_be_std_attribute_p (parser, n)) + n = cp_parser_skip_std_attribute_spec_seq (parser, n); + if (cp_lexer_nth_token_is (parser->lexer, n, CPP_OPEN_PAREN)) + return contract_name; + return NULL_TREE; +} + +/* Parse a contract specifier. + + function-contract-specifier : + precondition-specifier + postcondition-specifier + precondition-specifier : + pre attribute-specifier-seqopt ( conditional-expression ) + postcondition-specifier : + post attribute-specifier-seqopt ( result-name-introduceropt conditional-expression ) + result-name-introducer : + attributed-identifier : + + Return void_list_node if the current token doesn't start a + contract specifier. */ + +static tree +cp_parser_function_contract_specifier (cp_parser *parser) +{ + tree contract_name = cp_function_contract_specifier_intro (parser); + if (!contract_name) + return NULL_TREE; + + cp_token *token = cp_lexer_peek_token (parser->lexer); + cp_lexer_consume_token (parser->lexer); + location_t loc = token->location; + bool postcondition_p = id_equal (contract_name, "post"); + + location_t attrs_loc = cp_lexer_peek_token (parser->lexer)->location; + tree std_attrs = cp_parser_std_attribute_spec_seq (parser); + if (std_attrs) + { + attrs_loc = make_location (attrs_loc, attrs_loc, input_location); + warning_at (attrs_loc, OPT_Wattributes, "attributes are ignored on" + " function contract specifiers"); + std_attrs = NULL_TREE; + } + + matching_parens parens; + parens.require_open (parser); + + /* Check for postcondition identifiers. */ + cp_expr identifier; + if (postcondition_p && cp_lexer_next_token_is (parser->lexer, CPP_NAME) + && cp_lexer_peek_nth_token (parser->lexer, 2)->type == CPP_COLON) + identifier = cp_parser_identifier (parser); + + if (identifier == error_mark_node) + { + cp_parser_skip_to_closing_parenthesis (parser, + /*recovering=*/true, + /*or_comma=*/false, + /*consume_paren=*/true); + return error_mark_node; + } + + if (identifier) + cp_parser_require (parser, CPP_COLON, RT_COLON); + + tree contract; + if (current_class_type && TYPE_BEING_DEFINED (current_class_type)) + { + /* Defer the parsing of pre/post contracts inside class definitions. */ + cp_token *first = cp_lexer_peek_token (parser->lexer); + + /* Skip until we reach a closing token ). */ + cp_parser_skip_to_closing_parenthesis (parser, + /*recovering=*/false, + /*or_comma=*/false, + /*consume_paren=*/false); + + cp_token *last = cp_lexer_peek_token (parser->lexer); + location_t end = last->location; + loc = make_location (loc, loc, end); + + parens.require_close (parser); + + /* Build a deferred-parse node. */ + tree condition = make_node (DEFERRED_PARSE); + DEFPARSE_TOKENS (condition) = cp_token_cache_new (first, last); + DEFPARSE_INSTANTIATIONS (condition) = NULL; + + /* And its corresponding contract. */ + if (identifier) + identifier.maybe_add_location_wrapper (); + contract = grok_contract (contract_name, /*mode*/NULL_TREE, identifier, + condition, loc); + } + else + { + /* Enable location wrappers when parsing contracts. */ + auto suppression = make_temp_override (suppress_location_wrappers, 0); + + /* Parse the condition, ensuring that parameters or the return variable + aren't flagged for use outside the body of a function. */ + begin_scope (sk_contract, current_function_decl); + bool old_pc = processing_postcondition; + processing_postcondition = postcondition_p; + tree result = NULL_TREE; + if (identifier) + { + /* Build a fake variable for the result identifier. */ + result = make_postcondition_variable (identifier); + ++processing_template_decl; + } + cp_expr condition = cp_parser_conditional_expression (parser); + /* Build the contract. */ + contract = grok_contract (contract_name, /*mode*/NULL_TREE, result, + condition, loc); + if (identifier) + --processing_template_decl; + processing_postcondition = old_pc; + gcc_checking_assert (scope_chain && scope_chain->bindings + && scope_chain->bindings->kind == sk_contract); + pop_bindings_and_leave_scope (); + + if (contract != error_mark_node) + { + location_t end = cp_lexer_peek_token (parser->lexer)->location; + loc = make_location (loc, loc, end); + SET_EXPR_LOCATION (contract, loc); + } + + parens.require_close (parser); + } + + if (!flag_contracts) + { + error_at (loc, "contracts are only available with %qs", "-fcontracts"); + return error_mark_node; + } + + return contract; +} + +/* Parse a contract specifier seq. Returns a list of preconditions and + postconditions in an attribute tree. + + function-contract-specifier-seq : + function-contract-specifier function-contract-specifier-seq. */ + +static tree +cp_parser_function_contract_specifier_seq (cp_parser *parser) +{ + tree contract_specs = NULL_TREE; + + while (true) + { + tree contract_spec = cp_parser_function_contract_specifier (parser); + + /* If there are no more contracts, done. */ + if (contract_spec == NULL_TREE) + break; + + /* Ignore any erroneous contracts and attempt to continue parsing. */ + if (contract_spec == error_mark_node) + continue; + + /* For now, turn this into an attribute. */ + tree contract_name = TREE_CODE (contract_spec) == PRECONDITION_STMT + ? get_identifier ("pre") + : get_identifier ("post"); + contract_spec = finish_contract_specifier (contract_name, contract_spec); + /* Arrange to build the list in the correct order. */ + if (contract_specs) + contract_specs = attr_chainon (contract_specs, contract_spec); + else + contract_specs = contract_spec; + } + + return contract_specs; +} + /* Parse a standard C++-11 attribute specifier. attribute-specifier: @@ -36429,6 +36907,9 @@ cp_parser_save_default_args (cp_parser* parser, tree decl) vec_safe_push (unparsed_noexcepts, entry); } + /* Contracts are deferred. */ + if (DECL_HAS_CONTRACTS_P (decl)) + vec_safe_push (unparsed_contracts, decl); } /* DEFAULT_ARG contains the saved tokens for the initializer of DECL, diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc index 6f6ca0876ce..60392f1a089 100644 --- a/gcc/cp/pt.cc +++ b/gcc/cp/pt.cc @@ -49,6 +49,7 @@ along with GCC; see the file COPYING3. If not see #include "builtins.h" #include "omp-general.h" #include "pretty-print-markup.h" +#include "contracts.h" /* The type of functions taking a tree, and some additional data, and returning an int. */ @@ -3270,6 +3271,8 @@ check_explicit_specialization (tree declarator, } decl = register_specialization (tmpl, gen_tmpl, targs, is_friend, 0); + if (flag_contracts) + remove_fn_contract_specifiers (result); return decl; } @@ -3362,6 +3365,11 @@ check_explicit_specialization (tree declarator, is_friend, 0); } + if (flag_contracts + && decl != error_mark_node + && DECL_TEMPLATE_SPECIALIZATION (decl)) + remove_fn_contract_specifiers (decl); + /* A 'structor should already have clones. */ gcc_assert (decl == error_mark_node || variable_template_p (tmpl) @@ -12169,6 +12177,142 @@ can_complete_type_without_circularity (tree type) static tree tsubst_omp_clauses (tree, enum c_omp_region_type, tree, tsubst_flags_t, tree); +/* Instantiate the contract statement. */ + +static tree +tsubst_contract (tree decl, tree t, tree args, tsubst_flags_t complain, + tree in_decl) +{ + tree type = decl ? TREE_TYPE (TREE_TYPE (decl)) : NULL_TREE; + bool auto_p = type_uses_auto (type); + + tree r = copy_node (t); + + /* Rebuild the result variable, if present. */ + tree oldvar = NULL_TREE; + tree newvar = NULL_TREE; + if (type && POSTCONDITION_P (t) && POSTCONDITION_IDENTIFIER (t)) + { + oldvar = POSTCONDITION_IDENTIFIER (t); + if (oldvar == error_mark_node) + return invalidate_contract (r); + + newvar = copy_node (oldvar); + TREE_TYPE (newvar) = type; + DECL_CONTEXT (newvar) = decl; + POSTCONDITION_IDENTIFIER (r) = newvar; + + /* Make sure the postcondition is valid. */ + location_t loc = DECL_SOURCE_LOCATION (oldvar); + if (!auto_p) + if (!check_postcondition_result (decl, type, loc)) + return invalidate_contract (r); + } + + /* Instantiate the condition. If the return type is undeduced, process + the expression as if inside a template to avoid spurious type errors. */ + begin_scope (sk_contract, decl); + bool old_pc = processing_postcondition; + processing_postcondition = POSTCONDITION_P (t); + if (auto_p) + ++processing_template_decl; + if (newvar) + /* Make the variable available for lookup. */ + register_local_specialization (newvar, oldvar); + + /* Contract conditions have a wider application of location wrappers than + other trees (which will not work with the generic handling in tsubst_expr), + remove the wrapper here... */ + location_t cond_l = EXPR_LOCATION (CONTRACT_CONDITION (t)); + tree cond_t = tree_strip_any_location_wrapper (CONTRACT_CONDITION (t)); + + /* ... and substitute the contained expression. */ + cond_t = tsubst_expr (cond_t, args, complain, in_decl); + + /* Convert to bool, if possible, and then re-apply a location wrapper + when required. */ + cp_expr new_condition (cond_t, cond_l); + CONTRACT_CONDITION (r) = finish_contract_condition (new_condition); + + /* At present, the semantic, kind and comment cannot be dependent. */ + gcc_checking_assert + (!type_dependent_expression_p (CONTRACT_EVALUATION_SEMANTIC (r)) + && !type_dependent_expression_p (CONTRACT_ASSERTION_KIND (r)) + && !type_dependent_expression_p (CONTRACT_COMMENT (r))); + + if (auto_p) + --processing_template_decl; + processing_postcondition = old_pc; + gcc_checking_assert (scope_chain && scope_chain->bindings + && scope_chain->bindings->kind == sk_contract); + pop_bindings_and_leave_scope (); + + return r; +} + +/* Update T instantiating a contract specifier. */ + +static void +tsubst_contract_specifier (tree decl, tree t, tree args, + tsubst_flags_t complain, tree in_decl) +{ + /* For non-specializations, adjust the current declaration to the most general + version of in_decl. Because we defer the instantiation of contracts as long + as possible, they are still written in terms of the parameters (and return + type) of the most general template. */ + tree tmpl = DECL_TI_TEMPLATE (in_decl); + if (!DECL_TEMPLATE_SPECIALIZATION (tmpl)) + in_decl = DECL_TEMPLATE_RESULT (most_general_template (in_decl)); + local_specialization_stack specs (lss_copy); + register_parameter_specializations (in_decl, decl); + + /* Get the contract to be instantiated. */ + tree contract = CONTRACT_STATEMENT (t); + + /* Use the complete set of template arguments for instantiation. The + contract may not have been instantiated and still refer to outer levels + of template parameters. */ + args = DECL_TI_ARGS (decl); + + /* For member functions, make this available for semantic analysis. */ + tree save_ccp = current_class_ptr; + tree save_ccr = current_class_ref; + if (DECL_IOBJ_MEMBER_FUNCTION_P (decl)) + { + tree arg_types = TYPE_ARG_TYPES (TREE_TYPE (decl)); + tree this_type = TREE_TYPE (TREE_VALUE (arg_types)); + inject_this_parameter (this_type, cp_type_quals (this_type)); + } + + contract = tsubst_contract (decl, contract, args, complain, in_decl); + + current_class_ptr = save_ccp; + current_class_ref = save_ccr; + + /* Rebuild the attribute. */ + TREE_VALUE (t) = build_tree_list (NULL_TREE, contract); +} + +/* For unsubstituted list of contracts in SPECIFIERS, instantiate contracts + for DECL and set the list as contracts for decl. Substitution creates a deep + copy of the contract. */ + +void +tsubst_contract_specifiers (tree specfiers, tree decl, tree args, + tsubst_flags_t complain, tree in_decl) +{ + tree subst_contract_list = NULL_TREE; + for (tree spec = specfiers; spec; spec = TREE_CHAIN (spec)) + { + tree nc = copy_node (spec); + tsubst_contract_specifier (decl, nc, args, complain, in_decl); + TREE_CHAIN (nc) = subst_contract_list; + subst_contract_list = nc; + } + if (flag_contracts) + set_fn_contract_specifiers (decl, nreverse (subst_contract_list)); +} + /* Instantiate a single dependent attribute T (a TREE_LIST), and return either T or a new TREE_LIST, possibly a chain in the case of a pack expansion. */ @@ -15121,6 +15265,11 @@ tsubst_function_decl (tree t, tree args, tsubst_flags_t complain, if (tree ci = get_constraints (t)) set_constraints (r, ci); + /* copy_decl () does not know about contract specifiers. NOTE these are not + substituted at this point. */ + if (tree ctrct = get_fn_contract_specifiers (t)) + set_fn_contract_specifiers (r, ctrct); + if (DECL_FRIEND_CONTEXT (t)) SET_DECL_FRIEND_CONTEXT (r, tsubst (DECL_FRIEND_CONTEXT (t), @@ -19243,6 +19392,19 @@ tsubst_stmt (tree t, tree args, tsubst_flags_t complain, tree in_decl) finish_using_directive (USING_STMT_NAMESPACE (t), /*attribs=*/NULL_TREE); break; + case PRECONDITION_STMT: + case POSTCONDITION_STMT: + gcc_unreachable (); + + case ASSERTION_STMT: + { + r = tsubst_contract (NULL_TREE, t, args, complain, in_decl); + if (r != error_mark_node) + add_stmt (r); + RETURN (r); + } + break; + case DECL_EXPR: { tree decl, pattern_decl; @@ -27822,6 +27984,19 @@ regenerate_decl_from_template (tree decl, tree tmpl, tree args) DECL_CONTEXT (t) = decl; } + if (tree attr = get_fn_contract_specifiers (decl)) + { + /* If we're regenerating a specialization, the contracts will have + been copied from the most general template. Replace those with + the ones from the actual specialization. */ + tree tmpl = DECL_TI_TEMPLATE (decl); + if (DECL_TEMPLATE_SPECIALIZATION (tmpl)) + attr = get_fn_contract_specifiers (code_pattern); + + tsubst_contract_specifiers (attr, decl, args, + tf_warning_or_error, code_pattern); + } + /* Merge additional specifiers from the CODE_PATTERN. */ if (DECL_DECLARED_INLINE_P (code_pattern) && !DECL_DECLARED_INLINE_P (decl)) diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc index c188a7e7c1b..9191472c7b1 100644 --- a/gcc/cp/semantics.cc +++ b/gcc/cp/semantics.cc @@ -46,6 +46,7 @@ along with GCC; see the file COPYING3. If not see #include "predict.h" #include "memmodel.h" #include "gimplify.h" +#include "contracts.h" /* There routines provide a modular interface to perform many parsing operations. They may therefore be used during actual parsing, or @@ -609,7 +610,8 @@ set_one_cleanup_loc (tree t, location_t loc) if (!t) return; - protected_set_expr_location (t, loc); + if (TREE_CODE (t) != POSTCONDITION_STMT) + protected_set_expr_location (t, loc); /* Avoid locus differences for C++ cdtor calls depending on whether cdtor_returns_this: a conversion to void is added to discard the return @@ -2763,6 +2765,14 @@ finish_non_static_data_member (tree decl, tree object, tree qualifying_scope, if (current_function_decl && DECL_STATIC_FUNCTION_P (current_function_decl)) error ("invalid use of member %qD in static member function", decl); + else if (current_function_decl + && processing_contract_condition + && DECL_CONSTRUCTOR_P (current_function_decl)) + error ("invalid use of member %qD in constructor % contract", decl); + else if (current_function_decl + && processing_contract_condition + && DECL_DESTRUCTOR_P (current_function_decl)) + error ("invalid use of member %qD in destructor % contract", decl); else error ("invalid use of non-static data member %qD", decl); inform (DECL_SOURCE_LOCATION (decl), "declared here"); @@ -3656,6 +3666,10 @@ finish_this_expr (void) } else if (fn && DECL_STATIC_FUNCTION_P (fn)) error ("% is unavailable for static member functions"); + else if (fn && processing_contract_condition && DECL_CONSTRUCTOR_P (fn)) + error ("invalid use of % in a constructor % condition"); + else if (fn && processing_contract_condition && DECL_DESTRUCTOR_P (fn)) + error ("invalid use of % in a destructor % condition"); else if (fn) error ("invalid use of % in non-member function"); else @@ -4679,6 +4693,9 @@ process_outer_var_ref (tree decl, tsubst_flags_t complain, bool odr_use) } return error_mark_node; } + else if (processing_contract_condition && (TREE_CODE (decl) == PARM_DECL)) + /* Use of a parameter in a contract condition is fine. */ + return decl; else { if (complain & tf_error) @@ -4815,6 +4832,7 @@ finish_id_expression_1 (tree id_expression, && DECL_CONTEXT (decl) == NULL_TREE && !CONSTRAINT_VAR_P (decl) && !cp_unevaluated_operand + && !processing_contract_condition && !processing_omp_trait_property_expr) { *error_msg = G_("use of parameter outside function body"); @@ -4991,6 +5009,14 @@ finish_id_expression_1 (tree id_expression, } else if (TREE_CODE (decl) == FIELD_DECL) { + if (flag_contracts && processing_contract_condition + && contract_class_ptr == current_class_ptr) + { + error ("%qD 'this' required when accessing a member within a " + "constructor precondition or destructor postcondition " + "contract check", decl); + return error_mark_node; + } /* Since SCOPE is NULL here, this is an unqualified name. Access checking has been performed during name lookup already. Turn off checking to avoid duplicate errors. */ @@ -5014,6 +5040,14 @@ finish_id_expression_1 (tree id_expression, && !shared_member_p (decl)))) { /* A set of member functions. */ + if (flag_contracts && processing_contract_condition + && contract_class_ptr == current_class_ptr) + { + error ("%qD 'this' required when accessing a member within a " + "constructor precondition or destructor postcondition " + "contract check", decl); + return error_mark_node; + } decl = maybe_dummy_object (DECL_CONTEXT (first_fn), 0); return finish_class_member_access_expr (decl, id_expression, /*template_p=*/false, @@ -14438,6 +14472,8 @@ apply_deduced_return_type (tree fco, tree return_type) TREE_TYPE (fco) = change_return_type (return_type, TREE_TYPE (fco)); + maybe_update_postconditions (fco); + /* Apply the type to the result object. */ result = DECL_RESULT (fco); diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc index ab707c0a9d3..55afa3efb27 100644 --- a/gcc/cp/tree.cc +++ b/gcc/cp/tree.cc @@ -4119,6 +4119,26 @@ cp_tree_equal (tree t1, tree t2) code1 = TREE_CODE (t1); code2 = TREE_CODE (t2); + if (comparing_contracts) + { + /* When comparing contracts, one declaration may already be + genericized. Check for invisible references and unravel them + for comparison purposes. Remember that a parameter is an invisible + reference so we can compare the parameter types accordingly. */ + if (code1 == VIEW_CONVERT_EXPR + && is_invisiref_parm (TREE_OPERAND(t1, 0))) + { + t1 = TREE_OPERAND(t1, 0); + code1 = TREE_CODE(t1); + } + if (code2 == VIEW_CONVERT_EXPR + && is_invisiref_parm (TREE_OPERAND(t2, 0))) + { + t2 = TREE_OPERAND(t2, 0); + code2 = TREE_CODE(t2); + } + } + if (code1 != code2) return false; @@ -4281,8 +4301,14 @@ cp_tree_equal (tree t1, tree t2) with parameters with identical contexts. */ return false; - if (same_type_p (TREE_TYPE (t1), TREE_TYPE (t2))) + if (same_type_p (TREE_TYPE (t1), TREE_TYPE (t2)) || comparing_contracts) { + /* When comparing contracts, we already know the declarations match, + and that the arguments have the same type. If one of the declarations + has been genericised, then the type of arguments in that declaration + will be adjusted for an invisible reference and the type comparison + would spuriosly fail. The only thing we care about when comparing + contractsis that we're using the same parameter. */ if (DECL_ARTIFICIAL (t1) ^ DECL_ARTIFICIAL (t2)) return false; if (CONSTRAINT_VAR_P (t1) ^ CONSTRAINT_VAR_P (t2)) diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 95817e36a4f..dbffbc13114 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -224,6 +224,8 @@ in the following sections. -fconcepts -fconcepts-diagnostics-depth=@var{n} -fconstexpr-depth=@var{n} -fconstexpr-cache-depth=@var{n} -fconstexpr-loop-limit=@var{n} -fconstexpr-ops-limit=@var{n} +-fcontracts +-fcontract-evaluation-semantic=@r{[}ignore@r{|}observe@r{|}enforce@r{|}quick_enforce@r{]} -fcoroutines -fdiagnostics-all-candidates -fno-elide-constructors -fno-enforce-eh-specs @@ -3311,6 +3313,48 @@ of a loop too many expressions need to be evaluated, the resulting constexpr evaluation might take too long. The default is 33554432 (1<<25). +@opindex fcontracts +@opindex fno-contracts +@item -fcontracts +Enable support for the C++ Contracts feature, as specified in the C++26 +working draft (N5003). + +On violation of an enforced or observed contract (see @option{-fcontract-evaluation-semantic} +below), a violation handler is called; the standard library provides a default +handler that emits information about the contract that failed. + +Users can replace the default violation handler by defining +@smallexample +void +handle_contract_violation (const std::contracts::contract_violation&); +@end smallexample + +@opindex fcontract-evaluation-semantic +@item -fcontract-evaluation-semantic=@var{semantic} +Set the semantic with which contracts will be evaluated to @var{semantic}. + +Available values for the evaluation mode are: + +'@code{ignored}' The contract checks will be elided, with no checking added at +either compile or runtime. + +'@code{observed}' The contract checks are performed (at runtime) and, if one +fails, a handler is called that allows actions such as logging or emitting +run-time warnings. When the handler returns, the execution of the code will +continue. At compile-time the checks are performed for @code{constexpr} +evaluations, in this case a diagnostic is emitted if the check fails. + +'@code{enforce}' The contract checks are performed and the handler is called if +one fails. When the handler returns the execution of the code is terminated +preventing it from continuing past the failed case. At compile-time the checks +are applied to @code{constexpr} and, if one fails, the program is ill-formed; +an error will be emitted. + +'@code{quick_enforce}' The contract checks are performed (at runtime) and, if one +fails, the execution is terminated immediately (without calling any handler). +At compile-time there is no difference in behaviour between this option and +the @code{enforce} case, since no handler is invoked at compile time. + @opindex fcoroutines @opindex fno-coroutines @item -fcoroutines diff --git a/gcc/testsuite/g++.dg/contracts/cpp26/basic.contract.eval.p8-2.C b/gcc/testsuite/g++.dg/contracts/cpp26/basic.contract.eval.p8-2.C new file mode 100644 index 00000000000..5f889dbe9a1 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/cpp26/basic.contract.eval.p8-2.C @@ -0,0 +1,12 @@ +// basic.contract.eval/p8 +// If a contract violation occurs in a context that is manifestly +// constant-evaluated ([expr.const]), and the evaluation semantic is not +// terminating, then a diagnostic shall be emitted. +// { dg-do compile { target c++23 } } +// { dg-additional-options "-fcontracts -fcontract-evaluation-semantic=observe " } + +consteval void foo( auto x ) pre( false ) {} +// { dg-warning {contract predicate is false in constant expression} "" { target *-*-* } .-1 } +int main() { + foo( 42 ); +} diff --git a/gcc/testsuite/g++.dg/warn/Wkeyword-macro-1.C b/gcc/testsuite/g++.dg/warn/Wkeyword-macro-1.C index 1e2e29cee2d..ab21db84e4e 100644 --- a/gcc/testsuite/g++.dg/warn/Wkeyword-macro-1.C +++ b/gcc/testsuite/g++.dg/warn/Wkeyword-macro-1.C @@ -26,7 +26,7 @@ #define constexpr 1 // { dg-error "keyword 'constexpr' defined as macro" "" { target c++26 } } #define constinit 1 // { dg-error "keyword 'constinit' defined as macro" "" { target c++26 } } #define continue 1 // { dg-error "keyword 'continue' defined as macro" "" { target c++26 } } -#define contract_assert 1 +#define contract_assert 1 // { dg-error "keyword 'contract_assert' defined as macro" "" { target c++26 } } #define co_return 1 // { dg-error "keyword 'co_return' defined as macro" "" { target c++26 } } #define co_yield 1 // { dg-error "keyword 'co_yield' defined as macro" "" { target c++26 } } #define decltype 1 // { dg-error "keyword 'decltype' defined as macro" "" { target c++26 } } diff --git a/gcc/testsuite/g++.dg/warn/Wkeyword-macro-2.C b/gcc/testsuite/g++.dg/warn/Wkeyword-macro-2.C index 3e63c3e7bc4..3d1382f9ec4 100644 --- a/gcc/testsuite/g++.dg/warn/Wkeyword-macro-2.C +++ b/gcc/testsuite/g++.dg/warn/Wkeyword-macro-2.C @@ -26,7 +26,7 @@ #define constexpr 1 // { dg-warning "keyword 'constexpr' defined as macro" "" { target c++26 } } #define constinit 1 // { dg-warning "keyword 'constinit' defined as macro" "" { target c++26 } } #define continue 1 // { dg-warning "keyword 'continue' defined as macro" "" { target c++26 } } -#define contract_assert 1 +#define contract_assert 1 // { dg-warning "keyword 'contract_assert' defined as macro" "" { target c++26 } } #define co_return 1 // { dg-warning "keyword 'co_return' defined as macro" "" { target c++26 } } #define co_yield 1 // { dg-warning "keyword 'co_yield' defined as macro" "" { target c++26 } } #define decltype 1 // { dg-warning "keyword 'decltype' defined as macro" "" { target c++26 } } diff --git a/gcc/testsuite/g++.dg/warn/Wkeyword-macro-4.C b/gcc/testsuite/g++.dg/warn/Wkeyword-macro-4.C index 334508cb0f8..7f27857e4f3 100644 --- a/gcc/testsuite/g++.dg/warn/Wkeyword-macro-4.C +++ b/gcc/testsuite/g++.dg/warn/Wkeyword-macro-4.C @@ -26,7 +26,7 @@ #undef constexpr // { dg-error "undefining keyword 'constexpr'" "" { target c++26 } } #undef constinit // { dg-error "undefining keyword 'constinit'" "" { target c++26 } } #undef continue // { dg-error "undefining keyword 'continue'" "" { target c++26 } } -#undef contract_assert +#undef contract_assert // { dg-error "undefining keyword 'contract_assert'" "" { target c++26 } } #undef co_return // { dg-error "undefining keyword 'co_return'" "" { target c++26 } } #undef co_yield // { dg-error "undefining keyword 'co_yield'" "" { target c++26 } } #undef decltype // { dg-error "undefining keyword 'decltype'" "" { target c++26 } } diff --git a/gcc/testsuite/g++.dg/warn/Wkeyword-macro-5.C b/gcc/testsuite/g++.dg/warn/Wkeyword-macro-5.C index 87c53499a0b..d5c9f4d03bf 100644 --- a/gcc/testsuite/g++.dg/warn/Wkeyword-macro-5.C +++ b/gcc/testsuite/g++.dg/warn/Wkeyword-macro-5.C @@ -26,7 +26,7 @@ #undef constexpr // { dg-warning "undefining keyword 'constexpr'" "" { target c++26 } } #undef constinit // { dg-warning "undefining keyword 'constinit'" "" { target c++26 } } #undef continue // { dg-warning "undefining keyword 'continue'" "" { target c++26 } } -#undef contract_assert +#undef contract_assert // { dg-warning "undefining keyword 'contract_assert'" "" { target c++26 } } #undef co_return // { dg-warning "undefining keyword 'co_return'" "" { target c++26 } } #undef co_yield // { dg-warning "undefining keyword 'co_yield'" "" { target c++26 } } #undef decltype // { dg-warning "undefining keyword 'decltype'" "" { target c++26 } } diff --git a/gcc/testsuite/g++.dg/warn/Wkeyword-macro-7.C b/gcc/testsuite/g++.dg/warn/Wkeyword-macro-7.C index 66bfa463ba5..1f1ea19710b 100644 --- a/gcc/testsuite/g++.dg/warn/Wkeyword-macro-7.C +++ b/gcc/testsuite/g++.dg/warn/Wkeyword-macro-7.C @@ -3,6 +3,7 @@ // { dg-do preprocess } // { dg-options "-Wkeyword-macro" } // { dg-additional-options "-fmodules" { target c++20 } } +// { dg-additional-options "-fcontracts" { target c++26 } } // [lex.key] #define alignas 1 // { dg-warning "keyword 'alignas' defined as macro" "" { target c++11 } } @@ -26,7 +27,7 @@ #define constexpr 1 // { dg-warning "keyword 'constexpr' defined as macro" "" { target c++11 } } #define constinit 1 // { dg-warning "keyword 'constinit' defined as macro" "" { target c++20 } } #define continue 1 // { dg-warning "keyword 'continue' defined as macro" } -#define contract_assert 1 +#define contract_assert 1 // { dg-warning "keyword 'contract_assert' defined as macro" "" { target c++26 } } #define co_return 1 // { dg-warning "keyword 'co_return' defined as macro" "" { target c++20 } } #define co_yield 1 // { dg-warning "keyword 'co_yield' defined as macro" "" { target c++20 } } #define decltype 1 // { dg-warning "keyword 'decltype' defined as macro" "" { target c++11 } } diff --git a/gcc/testsuite/g++.dg/warn/Wkeyword-macro-8.C b/gcc/testsuite/g++.dg/warn/Wkeyword-macro-8.C index e7cc35d22d8..1c5bf7917e5 100644 --- a/gcc/testsuite/g++.dg/warn/Wkeyword-macro-8.C +++ b/gcc/testsuite/g++.dg/warn/Wkeyword-macro-8.C @@ -3,6 +3,7 @@ // { dg-do preprocess } // { dg-options "-Wkeyword-macro" } // { dg-additional-options "-fmodules" { target c++20 } } +// { dg-additional-options "-fcontracts" { target c++26 } } // [lex.key] #undef alignas // { dg-warning "undefining keyword 'alignas'" "" { target c++11 } } @@ -26,7 +27,7 @@ #undef constexpr // { dg-warning "undefining keyword 'constexpr'" "" { target c++11 } } #undef constinit // { dg-warning "undefining keyword 'constinit'" "" { target c++20 } } #undef continue // { dg-warning "undefining keyword 'continue'" } -#undef contract_assert +#undef contract_assert // { dg-warning "undefining keyword 'contract_assert'" "" { target c++26 } } #undef co_return // { dg-warning "undefining keyword 'co_return'" "" { target c++20 } } #undef co_yield // { dg-warning "undefining keyword 'co_yield'" "" { target c++20 } } #undef decltype // { dg-warning "undefining keyword 'decltype'" "" { target c++11 } }