From: Nina Ranns Date: Tue, 14 Oct 2025 11:37:48 +0000 (+0100) Subject: c++, contracts: Apply P200R14 constification. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=080b1cb5e4ad4af39c3a9e081dd8c382c0954cf6;p=thirdparty%2Fgcc.git c++, contracts: Apply P200R14 constification. Split from the main patch as it was potentially contentious and might have been altered by WG21 NB comment resolution. However, it most likely makes sense to review in isolation (although we would expect to apply it squashed into the base patch). gcc/cp/ChangeLog: * contracts.cc (view_as_const, constify_contract_access, set_parm_used_in_post, check_param_in_postcondition, parm_used_in_post_p, check_postconditions_in_redecl): New. (check_redecl_contract): Handle constification. * contracts.h (constify_contract_access, view_as_const, contract_const_wrapper_p, strip_contract_const_wrapper): New. * cp-tree.h: Update tree flag usage comment. * lambda.cc (build_capture_proxy): Handle constification. * parser.cc (cp_parser_late_contract_condition, cp_parser_contract_assert, cp_parser_function_contract_specifier): Likewise. * pt.cc (tsubst_function_decl, tsubst_expr): Likewise. * semantics.cc (finish_id_expression_1, finish_decltype_type): Likewise. gcc/ChangeLog: * tree.h (CONST_WRAPPER_P): New. gcc/ChangeLog: * tree-core.h (struct tree_base): Update tree flag usage comment. Signed-off-by: Iain Sandoe Co-authored-by: Iain Sandoe --- diff --git a/gcc/cp/contracts.cc b/gcc/cp/contracts.cc index a98f9a68e46..cae1be9d991 100644 --- a/gcc/cp/contracts.cc +++ b/gcc/cp/contracts.cc @@ -476,6 +476,128 @@ finish_contract_condition (cp_expr condition) return condition_conversion (condition); } +/* Wrap the DECL into VIEW_CONVERT_EXPR representing const qualified version + of the declaration. */ + +tree +view_as_const (tree decl) +{ + if (!contract_const_wrapper_p (decl)) + { + tree ctype = TREE_TYPE (decl); + location_t loc = + EXPR_P (decl) ? EXPR_LOCATION (decl) : DECL_SOURCE_LOCATION (decl); + ctype = cp_build_qualified_type (ctype, (cp_type_quals (ctype) + | TYPE_QUAL_CONST)); + decl = build1 (VIEW_CONVERT_EXPR, ctype, decl); + SET_EXPR_LOCATION (decl, loc); + /* Mark the VCE as contract const wrapper. */ + CONST_WRAPPER_P (decl) = true; + } + return decl; +} + +/* Constify access to DECL from within the contract condition. */ + +tree +constify_contract_access (tree decl) +{ + /* We check if we have a variable, a parameter, a variable of reference type, + * or a parameter of reference type + */ + if (!TREE_READONLY (decl) + && (VAR_P (decl) + || (TREE_CODE (decl) == PARM_DECL) + || (REFERENCE_REF_P (decl) + && (VAR_P (TREE_OPERAND (decl, 0)) + || (TREE_CODE (TREE_OPERAND (decl, 0)) == PARM_DECL) + || (TREE_CODE (TREE_OPERAND (decl, 0)) + == TEMPLATE_PARM_INDEX))))) + decl = view_as_const (decl); + + return decl; +} + +/* Indicate that PARM_DECL DECL is ODR used in a postcondition. */ + +static void +set_parm_used_in_post (tree decl, bool constify = true) +{ + gcc_checking_assert (TREE_CODE (decl) == PARM_DECL); + DECL_LANG_FLAG_4 (decl) = constify; +} + +/* Test if PARM_DECL is ODR used in a postcondition. */ + +static bool +parm_used_in_post_p (const_tree decl) +{ + /* Check if this parameter is odr used within a function's postcondition */ + return ((TREE_CODE (decl) == PARM_DECL) && DECL_LANG_FLAG_4 (decl)); +} + +/* If declaration DECL is a PARM_DECL and it appears in a postcondition, then + check that it is not a non-const by-value param. LOCATION is where the + expression was found and is used for diagnostic purposes. */ + +void +check_param_in_postcondition (tree decl, location_t location) +{ + if (processing_postcondition + && TREE_CODE (decl) == PARM_DECL + /* TREE_CODE (decl) == PARM_DECL only holds for non-reference + parameters. */ + && !cp_unevaluated_operand + /* Return value parameter has DECL_ARTIFICIAL flag set. The flag + presence of the flag should be sufficient to distinguish the + return value parameter in this context. */ + && !(DECL_ARTIFICIAL (decl))) + { + set_parm_used_in_post (decl); + + if (!dependent_type_p (TREE_TYPE (decl)) + && !CP_TYPE_CONST_P (TREE_TYPE (decl))) + { + error_at (location, + "a value parameter used in a postcondition must be const"); + inform (DECL_SOURCE_LOCATION (decl), "parameter declared here"); + } + } +} + +/* Check if parameters used in postconditions are const qualified on + a redeclaration that does not specify contracts or on an instantiation + of a function template. */ + +void +check_postconditions_in_redecl (tree olddecl, tree newdecl) +{ + tree contract_spec = get_fn_contract_specifiers (olddecl); + if (!contract_spec) + return; + + tree t1 = FUNCTION_FIRST_USER_PARM (olddecl); + tree t2 = FUNCTION_FIRST_USER_PARM (newdecl); + + for (; t1 && t1 != void_list_node; + t1 = TREE_CHAIN (t1), t2 = TREE_CHAIN (t2)) + { + if (parm_used_in_post_p (t1)) + { + set_parm_used_in_post (t2); + if (!dependent_type_p (TREE_TYPE (t2)) + && !CP_TYPE_CONST_P (TREE_TYPE (t2)) + && !TREE_READONLY (t2)) + { + error_at (DECL_SOURCE_LOCATION (t2), + "value parameter %qE used in a postcondition must be const", t2); + inform (DECL_SOURCE_LOCATION (olddecl), + "previous declaration here"); + } + } + } +} + void maybe_update_postconditions (tree fndecl) { @@ -1013,7 +1135,10 @@ check_redecl_contract (tree newdecl, tree olddecl) } if (old_contracts && !new_contracts) - return; + /* We allow re-declarations to omit contracts declared on the initial decl. + In fact, this is required if the conditions contain lambdas. Check if + all the parameters are correctly const qualified. */ + check_postconditions_in_redecl (olddecl, newdecl); else if (old_contracts && new_contracts && !contract_any_deferred_p (old_contracts) && contract_any_deferred_p (new_contracts) diff --git a/gcc/cp/contracts.h b/gcc/cp/contracts.h index 67ba65a1752..ba5a203321b 100644 --- a/gcc/cp/contracts.h +++ b/gcc/cp/contracts.h @@ -131,6 +131,8 @@ extern void update_late_contract (tree, tree, cp_expr); extern void check_redecl_contract (tree, tree); extern tree invalidate_contract (tree); extern tree copy_and_remap_contracts (tree, tree); +extern tree constify_contract_access (tree); +extern tree view_as_const (tree); extern void set_fn_contract_specifiers (tree, tree); extern void update_fn_contract_specifiers (tree, tree); @@ -162,6 +164,29 @@ set_decl_contracts (tree decl, tree contract_attrs) set_fn_contract_specifiers (decl, contract_attrs); } +/* Test if EXP is a contract const wrapper node. */ + +inline bool +contract_const_wrapper_p (const_tree exp) +{ + /* A wrapper node has code VIEW_CONVERT_EXPR, and the flag base.private_flag + is set. The wrapper node is used to used to constify entities inside + contract assertions. */ + return ((TREE_CODE (exp) == VIEW_CONVERT_EXPR) && CONST_WRAPPER_P (exp)); +} + +/* If EXP is a contract_const_wrapper_p, return the wrapped expression. + Otherwise, do nothing. */ + +inline tree +strip_contract_const_wrapper (tree exp) +{ + if (contract_const_wrapper_p (exp)) + return TREE_OPERAND (exp, 0); + else + return exp; +} + /* TODO : decide if we should push the tests into contracts.cc */ extern contract_evaluation_semantic get_evaluation_semantic (const_tree); diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index ef663905680..9153ff5b43d 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -582,6 +582,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX]; DECL_SELF_REFERENCE_P (in a TYPE_DECL) DECL_INVALID_OVERRIDER_P (in a FUNCTION_DECL) DECL_UNINSTANIATED_TEMPLATE_FRIEND_P (in TEMPLATE_DECL) + parm_used_in_post_p (in PARM_DECL) 5: DECL_INTERFACE_KNOWN. 6: DECL_THIS_STATIC (in VAR_DECL, FUNCTION_DECL or PARM_DECL) DECL_FIELD_IS_BASE (in FIELD_DECL) diff --git a/gcc/cp/lambda.cc b/gcc/cp/lambda.cc index 6bec288d4c5..402d5f7110a 100644 --- a/gcc/cp/lambda.cc +++ b/gcc/cp/lambda.cc @@ -33,6 +33,7 @@ along with GCC; see the file COPYING3. If not see #include "target.h" #include "decl.h" #include "flags.h" +#include "contracts.h" /* Constructor for a lambda expression. */ @@ -497,6 +498,8 @@ build_capture_proxy (tree member, tree init, bool early_p) init = PACK_EXPANSION_PATTERN (init); } + init = strip_contract_const_wrapper (init); + if (INDIRECT_REF_P (init)) init = TREE_OPERAND (init, 0); STRIP_NOPS (init); diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc index a8d373ea6b7..6e310f2c0fd 100644 --- a/gcc/cp/parser.cc +++ b/gcc/cp/parser.cc @@ -33466,6 +33466,12 @@ cp_parser_late_contract_condition (cp_parser *parser, tree fn, tree contract) cp_token_cache *tokens = DEFPARSE_TOKENS (condition); cp_parser_push_lexer_for_tokens (parser, tokens); + /* If we have a current class object, we need to consider + it const when processing the contract condition. */ + tree current_class_ref_copy = current_class_ref; + if (flag_contracts && current_class_ref_copy) + current_class_ref = view_as_const (current_class_ref_copy); + /* 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); @@ -33568,6 +33574,12 @@ cp_parser_contract_assert (cp_parser *parser, cp_token *token) /* Enable location wrappers when parsing contracts. */ auto suppression = make_temp_override (suppress_location_wrappers, 0); + /* If we have a current class object, see if we need to consider + it const when processing the contract condition. */ + tree current_class_ref_copy = current_class_ref; + if (current_class_ref_copy) + current_class_ref = view_as_const (current_class_ref_copy); + /* Parse the condition. */ begin_scope (sk_contract, current_function_decl); bool old_pc = processing_postcondition; @@ -33581,6 +33593,9 @@ cp_parser_contract_assert (cp_parser *parser, cp_token *token) processing_postcondition = old_pc; pop_bindings_and_leave_scope (); + /* Revert (any) constification of the current class object. */ + current_class_ref = current_class_ref_copy; + parens.require_close (parser); if (!contract || contract == error_mark_node) @@ -33725,6 +33740,12 @@ cp_parser_function_contract_specifier (cp_parser *parser) /* Enable location wrappers when parsing contracts. */ auto suppression = make_temp_override (suppress_location_wrappers, 0); + /* If we have a current class object, see if we need to consider + it const when processing the contract condition. */ + tree current_class_ref_copy = current_class_ref; + if (current_class_ref_copy) + current_class_ref = view_as_const (current_class_ref_copy); + /* 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); @@ -33748,6 +33769,9 @@ cp_parser_function_contract_specifier (cp_parser *parser) && scope_chain->bindings->kind == sk_contract); pop_bindings_and_leave_scope (); + /* Revert (any) constification of the current class object. */ + current_class_ref = current_class_ref_copy; + if (contract != error_mark_node) { location_t end = cp_lexer_peek_token (parser->lexer)->location; diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc index 60392f1a089..614b006069d 100644 --- a/gcc/cp/pt.cc +++ b/gcc/cp/pt.cc @@ -15270,6 +15270,9 @@ tsubst_function_decl (tree t, tree args, tsubst_flags_t complain, if (tree ctrct = get_fn_contract_specifiers (t)) set_fn_contract_specifiers (r, ctrct); + /* The parms have now been substituted, check for incorrect const cases. */ + check_postconditions_in_redecl (t, r); + if (DECL_FRIEND_CONTEXT (t)) SET_DECL_FRIEND_CONTEXT (r, tsubst (DECL_FRIEND_CONTEXT (t), @@ -23037,6 +23040,11 @@ tsubst_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl) /* force_paren_expr can also create a VIEW_CONVERT_EXPR. */ RETURN (finish_parenthesized_expr (op)); + check_param_in_postcondition (op, EXPR_LOCATION (t)); + + if (flag_contracts && processing_contract_condition) + op = constify_contract_access (op); + /* Otherwise, we're dealing with a wrapper to make a C++20 template parameter object const. */ if (TREE_TYPE (op) == NULL_TREE diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc index 9191472c7b1..0a15b32a16d 100644 --- a/gcc/cp/semantics.cc +++ b/gcc/cp/semantics.cc @@ -4886,6 +4886,10 @@ finish_id_expression_1 (tree id_expression, "integral or enumeration type", decl, TREE_TYPE (decl)); *non_integral_constant_expression_p = true; } + + if (flag_contracts && processing_contract_condition) + r = constify_contract_access (r); + return r; } else if (TREE_CODE (decl) == UNBOUND_CLASS_TEMPLATE) @@ -5083,6 +5087,10 @@ finish_id_expression_1 (tree id_expression, } } + check_param_in_postcondition (decl, location); + if (flag_contracts && processing_contract_condition) + decl = constify_contract_access (decl); + return cp_expr (decl, location); } @@ -13054,6 +13062,12 @@ finish_decltype_type (tree expr, bool id_expression_or_member_access_p, if (identifier_p (expr)) expr = lookup_name (expr); + /* If e is a constified expression inside a contract assertion, + strip the const wrapper. Per P2900R14, "For a function f with the + return type T , the result name is an lvalue of type const T , decltype(r) + is T , and decltype((r)) is const T&." */ + expr = strip_contract_const_wrapper (expr); + if (INDIRECT_REF_P (expr) || TREE_CODE (expr) == VIEW_CONVERT_EXPR) /* This can happen when the expression is, e.g., "a.b". Just diff --git a/gcc/tree-core.h b/gcc/tree-core.h index 4e158336c6c..07e9318f5e8 100644 --- a/gcc/tree-core.h +++ b/gcc/tree-core.h @@ -1387,6 +1387,9 @@ struct GTY(()) tree_base { ENUM_IS_OPAQUE in ENUMERAL_TYPE + CONST_WRAPPER_P in + VIEW_CONVERT_EXPR (used by C++) + protected_flag: TREE_PROTECTED in diff --git a/gcc/tree.h b/gcc/tree.h index b4734172f6b..dcd3ff85d04 100644 --- a/gcc/tree.h +++ b/gcc/tree.h @@ -796,6 +796,11 @@ extern void omp_clause_range_check_failed (const_tree, const char *, int, /* Determines whether an ENUMERAL_TYPE has defined the list of constants. */ #define ENUM_IS_OPAQUE(NODE) (ENUMERAL_TYPE_CHECK (NODE)->base.private_flag) +/* Determines whether a VIEW_CONVERT_EXPR node is used to create const + qualified variant of its first operand (used by C++ contracts). */ +#define CONST_WRAPPER_P(NODE) \ + (TREE_CHECK (NODE, VIEW_CONVERT_EXPR)->base.private_flag) + /* In an expr node (usually a conversion) this means the node was made implicitly and should not lead to any sort of warning. In a decl node, warnings concerning the decl should be suppressed. This is used at