From: Jason Merrill Date: Tue, 5 Aug 2025 22:16:50 +0000 (-0700) Subject: c++: clobber object on placement new [PR121068] X-Git-Url: http://git.ipfire.org/gitweb/gitweb.cgi?a=commitdiff_plain;h=bc42128330c0ea70a015b74b655cb8c48b6a8c06;p=thirdparty%2Fgcc.git c++: clobber object on placement new [PR121068] My r16-2432 patch addressed the original testcase involving an array of scalars, but not this additional testcase involving an array of classes. This patch addresses the issue more thoroughly, by having placement new first clobber the new object, and improving cxx_eval_store_expression to implement initial clobbers as well. My earlier attempt to do this clobbered the array as a whole, which broke construct_at after the resolution of LWG3436 due to trying to create a multidimensional array over the top of a single-dimensional array. To side-step that issue, this patch instead clobbers the individual elements of an array, taking advantage of the earlier change to let that activate the array member of a union. PR c++/121068 gcc/cp/ChangeLog: * constexpr.cc (cxx_eval_store_expression): Handle clobbers. (potential_constant_expression_1): Handle clobbers more. * decl.cc (build_clobber_this): Use INIT_EXPR for initial clobber. * init.cc (build_new_1): Clobber on placement new. (build_vec_init): Don't clean up after clobber. gcc/testsuite/ChangeLog: * g++.dg/cpp26/constexpr-new5.C: New test. --- diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc index 142579a9102..65b91ec4152 100644 --- a/gcc/cp/constexpr.cc +++ b/gcc/cp/constexpr.cc @@ -7452,12 +7452,6 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, tree init = TREE_OPERAND (t, 1); - if (TREE_CLOBBER_P (init) - && CLOBBER_KIND (init) < CLOBBER_OBJECT_END) - /* Only handle clobbers ending the lifetime of objects. - ??? We should probably set CONSTRUCTOR_NO_CLEARING. */ - return void_node; - /* First we figure out where we're storing to. */ tree target = TREE_OPERAND (t, 0); @@ -7644,11 +7638,14 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, } /* Handle explicit end-of-lifetime. */ - if (TREE_CLOBBER_P (init)) + if (TREE_CLOBBER_P (init) + && CLOBBER_KIND (init) >= CLOBBER_OBJECT_END) { if (refs->is_empty ()) - ctx->global->destroy_value (object); - return void_node; + { + ctx->global->destroy_value (object); + return void_node; + } } type = TREE_TYPE (object); @@ -7785,6 +7782,8 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, *non_constant_p = true; } else if (!is_access_expr + || (TREE_CLOBBER_P (init) + && CLOBBER_KIND (init) >= CLOBBER_OBJECT_END) || (TREE_CODE (t) == MODIFY_EXPR && CLASS_TYPE_P (inner) && !type_has_non_deleted_trivial_default_ctor (inner))) @@ -7848,11 +7847,17 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, type = reftype; } + /* Change an "as-base" clobber to the real type; + we don't need to worry about padding in constexpr. */ + tree itype = initialized_type (init); + if (IS_FAKE_BASE_TYPE (itype)) + itype = TYPE_CONTEXT (itype); + /* For initialization of an empty base, the original target will be *(base*)this, evaluation of which resolves to the object argument, which has the derived type rather than the base type. */ if (!empty_base && !(same_type_ignoring_top_level_qualifiers_p - (initialized_type (init), type))) + (itype, type))) { gcc_assert (is_empty_class (TREE_TYPE (target))); empty_base = true; @@ -7959,8 +7964,10 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, /* Don't share a CONSTRUCTOR that might be changed later. */ init = unshare_constructor (init); - gcc_checking_assert (!*valp || (same_type_ignoring_top_level_qualifiers_p - (TREE_TYPE (*valp), type))); + gcc_checking_assert (!*valp + || *valp == void_node + || (same_type_ignoring_top_level_qualifiers_p + (TREE_TYPE (*valp), type))); if (empty_base) { /* Just evaluate the initializer and return, since there's no actual data @@ -7973,6 +7980,22 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, CONSTRUCTOR_ZERO_PADDING_BITS (*valp) = zero_padding_bits; } } + else if (TREE_CLOBBER_P (init)) + { + if (AGGREGATE_TYPE_P (type)) + { + if (*valp) + CONSTRUCTOR_ELTS (*valp) = nullptr; + else + *valp = build_constructor (type, nullptr); + TREE_CONSTANT (*valp) = true; + TREE_SIDE_EFFECTS (*valp) = false; + CONSTRUCTOR_NO_CLEARING (*valp) = true; + CONSTRUCTOR_ZERO_PADDING_BITS (*valp) = zero_padding_bits; + } + else + *valp = void_node; + } else if (*valp && TREE_CODE (*valp) == CONSTRUCTOR && TREE_CODE (init) == CONSTRUCTOR) { @@ -7997,6 +8020,9 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, && TREE_CODE (*valp) == CONSTRUCTOR && TYPE_READONLY (type)) { + tree target_type = TREE_TYPE (target); + if (IS_FAKE_BASE_TYPE (target_type)) + target_type = TYPE_CONTEXT (target_type); if (INDIRECT_REF_P (target) && (is_this_parameter (tree_strip_nop_conversions (TREE_OPERAND (target, 0))))) @@ -8004,7 +8030,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, constructor of a delegating constructor). Leave it up to the caller that set 'this' to set TREE_READONLY appropriately. */ gcc_checking_assert (same_type_ignoring_top_level_qualifiers_p - (TREE_TYPE (target), type) || empty_base); + (target_type, type) || empty_base); else TREE_READONLY (*valp) = true; } @@ -11308,6 +11334,13 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, && !FUNC_OR_METHOD_TYPE_P (TREE_TYPE (t)) && !NULLPTR_TYPE_P (TREE_TYPE (t))) { + if (TREE_CLOBBER_P (t)) + { + /* We should have caught any clobbers in INIT/MODIFY_EXPR. */ + gcc_checking_assert (false); + return true; + } + if (flags & tf_error) constexpr_error (loc, fundef_p, "lvalue-to-rvalue conversion of " "a volatile lvalue %qE with type %qT", t, @@ -12131,6 +12164,8 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, } /* FALLTHRU */ case INIT_EXPR: + if (TREE_CLOBBER_P (TREE_OPERAND (t, 1))) + return true; return RECUR (TREE_OPERAND (t, 1), rval); case CONSTRUCTOR: diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc index cb3ebfff429..8122fca0af1 100644 --- a/gcc/cp/decl.cc +++ b/gcc/cp/decl.cc @@ -18440,6 +18440,8 @@ build_clobber_this (clobber_kind kind) } tree exprstmt = build2 (MODIFY_EXPR, void_type_node, thisref, clobber); + if (kind == CLOBBER_OBJECT_BEGIN) + TREE_SET_CODE (exprstmt, INIT_EXPR); if (vbases) exprstmt = build_if_in_charge (exprstmt); diff --git a/gcc/cp/init.cc b/gcc/cp/init.cc index 09fb4f36552..f19794caadc 100644 --- a/gcc/cp/init.cc +++ b/gcc/cp/init.cc @@ -3557,9 +3557,19 @@ build_new_1 (vec **placement, tree type, tree nelts, alloc_expr = maybe_wrap_new_for_constexpr (alloc_expr, type, cookie_size); + bool std_placement = std_placement_new_fn_p (alloc_fn); + + /* For std placement new, clobber the object if the constructor won't do it + 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 + && (!CLASS_TYPE_P (elt_type) + || type_has_non_user_provided_default_constructor (elt_type))); + /* In the simple case, we can stop now. */ pointer_type = build_pointer_type (type); - if (!cookie_size && !is_initialized && !member_delete_p) + if (!cookie_size && !is_initialized && !member_delete_p && !do_clobber) return build_nop (pointer_type, alloc_expr); /* Store the result of the allocation call in a variable so that we can @@ -3593,8 +3603,7 @@ build_new_1 (vec **placement, tree type, tree nelts, So check for a null exception spec on the op new we just called. */ nothrow = TYPE_NOTHROW_P (TREE_TYPE (alloc_fn)); - check_new - = flag_check_new || (nothrow && !std_placement_new_fn_p (alloc_fn)); + check_new = flag_check_new || (nothrow && !std_placement); if (cookie_size) { @@ -3649,6 +3658,29 @@ build_new_1 (vec **placement, tree type, tree nelts, /* Any further uses of alloc_node will want this type, too. */ alloc_node = fold_convert (non_const_pointer_type, alloc_node); + tree clobber_expr = NULL_TREE; + if (do_clobber) + { + tree clobber = build_clobber (elt_type, CLOBBER_OBJECT_BEGIN); + CONSTRUCTOR_IS_DIRECT_INIT (clobber) = true; + if (array_p) + { + /* Clobber each element rather than the array at once. */ + tree maxindex = cp_build_binary_op (input_location, + MINUS_EXPR, outer_nelts, + integer_one_node, + complain); + clobber_expr = build_vec_init (data_addr, maxindex, clobber, + /*valinit*/false, /*from_arr*/0, + complain, nullptr); + } + else + { + tree targ = cp_build_fold_indirect_ref (data_addr); + clobber_expr = cp_build_init_expr (targ, clobber); + } + } + /* Now initialize the allocated object. Note that we preevaluate the initialization expression, apart from the actual constructor call or assignment--we do this because we want to delay the allocation as long @@ -3877,6 +3909,8 @@ build_new_1 (vec **placement, tree type, tree nelts, if (init_expr) rval = build2 (COMPOUND_EXPR, TREE_TYPE (rval), init_expr, rval); + if (clobber_expr) + rval = build2 (COMPOUND_EXPR, TREE_TYPE (rval), clobber_expr, rval); if (cookie_expr) rval = build2 (COMPOUND_EXPR, TREE_TYPE (rval), cookie_expr, rval); @@ -4717,6 +4751,9 @@ build_vec_init (tree base, tree maxindex, tree init, the partially constructed array if an exception is thrown. But don't do this if we're assigning. */ if (flag_exceptions && TYPE_HAS_NONTRIVIAL_DESTRUCTOR (type) + /* And don't clean up from clobbers, the actual initialization will + follow as a separate build_vec_init. */ + && !(init && TREE_CLOBBER_P (init)) && from_array != 2) { tree e; diff --git a/gcc/testsuite/g++.dg/cpp26/constexpr-new5.C b/gcc/testsuite/g++.dg/cpp26/constexpr-new5.C new file mode 100644 index 00000000000..b98b9e98859 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/constexpr-new5.C @@ -0,0 +1,43 @@ +// PR c++/121068 +// { dg-do compile { target c++26 } } + +#include + +struct S +{ + constexpr S() = default; + constexpr S(int x) : s(x) {} + constexpr S(S&& x) : s(x.s) {} + constexpr S& operator=(S&& x) { s = x.s; return *this; } + unsigned char s; +}; + +constexpr +int foo() +{ + union { S a[20]; }; + new (&a) S[20](); // OK + for (int i = 0; i < 20; ++i) + a[i].~S(); + + auto* sf = ::new(&a[2]) S(11); + return 1; +} + +static_assert(foo()); + +constexpr +int foo2() +{ + union { S a[20]; }; + new (&a) S[20]; // ILL-FORMED + for (int i = 0; i < 20; ++i) + a[i].~S(); + + auto* sf = ::new(&a[2]) S(11); + return 1; +} + +static_assert(foo2()); + +auto p = foo2;