Discussion:
[ub] Is dereferencing this pointer a UB?
Andrzej Krzemienski
2017-08-11 07:58:22 UTC
Permalink
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),
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?

Some people point out that pointer dereference means accessing the (not yet
constructed) object, but according to my reading referring to an object is
not access. And also, UB sanitizer reports an UB when there is no
dereference:


```
struct B;

struct I {
virtual void f() {}; // <- virtual
};

struct A : I {
A();
};

struct B : A {
};

A::A() { static_cast<B*>(this); } // <- No dereference

int main()
{
B{};
}
```
Is this second example a UB also?

Regards,
&rzej;
Jens Maurer
2017-08-11 11:47:25 UTC
Permalink
Post by Andrzej Krzemienski
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>), but maybe this list is better suited.
```
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.

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.

Jens
Andrzej Krzemienski
2017-08-12 08:52:00 UTC
Permalink
Post by Andrzej Krzemienski
Post by Andrzej Krzemienski
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>), but
maybe this list is better suited.
Post by Andrzej Krzemienski
```
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.
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. 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. 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.

Is the above reasoning correct?

Regards,
&rzej;
Jens Maurer
2017-08-12 09:55:50 UTC
Permalink
Post by Jens Maurer
Post by Andrzej Krzemienski
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.
```
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.
Post by Jens Maurer
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.
Post by Jens Maurer
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.
Post by Jens Maurer
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

Loading...