On Fri, 20 Mar 2020, Patrick Palka wrote: > On Fri, 20 Mar 2020, Jason Merrill wrote: > > > On 3/20/20 9:49 AM, Patrick Palka wrote: > > > On Thu, 19 Mar 2020, Jason Merrill wrote: > > > > > > > On 3/19/20 2:06 PM, Patrick Palka via Gcc-patches wrote: > > > > > On Thu, 19 Mar 2020, Marek Polacek wrote: > > > > > > > > > > > On Thu, Mar 19, 2020 at 01:06:35PM -0400, Patrick Palka via > > > > > > Gcc-patches > > > > > > wrote: > > > > > > > On Thu, 19 Mar 2020, Patrick Palka wrote: > > > > > > > > > > > > > > > This patch adds a check to detect changing the active union > > > > > > > > member > > > > > > > > during > > > > > > > > initialization of the union. It uses the > > > > > > > > CONSTRUCTOR_NO_CLEARING > > > > > > > > flag > > > > > > > > as a > > > > > > > > proxy for whether the non-empty CONSTRUCTOR of UNION_TYPE we're > > > > > > > > assigning to in > > > > > > > > cxx_eval_store_expression is in the process of being > > > > > > > > initialized, > > > > > > > > which seems to > > > > > > > > work well. > > > > > > > > > > > > > > If we can't rely on CONSTRUCTOR_NO_CLEARING to be set iff a > > > > > > > CONSTRUCTOR > > > > > > > is in the process of being initialized, then here's an alternative > > > > > > > patch > > > > > > > for consideration, that detects this UB in an indirect way and > > > > > > > after > > > > > > > the > > > > > > > fact. > > > > > > > > > > > > Yeah, I'm not sure if that would work well, especially in C++20 > > > > > > where > > > > > > we > > > > > > sometimes don't clear it: > > > > > > > > > > > > /* The result of a constexpr function must be completely > > > > > > initialized. > > > > > > > > > > > > However, in C++20, a constexpr constructor doesn't > > > > > > necessarily > > > > > > have > > > > > > to initialize all the fields, so we don't clear > > > > > > CONSTRUCTOR_NO_CLEARING > > > > > > in order to detect reading an unitialized object in constexpr > > > > > > instead > > > > > > of value-initializing it. (reduced_constant_expression_p is > > > > > > expected to > > > > > > take care of clearing the flag.) */ > > > > > > if (TREE_CODE (result) == CONSTRUCTOR > > > > > > && (cxx_dialect < cxx2a > > > > > > || !DECL_CONSTRUCTOR_P (fun))) > > > > > > clear_no_implicit_zero (result); > > > > > > > > > > > > and rely on reduced_constant_expression_p to clear it. > > > > > > > > > > I see, thanks. Here's a reproducer for the issue you pointed out, > > > > > which > > > > > is a valid testcase but gets rejected with the proposed patch: > > > > > > > > > > union U > > > > > { > > > > > int x; > > > > > char y; > > > > > }; > > > > > > > > > > constexpr bool > > > > > baz () > > > > > { > > > > > U u; > > > > > u.x = 3; > > > > > u.y = 7; > > > > > return (u.y == 7); > > > > > } > > > > > > > > > > static_assert (baz ()); > > > > > > > > > > CONSTRUCTOR_NO_CLEARING is set for 'u' and is not cleared after its > > > > > constructor returns, and so the check yields a false positive for the > > > > > assignment to u.y. That's unfortunate... > > > > > > > > We should probably clear the flag when we assign to u.x because once we > > > > give a > > > > value to one union member, the union has a value. > > > > > > That works well. Further testing revealed a couple of more issues > > > caused by the new hunk > > > > > > @@ -3751,6 +3751,11 @@ cxx_eval_bare_aggregate (const constexpr_ctx *ctx, > > > tree t, > > > /* If we built a new CONSTRUCTOR, attach it now so that other > > > initializers can refer to it. */ > > > CONSTRUCTOR_APPEND_ELT (*p, index, new_ctx.ctor); > > > + else if (TREE_CODE (type) == UNION_TYPE) > > > + /* If we're constructing a union, set the active union member now so > > > + that we can later detect if the initializer attempts to activate > > > + another member. */ > > > + CONSTRUCTOR_APPEND_ELT (*p, index, NULL_TREE); > > > tree elt = cxx_eval_constant_expression (&new_ctx, value, > > > lval, > > > non_constant_p, > > > overflow_p); > > > > > > where other routines aren't prepared to handle a NULL constructor union > > > element. They are cxx_eval_component_reference (fixed by emitting a > > > different error message given a NULL constructor elt) and > > > cxx_eval_bare_aggregate itself (fixed by not re-reducing a CONSTRUCTOR > > > returned by lookup_placeholder). > > > > > > As a drive-by this patch adds a check in reduced_constant_expression_p > > > to correctly return false for an empty CONSTRUCTOR of UNION_TYPE. This > > > allows us to correctly diagnose the uninitialized use in > > > constexpr-union4.C where we weren't before. > > > > > > -- >8 -- > > > > > > gcc/cp/ChangeLog: > > > > > > PR c++/94066 > > > * constexpr.c (reduced_constant_expression_p) [CONSTRUCTOR]: Properly > > > handle unions without an initializer. > > > (cxx_eval_component_reference): Emit a different diagnostic when the > > > constructor element corresponding to a union member is NULL. > > > (cxx_eval_bare_aggregate): When constructing a union, always set the > > > active union member before evaluating the initializer. Relax > > > assertion > > > that verifies the index of the constructor element we're initializing > > > hasn't been changed. > > > (cxx_eval_store_expression): Diagnose changing the active union member > > > while the union is in the process of being initialized. After setting > > > an active union member, clear CONSTRUCTOR_NO_CLEARING on the > > > underlying > > > CONSTRUCTOR. > > > (cxx_eval_constant_expression) [PLACEHOLDER_EXPR]: Don't re-reduce a > > > CONSTRUCTOR returned by lookup_placeholder. > > > > > > gcc/testsuite/ChangeLog: > > > > > > PR c++/94066 > > > * g++.dg/cpp1y/constexpr-union2.C: New test. > > > * g++.dg/cpp1y/constexpr-union3.C: New test. > > > * g++.dg/cpp1y/constexpr-union4.C: New test. > > > * g++.dg/cpp1y/constexpr-union5.C: New test. > > > * g++.dg/cpp1y/pr94066.C: New test. > > > * g++.dg/cpp1y/pr94066-2.C: New test. > > > * g++.dg/cpp1y/pr94066-3.C: New test. > > > * g++.dg/cpp2a/constexpr-union1.C: New test. > > > --- > > > gcc/cp/constexpr.c | 69 ++++++++++++++++--- > > > gcc/testsuite/g++.dg/cpp1y/constexpr-union2.C | 9 +++ > > > gcc/testsuite/g++.dg/cpp1y/constexpr-union3.C | 9 +++ > > > gcc/testsuite/g++.dg/cpp1y/constexpr-union4.C | 9 +++ > > > gcc/testsuite/g++.dg/cpp1y/constexpr-union5.C | 15 ++++ > > > gcc/testsuite/g++.dg/cpp1y/pr94066-2.C | 19 +++++ > > > gcc/testsuite/g++.dg/cpp1y/pr94066-3.C | 16 +++++ > > > gcc/testsuite/g++.dg/cpp1y/pr94066.C | 18 +++++ > > > gcc/testsuite/g++.dg/cpp2a/constexpr-union1.C | 18 +++++ > > > 9 files changed, 173 insertions(+), 9 deletions(-) > > > create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-union2.C > > > create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-union3.C > > > create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-union4.C > > > create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-union5.C > > > create mode 100644 gcc/testsuite/g++.dg/cpp1y/pr94066-2.C > > > create mode 100644 gcc/testsuite/g++.dg/cpp1y/pr94066-3.C > > > create mode 100644 gcc/testsuite/g++.dg/cpp1y/pr94066.C > > > create mode 100644 gcc/testsuite/g++.dg/cpp2a/constexpr-union1.C > > > > > > diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c > > > index 192face9a3a..6284ab25b7d 100644 > > > --- a/gcc/cp/constexpr.c > > > +++ b/gcc/cp/constexpr.c > > > @@ -2591,10 +2591,17 @@ reduced_constant_expression_p (tree t) > > > return false; > > > else if (cxx_dialect >= cxx2a > > > /* An ARRAY_TYPE doesn't have any TYPE_FIELDS. */ > > > - && (TREE_CODE (TREE_TYPE (t)) == ARRAY_TYPE > > > - /* A union only initializes one member. */ > > > - || TREE_CODE (TREE_TYPE (t)) == UNION_TYPE)) > > > + && TREE_CODE (TREE_TYPE (t)) == ARRAY_TYPE) > > > field = NULL_TREE; > > > + else if (cxx_dialect >= cxx2a > > > + && TREE_CODE (TREE_TYPE (t)) == UNION_TYPE) > > > + { > > > + if (CONSTRUCTOR_NELTS (t) == 0) > > > + /* An initialized union has a constructor element. */ > > > + return false; > > > + /* And it only initializes one member. */ > > > + field = NULL_TREE; > > > + } > > > else > > > field = next_initializable_field (TYPE_FIELDS (TREE_TYPE > > > (t))); > > > } > > > @@ -3446,8 +3453,14 @@ cxx_eval_component_reference (const constexpr_ctx > > > *ctx, tree t, > > > { > > > /* DR 1188 says we don't have to deal with this. */ > > > if (!ctx->quiet) > > > - error ("accessing %qD member instead of initialized %qD member in " > > > - "constant expression", part, CONSTRUCTOR_ELT (whole, > > > 0)->index); > > > + { > > > + 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; > > > } > > > @@ -3751,6 +3764,11 @@ cxx_eval_bare_aggregate (const constexpr_ctx *ctx, > > > tree t, > > > /* If we built a new CONSTRUCTOR, attach it now so that other > > > initializers can refer to it. */ > > > CONSTRUCTOR_APPEND_ELT (*p, index, new_ctx.ctor); > > > + else if (TREE_CODE (type) == UNION_TYPE) > > > + /* If we're constructing a union, set the active union member now so > > > + that we can later detect if the initializer attempts to activate > > > + another member. */ > > > + CONSTRUCTOR_APPEND_ELT (*p, index, NULL_TREE); > > > tree elt = cxx_eval_constant_expression (&new_ctx, value, > > > lval, > > > non_constant_p, > > > overflow_p); > > > @@ -3784,7 +3802,13 @@ cxx_eval_bare_aggregate (const constexpr_ctx *ctx, > > > tree t, > > > } > > > else > > > { > > > - if (new_ctx.ctor != ctx->ctor) > > > + if (TREE_CODE (type) == UNION_TYPE > > > + && (*p)->last().index != index) > > > + /* The initializer may have erroneously changed the active union > > > + member that we're initializing. */ > > > + gcc_assert (*non_constant_p); > > > + else if (new_ctx.ctor != ctx->ctor > > > + || TREE_CODE (type) == UNION_TYPE) > > > { > > > /* We appended this element above; update the value. */ > > > gcc_assert ((*p)->last().index == index); > > > @@ -4567,6 +4591,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, > > > tree t, > > > bool no_zero_init = true; > > > releasing_vec ctors; > > > + bool changed_active_union_member_p = false; > > > while (!refs->is_empty ()) > > > { > > > if (*valp == NULL_TREE) > > > @@ -4647,6 +4672,19 @@ cxx_eval_store_expression (const constexpr_ctx > > > *ctx, > > > tree t, > > > index); > > > *non_constant_p = true; > > > } > > > + else if (TREE_CODE (t) == MODIFY_EXPR > > > + && CONSTRUCTOR_NO_CLEARING (*valp)) > > > + { > > > + /* Diagnose changing the active union member while the union > > > + is in the process of being initialized. */ > > > > Hmm, could we also detect this situation by noticing that the current active > > constructor element has NULL_TREE value? > > I don't think a looking for a NULL_TREE value here would be sufficient as-is, > only because at that point the active constructor element of an > under-construction > union could also be an empty CONSTRUCTOR if the union member we're > initializing > is an aggregate and we're coming from cxx_eval_bare_aggregate, which does: > > if (new_ctx.ctor != ctx->ctor) > /* If we built a new CONSTRUCTOR, attach it now so that other > initializers can refer to it. */ > ---> CONSTRUCTOR_APPEND_ELT (*p, index, new_ctx.ctor); > else if (TREE_CODE (type) == UNION_TYPE) > /* If we're constructing a union, set the active union member now so > that we can later detect if the initializer attempts to activate > another member. */ > CONSTRUCTOR_APPEND_ELT (*p, index, NULL_TREE); > tree elt = cxx_eval_constant_expression (&new_ctx, value, > lval, > non_constant_p, overflow_p);
Checking for a NULL_TREE value instead would also not catch the case when we're called recursively from cxx_eval_store_expression in the !preeval case with an INIT_EXPR like u.a = foo(&u); for a similar reason -- cxx_eval_store_expression sets the value of the current active constructor element to an empty CONSTRUCTOR before evaluating the RHS. So we would end up ICEing on pr94066.C and not rejecting pr94066-2.C apparently. > > > > > > + if (!ctx->quiet) > > > + error_at (cp_expr_loc_or_input_loc (t), > > > + "change of the active member of a union " > > > + "from %qD to %qD during initialization", > > > + CONSTRUCTOR_ELT (*valp, 0)->index, > > > + index); > > > + *non_constant_p = true; > > > + } > > > /* Changing active member. */ > > > vec_safe_truncate (CONSTRUCTOR_ELTS (*valp), 0); > > > no_zero_init = true; > > > @@ -4675,6 +4713,10 @@ cxx_eval_store_expression (const constexpr_ctx > > > *ctx, > > > tree t, > > > vec_safe_insert (CONSTRUCTOR_ELTS (*valp), idx, ce); > > > cep = CONSTRUCTOR_ELT (*valp, idx); > > > + > > > + if (code == UNION_TYPE) > > > + /* Record that we've changed an active union member. */ > > > + changed_active_union_member_p = true; > > > } > > > found:; > > > } > > > @@ -4805,13 +4847,17 @@ cxx_eval_store_expression (const constexpr_ctx > > > *ctx, > > > tree t, > > > unsigned i; > > > bool c = TREE_CONSTANT (init); > > > bool s = TREE_SIDE_EFFECTS (init); > > > - if (!c || s) > > > + if (!c || s || changed_active_union_member_p) > > > FOR_EACH_VEC_ELT (*ctors, i, elt) > > > { > > > if (!c) > > > TREE_CONSTANT (elt) = false; > > > if (s) > > > TREE_SIDE_EFFECTS (elt) = true; > > > + /* Clear CONSTRUCTOR_NO_CLEARING since we've activated a member of > > > + this union. */ > > > + if (TREE_CODE (TREE_TYPE (elt)) == UNION_TYPE) > > > + CONSTRUCTOR_NO_CLEARING (elt) = false; > > > } > > > if (*non_constant_p) > > > @@ -6133,8 +6179,13 @@ cxx_eval_constant_expression (const constexpr_ctx > > > *ctx, tree t, > > > case PLACEHOLDER_EXPR: > > > /* Use of the value or address of the current object. */ > > > if (tree ctor = lookup_placeholder (ctx, lval, TREE_TYPE (t))) > > > - return cxx_eval_constant_expression (ctx, ctor, lval, > > > - non_constant_p, overflow_p); > > > + { > > > + if (TREE_CODE (ctor) == CONSTRUCTOR) > > > + return ctor; > > > + else > > > + return cxx_eval_constant_expression (ctx, ctor, lval, > > > + non_constant_p, overflow_p); > > > + } > > > /* A placeholder without a referent. We can get here when > > > checking whether NSDMIs are noexcept, or in massage_init_elt; > > > just say it's non-constant for now. */ > > > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-union2.C > > > b/gcc/testsuite/g++.dg/cpp1y/constexpr-union2.C > > > new file mode 100644 > > > index 00000000000..7a6a818742b > > > --- /dev/null > > > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-union2.C > > > @@ -0,0 +1,9 @@ > > > +// { dg-do compile { target c++14 } } > > > + > > > +union U > > > +{ > > > + char *x = &y; > > > + char y; > > > +}; > > > + > > > +constexpr U u = {}; > > > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-union3.C > > > b/gcc/testsuite/g++.dg/cpp1y/constexpr-union3.C > > > new file mode 100644 > > > index 00000000000..5cf62e46cb5 > > > --- /dev/null > > > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-union3.C > > > @@ -0,0 +1,9 @@ > > > +// { dg-do compile { target c++14 } } > > > + > > > +union U > > > +{ > > > + int x = (x = x + 1); > > > + char y; > > > +}; > > > + > > > +constexpr U u = {}; // { dg-error "accessing uninitialized member" } > > > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-union4.C > > > b/gcc/testsuite/g++.dg/cpp1y/constexpr-union4.C > > > new file mode 100644 > > > index 00000000000..3e44a1378f3 > > > --- /dev/null > > > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-union4.C > > > @@ -0,0 +1,9 @@ > > > +// { dg-do compile { target c++14 } } > > > + > > > +union U > > > +{ > > > + int x = y; > > > + char y; > > > +}; > > > + > > > +constexpr U u = {}; // { dg-error "accessing uninitialized member" } > > > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-union5.C > > > b/gcc/testsuite/g++.dg/cpp1y/constexpr-union5.C > > > new file mode 100644 > > > index 00000000000..55fe9fa2f0b > > > --- /dev/null > > > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-union5.C > > > @@ -0,0 +1,15 @@ > > > +// { dg-do compile { target c++14 } } > > > + > > > +union U; > > > +constexpr int foo(U *up); > > > + > > > +union U { > > > + int a = foo(this); int y; > > > +}; > > > + > > > +constexpr int foo(U *up) { > > > + up->a++; > > > + return {42}; > > > +} > > > + > > > +extern constexpr U u = {}; // { dg-error "accessing uninitialized > > > member" } > > > diff --git a/gcc/testsuite/g++.dg/cpp1y/pr94066-2.C > > > b/gcc/testsuite/g++.dg/cpp1y/pr94066-2.C > > > new file mode 100644 > > > index 00000000000..1c00b650961 > > > --- /dev/null > > > +++ b/gcc/testsuite/g++.dg/cpp1y/pr94066-2.C > > > @@ -0,0 +1,19 @@ > > > +// PR c++/94066 > > > +// { dg-do compile { target c++14 } } > > > + > > > +struct A { long x; }; > > > + > > > +union U; > > > +constexpr A foo(U *up); > > > + > > > +union U { > > > + U() = default; > > > + A a = foo(this); int y; > > > +}; > > > + > > > +constexpr A foo(U *up) { > > > + up->y = 11; // { dg-error "'U::a' to 'U::y'" } > > > + return {42}; > > > +} > > > + > > > +extern constexpr U u = {}; > > > diff --git a/gcc/testsuite/g++.dg/cpp1y/pr94066-3.C > > > b/gcc/testsuite/g++.dg/cpp1y/pr94066-3.C > > > new file mode 100644 > > > index 00000000000..175018acf86 > > > --- /dev/null > > > +++ b/gcc/testsuite/g++.dg/cpp1y/pr94066-3.C > > > @@ -0,0 +1,16 @@ > > > +// PR c++/94066 > > > +// { dg-do compile { target c++14 } } > > > + > > > +union U; > > > +constexpr int foo(U *up); > > > + > > > +union U { > > > + int a = foo(this); int y; > > > +}; > > > + > > > +constexpr int foo(U *up) { > > > + up->y = 11; // { dg-error "'U::a' to 'U::y'" } > > > + return {42}; > > > +} > > > + > > > +extern constexpr U u = {}; > > > diff --git a/gcc/testsuite/g++.dg/cpp1y/pr94066.C > > > b/gcc/testsuite/g++.dg/cpp1y/pr94066.C > > > new file mode 100644 > > > index 00000000000..6725c8c737f > > > --- /dev/null > > > +++ b/gcc/testsuite/g++.dg/cpp1y/pr94066.C > > > @@ -0,0 +1,18 @@ > > > +// PR c++/94066 > > > +// { dg-do compile { target c++14 } } > > > + > > > +struct A { long x; }; > > > + > > > +union U; > > > +constexpr A foo(U *up); > > > + > > > +union U { > > > + A a = foo(this); int y; > > > +}; > > > + > > > +constexpr A foo(U *up) { > > > + up->y = 11; // { dg-error "'U::a' to 'U::y'" } > > > + return {42}; > > > +} > > > + > > > +extern constexpr U u = {}; > > > diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-union1.C > > > b/gcc/testsuite/g++.dg/cpp2a/constexpr-union1.C > > > new file mode 100644 > > > index 00000000000..c38167ad798 > > > --- /dev/null > > > +++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-union1.C > > > @@ -0,0 +1,18 @@ > > > +// { dg-do compile { target c++2a } } > > > + > > > +union U > > > +{ > > > + int x; > > > + char y; > > > +}; > > > + > > > +constexpr bool > > > +baz () > > > +{ > > > + U u; > > > + u.x = 3; > > > + u.y = 7; > > > + return (u.y == 7); > > > +} > > > + > > > +static_assert (baz ()); > > > > > > > >