On 10/12/23 04:53, Nathaniel Shead wrote:
On Wed, Oct 11, 2023 at 12:48:12AM +1100, Nathaniel Shead wrote:
On Mon, Oct 09, 2023 at 04:46:46PM -0400, Jason Merrill wrote:
On 10/8/23 21:03, Nathaniel Shead wrote:
Ping for https://gcc.gnu.org/pipermail/gcc-patches/2023-September/631203.html

+         && (TREE_CODE (t) == MODIFY_EXPR
+             /* Also check if initializations have implicit change of active
+                member earlier up the access chain.  */
+             || !refs->is_empty())

I'm not sure what the cumulative point of these two tests is.  TREE_CODE (t)
will be either MODIFY_EXPR or INIT_EXPR, and either should be OK.

As I understand it, the problematic case is something like
constexpr-union2.C, where we're also looking at a MODIFY_EXPR.  So what is
this check doing?

The reasoning was to correctly handle cases like the the following (in
constexpr-union6.C):

   constexpr int test1() {
     U u {};
     std::construct_at(&u.s, S{ 1, 2 });
     return u.s.b;
   }
   static_assert(test1() == 2);

The initialisation of &u.s here is not a member access expression within
the call to std::construct_at, since it's just a pointer, but this code
is still legal; in general, an INIT_EXPR to initialise a union member
should always be OK (I believe?), hence constraining to just
MODIFY_EXPR.

However, just that would then (incorrectly) allow all the following
cases in that test to compile, such as

   constexpr int test2() {
     U u {};
     int* p = &u.s.b;
     std::construct_at(p, 5);
     return u.s.b;
   }
   constexpr int x2 = test2();

since the INIT_EXPR is really only initialising 'b', but the implicit
"modification" of active member to 'u.s' is illegal.

Maybe a better way of expressing this condition would be something like
this?

   /* An INIT_EXPR of the last member in an access chain is always OK,
      but still check implicit change of members earlier on; see
      cpp2a/constexpr-union6.C.  */
   && !(TREE_CODE (t) == INIT_EXPR && refs->is_empty ())

Otherwise I'll see if I can rework some of the other conditions instead.

Incidentally, I think constexpr-union6.C could use a test where we pass &u.s
to a function other than construct_at, and then try (and fail) to assign to
the b member from that function.

Jason


Sounds good; I've added the following test:

   constexpr void foo(S* s) {
     s->b = 10;  // { dg-error "accessing .U::s. member instead of initialized 
.U::k." }
   }
   constexpr int test3() {
     U u {};
     foo(&u.s);  // { dg-message "in .constexpr. expansion" }
     return u.s.b;
   }
   constexpr int x3 = test3();  // { dg-message "in .constexpr. expansion" }

Incidentally I found this particular example caused a very unhelpful
error + ICE due to reporting that S could not be value-initialized in
the current version of the patch. The updated version below fixes that
by using 'build_zero_init' instead -- is this an appropriate choice
here?

A similar (but unrelated) issue is with e.g.
struct S { const int a; int b; };
   union U { int k; S s; };

   constexpr int test() {
     U u {};
     return u.s.b;
   }
   constexpr int x = test();

giving me this pretty unhelpful error message:

/home/ns/main.cpp:8:23:   in ‘constexpr’ expansion of ‘test()’
/home/ns/main.cpp:6:12: error: use of deleted function ‘S::S()’
     6 |   return u.s.b;
       |          ~~^
/home/ns/main.cpp:1:8: note: ‘S::S()’ is implicitly deleted because the default 
definition would be ill-formed:
     1 | struct S { const int a; int b; };
       |        ^
/home/ns/main.cpp:1:8: error: uninitialised const member in ‘struct S’
/home/ns/main.cpp:1:22: note: ‘const int S::a’ should be initialised
     1 | struct S { const int a; int b; };
       |                      ^
/home/ns/main.cpp:8:23:   in ‘constexpr’ expansion of ‘test()’
/home/ns/main.cpp:6:12: error: use of deleted function ‘S::S()’
     6 |   return u.s.b;
       |          ~~^
/home/ns/main.cpp:8:23:   in ‘constexpr’ expansion of ‘test()’
/home/ns/main.cpp:6:12: error: use of deleted function ‘S::S()’

but I'll try and fix this separately (it exists on current trunk without
this patch as well).

While attempting to fix this I found a much better way of handling
value-initialised unions. Here's a new version of the patch which also
includes the fix for accessing the wrong member of a value-initialised
union as well.

Additionally includes an `auto_diagnostic_group` for the `inform`
diagnostics as Marek helpfully informed me about in my other patch.

Bootstrapped and regtested on x86_64-pc-linux-gnu.

@@ -4496,21 +4491,36 @@ cxx_eval_component_reference (const constexpr_ctx *ctx, 
tree t,
            break;
        }
      }
-  if (TREE_CODE (TREE_TYPE (whole)) == UNION_TYPE
-      && CONSTRUCTOR_NELTS (whole) > 0)
+  if (TREE_CODE (TREE_TYPE (whole)) == UNION_TYPE)
      {
-      /* DR 1188 says we don't have to deal with this.  */
-      if (!ctx->quiet)
+      if (CONSTRUCTOR_NELTS (whole) > 0)
        {
-         constructor_elt *cep = CONSTRUCTOR_ELT (whole, 0);
-         if (cep->value == NULL_TREE)
-           error ("accessing uninitialized member %qD", part);
-         else
-           error ("accessing %qD member instead of initialized %qD member in "
-                  "constant expression", part, cep->index);
+         /* DR 1188 says we don't have to deal with this.  */
+         if (!ctx->quiet)
+           {
+             constructor_elt *cep = CONSTRUCTOR_ELT (whole, 0);
+             if (cep->value == NULL_TREE)
+               error ("accessing uninitialized member %qD", part);
+             else
+               error ("accessing %qD member instead of initialized %qD member in 
"
+                      "constant expression", part, cep->index);
+           }
+         *non_constant_p = true;
+         return t;
+       }
+      else if (!CONSTRUCTOR_NO_CLEARING (whole))
+       {
+         /* Value-initialized union, check if looking at the first member.  */
+         tree first = next_aggregate_field (TYPE_FIELDS (TREE_TYPE (whole)));
+         if (pmf ? DECL_NAME (first) != DECL_NAME (part) : first != part)

You don't need to consider pmf here, since a PMF isn't UNION_TYPE.

@@ -6267,23 +6288,74 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, 
tree t,
          break;
        }
+ /* Process value-initialization of a union. */
+      if (code == UNION_TYPE
+         && !CONSTRUCTOR_NO_CLEARING (*valp)
+         && CONSTRUCTOR_NELTS (*valp) == 0)
+       if (tree first = next_aggregate_field (TYPE_FIELDS (type)))
+         CONSTRUCTOR_APPEND_ELT (CONSTRUCTOR_ELTS (*valp), first, NULL_TREE);

Isn't this adding an uninitialized member instead of value-initialized?

Jason

Reply via email to