[ub] Is dereferencing this pointer a UB?

Jens Maurer Jens.Maurer at gmx.net
Sat Aug 12 11:55:50 CEST 2017


On 08/12/2017 10:52 AM, Andrzej Krzemienski wrote:
> 
> 
> 2017-08-11 13:47 GMT+02:00 Jens Maurer <Jens.Maurer at gmx.net <mailto:Jens.Maurer at gmx.net>>:
> 
>     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


More information about the ub mailing list