C++ Logo

SG12

Advanced search

Subject: Re: [ub] Is dereferencing this pointer a UB?
From: Jens Maurer (Jens.Maurer_at_[hidden])
Date: 2017-08-12 04:55:50


On 08/12/2017 10:52 AM, Andrzej Krzemienski wrote:
>
>
> 2017-08-11 13:47 GMT+02:00 Jens Maurer <Jens.Maurer_at_[hidden] <mailto:Jens.Maurer_at_[hidden]>>:
>
> On 08/11/2017 09:58 AM, Andrzej Krzemienski wrote:
> > Hi SG12 Members,
> >
> > I already asked this question in ISO C++ Standard - Discussion (https://groups.google.com/a/isocpp.org/forum/?fromgroups=#!topic/std-discussion/UbROFU6Fs0E <https://groups.google.com/a/isocpp.org/forum/?fromgroups=#%21topic/std-discussion/UbROFU6Fs0E> <https://groups.google.com/a/isocpp.org/forum/?fromgroups=#%21topic/std-discussion/UbROFU6Fs0E <https://groups.google.com/a/isocpp.org/forum/?fromgroups=#%21topic/std-discussion/UbROFU6Fs0E>>), but maybe this list is better suited.
> >
> > UB-sanitizer reports a runtime error for the following program:
> >
> > ```
> > struct B;
> >
> > struct I {
> > virtual void f() {}; // <- virtual
> > };
> >
> > struct A : I {
> > A();
> > };
> >
> > struct B : A {
> > };
> >
> > A::A() { *static_cast<B*>(this); } // <- UB in static_cast
> >
> > int main()
> > {
> > B{};
> > }
> > ```
> >
> > My question: is UB-sanitizer correct? Is this a UB according to the standard? And if so, could you point me to the relevant sections?
>
> The dereference here is immaterial; it just converts a pointer to an lvalue,
> neither of which accesses the pointed-to value per se.
>
> The conversion happens while A and B are being constructed, and we have
> special rules in 15.7 [class.cdtor] for that. Of particular interest
> is p2, which discusses conversions from B* to A*, but 8.2.9 [expr.static.cast]
> indirectly refers to that case when discussing the A* to B* case.

Sorry, p2 discusses the lvalue case; p11 is applicable for base-to-derived
pointer casts and uses the same words.

> Both the construction of B and A have started at the point in question,
> so it seems to me the pointer conversion is, in fact, valid.
>
>
> Thanks for your analysis. Interestingly, I have now arrived at the
> opposite conclusion, based on [expr.static.cast]/p11 and
> [class.dtor]/p16, and some interpolation of my own. Let me share my
> reasoning, and tell me what you make of it.

> [class.cdtor]/p2 talks about the upcast, so I would rather go with
> [expr.static.cast]/p11 which talks about the downcast.

Ok.

> The latter
> says the downcast is UB-free if the casted-from object O is casted to
> an object that contains O as its subobject.

I've always understood this to mean "if B is a base class of X and Y,
and you cast a B* to an X*, but the complete object is actually a Y,
you've got undefined behavior". Which seems totally reasonable.

> Term "object" implies a
> relation to run-time (life-time), there is just the question whether
> we mean "object lifetime (after non-delegating constructor finishes
> and before the destructor starts) or the "extended object time"
> (after constructor starts and before destructor finishes).
>
> Now, [class.dtor]/p16 says, "Once a destructor is invoked for an
> object, the object no longer exists". This "exists" I find applicable
> to the definition of class.cdtor]/p2. If we were considering the same
> downcast in the *destructor* of A, I would have a clear answer. Now,
> there is no similar statement when the object starts to "exist", but
> by analogy to destructor, I would conclude that it starts to exist
> when the constructor finises. Untill then, there is no object B in
> existence and therefore the the static_cast condition that "the
> casted-from object O is casted to an object that contains O as its
> subobject" is not satisfied.

[class.cdtor] p2 is clearly written to spell out the details of
allowable "this" casts while an object is under construction or
destruction. Beyond that, I find it implausible that general object
lifetime constraints should come into play just for the "subobject"
part in 8.2.9p11.

Also, it seems we're using "subobject" elsewhere in a non-lifetime
related manner, e.g. 15.6.2p12:

"In a non-delegating constructor, the destructor for each potentially
constructed subobject of class type is potentially invoked (15.4)."

In any case, it seems the standard is not super-clear here and
could do with at least a note or an example. I've sent an e-mail
to the core reflector: http://lists.isocpp.org/core/2017/08/2759.php .

Jens


SG12 list run by herb.sutter at gmail.com