From: Jason Merrill Date: Thu, 18 Sep 2025 11:10:55 +0000 (+0200) Subject: c++: improve constexpr clobber handling X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=b8af1b3a00249eceacf53444cdb339761eeb9f02;p=thirdparty%2Fgcc.git c++: improve constexpr clobber handling r16-3022 changed placement new to clobber the object, and improved constexpr handling to do more with clobbers. But it occurred to me that in a lot of cases we don't need to introduce a constructor_elt to represent an uninitialized member of an uninitialized struct/array. gcc/cp/ChangeLog: * constexpr.cc (get_or_insert_ctor_field): -2 means don't insert. (cxx_eval_component_reference): Handle finding void_node. (cxx_eval_store_expression): Don't represent initial clobber unless we need to activate a union member. (cxx_eval_statement_list): Don't ask for a void prvalue. (cxx_eval_loop_expr): The expr is discarded-value. (cxx_eval_constant_expression): A loose clobber is non-constant. Handle getting void_node instead of a real result. (potential_constant_expression_1): A local temp is potentially-constant. * init.cc (build_new_1): Don't clobber empty types or in a template. (build_vec_init): Fix clobber handling. gcc/testsuite/ChangeLog: * g++.dg/init/pr25811.C: Tweak diagnostic. * g++.dg/warn/Warray-bounds-12.C: Likewise. * g++.dg/warn/Warray-bounds-13.C: Likewise. * g++.dg/cpp26/constexpr-new6.C: New test. --- diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc index db363dc2760..1621e28da5c 100644 --- a/gcc/cp/constexpr.cc +++ b/gcc/cp/constexpr.cc @@ -5293,7 +5293,9 @@ find_array_ctor_elt (tree ary, tree dindex, bool insert) matching constructor_elt exists, then add one to CTOR. As an optimization, if POS_HINT is non-negative then it is used as a guess - for the (integer) index of the matching constructor_elt within CTOR. */ + for the (integer) index of the matching constructor_elt within CTOR. + + If POS_HINT is -2, it means do not insert. */ static constructor_elt * get_or_insert_ctor_field (tree ctor, tree index, int pos_hint = -1) @@ -5303,9 +5305,11 @@ get_or_insert_ctor_field (tree ctor, tree index, int pos_hint = -1) && CONSTRUCTOR_ELT (ctor, pos_hint)->index == index) return CONSTRUCTOR_ELT (ctor, pos_hint); + bool insertp = (pos_hint != -2); tree type = TREE_TYPE (ctor); if (TREE_CODE (type) == VECTOR_TYPE && index == NULL_TREE) { + gcc_assert (insertp); CONSTRUCTOR_APPEND_ELT (CONSTRUCTOR_ELTS (ctor), index, NULL_TREE); return &CONSTRUCTOR_ELTS (ctor)->last(); } @@ -5323,13 +5327,26 @@ get_or_insert_ctor_field (tree ctor, tree index, int pos_hint = -1) tree lo = TREE_OPERAND (index, 0); gcc_assert (array_index_cmp (elts->last().index, lo) < 0); } + gcc_assert (insertp); CONSTRUCTOR_APPEND_ELT (elts, index, NULL_TREE); return &elts->last(); } - HOST_WIDE_INT i = find_array_ctor_elt (ctor, index, /*insert*/true); - gcc_assert (i >= 0); + HOST_WIDE_INT i = find_array_ctor_elt (ctor, index, insertp); + if (i < 0) + { + gcc_assert (!insertp); + return nullptr; + } constructor_elt *cep = CONSTRUCTOR_ELT (ctor, i); + if (!insertp && cep->index && TREE_CODE (cep->index) == RANGE_EXPR) + { + /* Split a range even if we aren't inserting new entries. */ + gcc_assert (!insertp); + i = find_array_ctor_elt (ctor, index, /*insert*/true); + gcc_assert (i >= 0); + cep = CONSTRUCTOR_ELT (ctor, i); + } gcc_assert (cep->index == NULL_TREE || TREE_CODE (cep->index) != RANGE_EXPR); return cep; @@ -5384,6 +5401,9 @@ get_or_insert_ctor_field (tree ctor, tree index, int pos_hint = -1) entry at the end. */ insert: + if (!insertp) + return nullptr; + { constructor_elt ce = { index, NULL_TREE }; @@ -5745,6 +5765,8 @@ cxx_eval_component_reference (const constexpr_ctx *ctx, tree t, if (pmf ? DECL_NAME (field) == DECL_NAME (part) : field == part) { + if (value == void_node) + goto uninit; if (value) { STRIP_ANY_LOCATION_WRAPPER (value); @@ -5796,6 +5818,7 @@ cxx_eval_component_reference (const constexpr_ctx *ctx, tree t, if (CONSTRUCTOR_NO_CLEARING (whole)) { + uninit: /* 'whole' is part of the aggregate initializer we're currently building; if there's no initializer for this member yet, that's an error. */ @@ -7510,6 +7533,8 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, /* If we're modifying a const object, save it. */ tree const_object_being_modified = NULL_TREE; bool mutable_p = false; + /* If we see a union, we can't ignore clobbers. */ + int seen_union = 0; for (tree probe = target; object == NULL_TREE; ) { switch (TREE_CODE (probe)) @@ -7552,6 +7577,8 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, vec_safe_push (refs, elt); vec_safe_push (refs, TREE_TYPE (probe)); probe = ob; + if (TREE_CODE (TREE_TYPE (ob)) == UNION_TYPE) + ++seen_union; } break; @@ -7652,15 +7679,19 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, } /* Handle explicit end-of-lifetime. */ - if (TREE_CLOBBER_P (init) - && CLOBBER_KIND (init) >= CLOBBER_OBJECT_END) + if (TREE_CLOBBER_P (init)) { - if (refs->is_empty ()) + if (CLOBBER_KIND (init) >= CLOBBER_OBJECT_END + && refs->is_empty ()) { ctx->global->destroy_value (object); return void_node; } + if (!seen_union && !*valp + && CLOBBER_KIND (init) < CLOBBER_OBJECT_END) + return void_node; + /* Ending the lifetime of a const object is OK. */ const_object_being_modified = NULL_TREE; } @@ -7853,12 +7884,22 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, ctors.safe_push (valp); vec_safe_push (indexes, index); + /* Avoid adding an _elt for a clobber when the whole CONSTRUCTOR is + uninitialized. */ + int pos = (!seen_union && TREE_CLOBBER_P (init) + && CONSTRUCTOR_NO_CLEARING (*valp) + && CLOBBER_KIND (init) < CLOBBER_OBJECT_END) ? -2 : -1; constructor_elt *cep - = get_or_insert_ctor_field (*valp, index); + = get_or_insert_ctor_field (*valp, index, pos); + if (cep == nullptr) + return void_node; index_pos_hints.safe_push (cep - CONSTRUCTOR_ELTS (*valp)->begin()); if (code == UNION_TYPE) - activated_union_member_p = true; + { + activated_union_member_p = true; + --seen_union; + } valp = &cep->value; type = reftype; @@ -8279,7 +8320,8 @@ cxx_eval_statement_list (const constexpr_ctx *ctx, tree t, value_cat lval = vc_discard; /* The result of a statement-expression is not wrapped in EXPR_STMT. */ - if (tsi_one_before_end_p (i) && TREE_CODE (stmt) != EXPR_STMT) + if (tsi_one_before_end_p (i) + && !VOID_TYPE_P (TREE_TYPE (stmt))) lval = vc_prvalue; r = cxx_eval_constant_expression (ctx, stmt, lval, @@ -8383,7 +8425,7 @@ cxx_eval_loop_expr (const constexpr_ctx *ctx, tree t, *jump_target = NULL_TREE; if (expr) - cxx_eval_constant_expression (ctx, expr, vc_prvalue, + cxx_eval_constant_expression (ctx, expr, vc_discard, non_constant_p, overflow_p, jump_target); cleanup_cond (); @@ -9664,6 +9706,14 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t, if (TREE_CONSTANT (t)) return t; } + if (TREE_CLOBBER_P (t)) + { + /* Assignment from a clobber is handled in cxx_eval_store_expression; + a clobber by itself isn't a constant-expression. */ + gcc_assert (ctx->quiet); + *non_constant_p = true; + break; + } r = cxx_eval_bare_aggregate (ctx, t, lval, non_constant_p, overflow_p, jump_target); break; @@ -10226,6 +10276,19 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t, if (r == error_mark_node) *non_constant_p = true; + if (r == void_node && lval != vc_discard && !*jump_target + && !VOID_TYPE_P (TREE_TYPE (t))) + { + /* For diagnostic quality we should have handled this sooner, where we + can be more specific about the out-of-lifetime object. But here we + can still be correct. */ + gcc_checking_assert (false); + if (!ctx->quiet) + error_at (EXPR_LOCATION (t), + "%qE accesses an object outside its lifetime", t); + *non_constant_p = true; + } + if (*non_constant_p) return t; else @@ -11632,6 +11695,7 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, && (now || !var_in_maybe_constexpr_fn (t)) && !type_dependent_expression_p (t) && !decl_maybe_constant_var_p (t) + && !is_local_temp (t) && (strict || !CP_TYPE_CONST_NON_VOLATILE_P (TREE_TYPE (t)) || (DECL_INITIAL (t) diff --git a/gcc/cp/init.cc b/gcc/cp/init.cc index 8db84eb5e38..c950c363f59 100644 --- a/gcc/cp/init.cc +++ b/gcc/cp/init.cc @@ -3563,7 +3563,10 @@ build_new_1 (vec **placement, tree type, tree nelts, in start_preparsed_function. This is most important for activating an array in a union (c++/121068), but should also help the optimizers. */ const bool do_clobber - = (std_placement && !*init && flag_lifetime_dse > 1 + = (std_placement && flag_lifetime_dse > 1 + && !processing_template_decl + && !is_empty_type (elt_type) + && !*init && (!CLASS_TYPE_P (elt_type) || type_has_non_user_provided_default_constructor (elt_type))); @@ -4923,7 +4926,8 @@ build_vec_init (tree base, tree maxindex, tree init, } /* Any elements without explicit initializers get T{}. */ - empty_list = true; + if (!TREE_CLOBBER_P (init)) + empty_list = true; } else if (init && TREE_CODE (init) == STRING_CST) { @@ -5062,7 +5066,8 @@ build_vec_init (tree base, tree maxindex, tree init, } else if (TREE_CODE (type) == ARRAY_TYPE) { - if (init && !BRACE_ENCLOSED_INITIALIZER_P (init)) + if (init && !BRACE_ENCLOSED_INITIALIZER_P (init) + && !TREE_CLOBBER_P (init)) { if ((complain & tf_error)) error_at (loc, "array must be initialized " diff --git a/gcc/testsuite/g++.dg/cpp26/constexpr-new6.C b/gcc/testsuite/g++.dg/cpp26/constexpr-new6.C new file mode 100644 index 00000000000..b27c80d71c0 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/constexpr-new6.C @@ -0,0 +1,17 @@ +// { dg-do compile { target c++26 } } + +#include + +union U { double d; int i; }; + +constexpr int f() +{ + U u; + new (&u.i) int; + return u.i; // { dg-error "uninitialized" } +} + +int main () +{ + constexpr int i = f(); // { dg-message "" } +} diff --git a/gcc/testsuite/g++.dg/init/pr25811.C b/gcc/testsuite/g++.dg/init/pr25811.C index 4cda484e5af..853eeae3feb 100644 --- a/gcc/testsuite/g++.dg/init/pr25811.C +++ b/gcc/testsuite/g++.dg/init/pr25811.C @@ -187,7 +187,7 @@ void f11 () void f12 () { - new A3[1]; // { dg-error "deleted|uninitialized reference member" } + new A3[1]; // { dg-error "deleted|uninitialized reference" } } void f13 () diff --git a/gcc/testsuite/g++.dg/warn/Warray-bounds-12.C b/gcc/testsuite/g++.dg/warn/Warray-bounds-12.C index 07fa351a86c..3a0b6093c82 100644 --- a/gcc/testsuite/g++.dg/warn/Warray-bounds-12.C +++ b/gcc/testsuite/g++.dg/warn/Warray-bounds-12.C @@ -19,7 +19,7 @@ void sink (void*); void warn_new () { - T (int32_t, 0, 0); // { dg-warning "array subscript 0 is outside array bounds of 'int32_t \\\[0]'" } + T (int32_t, 0, 0); // { dg-warning "array subscript 0 is outside array bounds" } // { dg-message "object of size \\d allocated by '\[^\n\r]*operator new\[^\n\r]*'" "note" { target *-*-* } .-1 } T (int32_t, 1, 0); // { dg-warning "array subscript 'int32_t {aka (long )?int}\\\[0]' is partly outside array bounds of 'unsigned char \\\[1]'" } T (int32_t, 2, 0); // { dg-warning "array subscript 'int32_t {aka (long )?int}\\\[0]' is partly outside array bounds of 'unsigned char \\\[2]'" } @@ -45,7 +45,7 @@ void warn_array_new () #undef NEW #define NEW(n) new char [n] - T (int32_t, 0, 0); // { dg-warning "array subscript 0 is outside array bounds of 'int32_t \\\[0]'" } + T (int32_t, 0, 0); // { dg-warning "array subscript 0 is outside array bounds" } // { dg-message "object of size \\d allocated by '\[^\n\r]*operator new\[^\n\r]*'" "note" { target *-*-* } .-1 } T (int32_t, 1, 0); // { dg-warning "array subscript 'int32_t {aka (long )?int}\\\[0]' is partly outside array bounds of 'unsigned char \\\[1]'" } T (int32_t, 2, 0); // { dg-warning "array subscript 'int32_t {aka (long )?int}\\\[0]' is partly outside array bounds of 'unsigned char \\\[2]'" } @@ -53,7 +53,7 @@ void warn_array_new () T (int32_t, 4, 0); - T (int32_t, 0, 1); // { dg-warning "array subscript 1 is outside array bounds of 'int32_t \\\[0]'" } + T (int32_t, 0, 1); // { dg-warning "array subscript 1 is outside array bounds" } T (int32_t, 1, 1); // { dg-warning "array subscript 1 is outside array bounds " } T (int32_t, 2, 1); // { dg-warning "array subscript 1 is outside array bounds " } T (int32_t, 3, 1); // { dg-warning "array subscript 1 is outside array bounds " } diff --git a/gcc/testsuite/g++.dg/warn/Warray-bounds-13.C b/gcc/testsuite/g++.dg/warn/Warray-bounds-13.C index 449324a315d..68b78e3fd9e 100644 --- a/gcc/testsuite/g++.dg/warn/Warray-bounds-13.C +++ b/gcc/testsuite/g++.dg/warn/Warray-bounds-13.C @@ -66,7 +66,7 @@ void warn_nothrow_array_new () #undef NEW #define NEW(n) new (std::nothrow) char [n] - T (int32_t, 0, 0); // { dg-warning "array subscript 0 is outside array bounds of 'int32_t \\\[0]'" } + T (int32_t, 0, 0); // { dg-warning "array subscript 0 is outside array bounds" } // { dg-message "object of size \\d allocated by '\[^\n\r]*operator new\[^\n\r]*'" "note" { target *-*-* } .-1 } T (int32_t, 1, 0); // { dg-warning "array subscript 'int32_t {aka (long )?int}\\\[0]' is partly outside array bounds of 'unsigned char \\\[1]'" } T (int32_t, 2, 0); // { dg-warning "array subscript 'int32_t {aka (long )?int}\\\[0]' is partly outside array bounds of 'unsigned char \\\[2]'" } @@ -74,7 +74,7 @@ void warn_nothrow_array_new () T (int32_t, 4, 0); - T (int32_t, 0, 1); // { dg-warning "array subscript 1 is outside array bounds of 'int32_t \\\[0]'" } + T (int32_t, 0, 1); // { dg-warning "array subscript 1 is outside array bounds " } T (int32_t, 1, 1); // { dg-warning "array subscript 1 is outside array bounds " } T (int32_t, 2, 1); // { dg-warning "array subscript 1 is outside array bounds " } T (int32_t, 3, 1); // { dg-warning "array subscript 1 is outside array bounds " }