]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c++: clobber object on placement new [PR121068]
authorJason Merrill <jason@redhat.com>
Tue, 5 Aug 2025 22:16:50 +0000 (15:16 -0700)
committerJason Merrill <jason@redhat.com>
Tue, 5 Aug 2025 22:16:50 +0000 (15:16 -0700)
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.

gcc/cp/constexpr.cc
gcc/cp/decl.cc
gcc/cp/init.cc
gcc/testsuite/g++.dg/cpp26/constexpr-new5.C [new file with mode: 0644]

index 142579a91029bcc86744b60c9c55ab219dcf9250..65b91ec41529f2d3098b8b92095391bb3c2ef83a 100644 (file)
@@ -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:
index cb3ebfff429820c675731623cb01d95b005152bb..8122fca0af1ba67550a9fbb5c7ce29de850a7dd2 100644 (file)
@@ -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);
 
index 09fb4f365522e88c789f169e2e7f3d7d727ce552..f19794caadcb6812dc6b18f86c17ca7d6c586d25 100644 (file)
@@ -3557,9 +3557,19 @@ build_new_1 (vec<tree, va_gc> **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<tree, va_gc> **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<tree, va_gc> **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<tree, va_gc> **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 (file)
index 0000000..b98b9e9
--- /dev/null
@@ -0,0 +1,43 @@
+// PR c++/121068
+// { dg-do compile { target c++26 } }
+
+#include <new>
+
+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;