[gcc r16-3961] c++: improve constexpr clobber handling

Jason Merrill jason@gcc.gnu.org
Thu Sep 18 21:46:36 GMT 2025


https://gcc.gnu.org/g:b8af1b3a00249eceacf53444cdb339761eeb9f02

commit r16-3961-gb8af1b3a00249eceacf53444cdb339761eeb9f02
Author: Jason Merrill <jason@redhat.com>
Date:   Thu Sep 18 13:10:55 2025 +0200

    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:
---
 gcc/cp/constexpr.cc                          | 84 ++++++++++++++++++++++++----
 gcc/cp/init.cc                               | 11 +++-
 gcc/testsuite/g++.dg/cpp26/constexpr-new6.C  | 17 ++++++
 gcc/testsuite/g++.dg/init/pr25811.C          |  2 +-
 gcc/testsuite/g++.dg/warn/Warray-bounds-12.C |  6 +-
 gcc/testsuite/g++.dg/warn/Warray-bounds-13.C |  4 +-
 6 files changed, 105 insertions(+), 19 deletions(-)

diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
index db363dc27607..1621e28da5c8 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 8db84eb5e38c..c950c363f59c 100644
--- a/gcc/cp/init.cc
+++ b/gcc/cp/init.cc
@@ -3563,7 +3563,10 @@ build_new_1 (vec<tree, va_gc> **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 000000000000..b27c80d71c05
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp26/constexpr-new6.C
@@ -0,0 +1,17 @@
+// { dg-do compile { target c++26 } }
+
+#include <new>
+
+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 4cda484e5af5..853eeae3feb6 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 07fa351a86ce..3a0b6093c82b 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 449324a315d0..68b78e3fd9ea 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 " }


More information about the Gcc-cvs mailing list