]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c++: improve constexpr clobber handling
authorJason Merrill <jason@redhat.com>
Thu, 18 Sep 2025 11:10:55 +0000 (13:10 +0200)
committerJason Merrill <jason@redhat.com>
Thu, 18 Sep 2025 21:45:53 +0000 (23:45 +0200)
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.

gcc/cp/constexpr.cc
gcc/cp/init.cc
gcc/testsuite/g++.dg/cpp26/constexpr-new6.C [new file with mode: 0644]
gcc/testsuite/g++.dg/init/pr25811.C
gcc/testsuite/g++.dg/warn/Warray-bounds-12.C
gcc/testsuite/g++.dg/warn/Warray-bounds-13.C

index db363dc2760700fa61f55bc404cd3e52b024bafb..1621e28da5c867931ad8e14ca95f42e06e1516b4 100644 (file)
@@ -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)
index 8db84eb5e38cfb0ad95b5cd6af82852215342423..c950c363f59c327b3f2ffb5e61253718d340df25 100644 (file)
@@ -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 (file)
index 0000000..b27c80d
--- /dev/null
@@ -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 "" }
+}
index 4cda484e5af521fffbfad3415a00ef1c2db46cf4..853eeae3feb65f67334eb7f281312970b35a70f4 100644 (file)
@@ -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 ()
index 07fa351a86ceb265616ebe5a9c83d9507e55ce86..3a0b6093c82b59ce99dafce224df588e13f42aaf 100644 (file)
@@ -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 " }
index 449324a315d0f91cdbeda1effea2526744d9a03c..68b78e3fd9eaaa9c087335dc15b2080ca66e2b4d 100644 (file)
@@ -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 " }