C++ Logo

std-discussion

Advanced search

Re: UB in P2641 'Checking if a union alternative is active'

From: Matthew House <mattlloydhouse_at_[hidden]>
Date: Mon, 19 Jun 2023 20:04:32 -0400
On Mon, Jun 19, 2023 at 5:33 PM language.lawyer--- via Std-Discussion
<std-discussion_at_[hidden]> wrote:
>
> >>> How so? c1 is a punned reference to u, and c2 is a punned reference to
> >>> u.i, so [basic.lval]/11 applies, does it not? Meanwhile, reading u.c
> >>> for c3 would access an inactive variant.
> >>
> >> 'u.c' is pointer-interconvertible with 'u' (and 'u.i'), so '(char&)u'
> >> refers to 'u.c'.
> >
> > Pointer-interconvertibility is always a relationship between two
> > objects ([basic.compound]/4). If u.c exists, then the object u.c is
> > pointer-interconvertible with the object u. But in this case, the only
> > existing objects are u and u.i; the data member u.c does not refer to
> > an object.
>
> What is u.c then, an «empty lvalue»?)))))))))))))
> u.c denotes an out-of-lifetime object.

By my reading of [basic.life]/7, it refers to allocated storage that
the object could occupy, not any actual object. (I'm disregarding for
now the issue regarding whether it only applies if the object is
actually created at some point.) In particular, I interpret the
"before the lifetime" part as applying to glvalues that would
otherwise have referred to an object, had any object been created. If
an object is later created in that storage, then only at that point
will the glvalue start referring to the object.

For [expr.static.cast]/14 to change a pointer value, it demands that
"there is an object *b*" that is pointer-interconvertible, and I just
don't see how "there is" could be read as applying before creation (or
for that matter, after destruction). That would imply all sorts of
silly things; e.g., at a given point in the program, how can an
"out-of-lifetime" object have the same address as an object with which
it is pointer-interconvertible ([basic.compound]/4), even while its
properties haven't yet been determined since it hasn't yet been
created ([intro.object]/1)?

More generally, such an interpretation would completely break
mechanisms enabled by [basic.lval]/11 using pointers that happen to
refer to union members, since inactive union members would always have
preference over reinterpretations allowed by the rule. For instance,
suppose that u.c were declared as an unsigned char instead of a char.
Then, std::memcpy(dest, &u.i, sizeof(int)) would be UB, since by
reinterpreting its argument as an array of unsigned char, memcpy would
produce a pointer to u.c, then read past its end. I don't think that's
something that can be considered reasonable.

(Brian Bi refers to the issue of using [basic.lval]/11 to read the
object representation being somewhat dubious with respect to the
wording, but this issue also applies to other conversions that would
be otherwise allowed, such as converting an int* to an unsigned int*
when a containing union has an inactive unsigned int member.)

Received on 2023-06-20 00:04:44