Date: Thu, 20 Oct 2022 17:11:47 +0100
On Thu, 20 Oct 2022 at 10:14, language.lawyer--- via Std-Discussion <
std-discussion_at_[hidden]> wrote:
> On 20/10/2022 07:11, Eric Schmidt via Std-Discussion wrote:
> > This issue came up on a StackOverflow question:
> >
> >
> https://stackoverflow.com/questions/74080177/double-free-in-the-c-standard-library-using-only-stdfunction-and-stdshared/74080676
> >
> > This led to a discussion here:
> >
> > https://chat.stackoverflow.com/rooms/248862/discuss-lwg2224
> >
> > Now, https://eel.is/c++draft/res.on.objects states
> >
> > "If an object of a standard library type is accessed, and the beginning
> of the object's lifetime does not happen before the access, or the access
> does not happen before the end of the object's lifetime, the behavior is
> undefined unless otherwise specified."
> >
> > According to a non-normative note in
> https://eel.is/c++draft/defns.access,
> >
> > "Only glvalues of scalar type can be used to access objects. ...
> Attempts to read or modify an object of class type typically invoke a
> constructor or assignment operator; such invocations do not themselves
> constitute accesses, although they may involve accesses of scalar
> subobjects."
> >
> > The question is, can objects of class type be accessed?
>
> Can, in some sense. But it would be UB. E.g.
>
> struct S
> {
> char buf[sizeof(int)];
> } s;
>
> reinterpret_cast<int&>(s) = 0; // kinda accesses `s`, but this violates
> strict aliasing, so the behavior is actually undefined
>
It accesses `s`'s storage, sure. But a read would not have undefined
behavior.
> Is it the intent that an access to a scalar subobject also constitutes an
> access to the object itself?
>
> No
>
> > If not, then it is impossible for objects of most library types to be
> accessed.
>
> There are other things which are undefined if an object (or rather a
> pointer/glvalue referring to it) is used before the lifetime has started
> https://timsong-cpp.github.io/cppwp/n4868/basic.life#6.sentence-5
> https://timsong-cpp.github.io/cppwp/n4868/basic.life#7.sentence-4
That doesn't apply to objects of class type within
construction/destruction; it only applies before their constructor starts
or after the destructor ends. During construction/destruction, we are
referred to [class.cdtor].
> If so, then objects of many library types are unavoidably accessed by
> their own destructor, after the end of their lifetime. Either way,
> [res.on.objects] doesn't work.
>
> Treat [res.on.objects] as a non-normative reminder about above-mentioned
> parts from [basic.life]
> (But in general, there are lots of nonsensical wording in the library
> part, if read it from strict core part POV.)
>
The library desires to be more restrictive than the language here. It is
possible to write user code that is robust against recursive invocation (by
ensuring that invariants hold before calling any operation not directly
under the control of the user code, and by ensuring that all and any
recursive modifications called from therein are handled in some defined
way). But we don't want to constrain the library to that extent, so we add
UB under [constraints].
> "11.9.5 [class.cdtor] <https://wg21.link/class.cdtor>/p4 explicitly
permits member function calls during destruction, so the behavior of this
code is well-defined as far as the core language is concerned, despite the
fact that it accesses a library object after the end of the object's
lifetime. If we want this code to have undefined behavior, we need to
specify that at the library level."
In 2414, Tim appears to be following the "scalar subobject" interpretation
(the reference to data races makes this fairly clear). For 2224, surely the
same interpretation is in play ("access" was defined in CWG 1531 the
previous year, and the power of that definition is clearly on respondents'
minds). Perhaps one point that can save us from concluding that library
classes aren't allowed to access their scalar subobjects from their cdtors
would be to note that [res.on.objects] is a subclause of [constraints], so
the restriction should only hold for access caused by user code. But that's
a bit of a reach.
Another concern would be: is invocation of a virtual member function (which
may not access a scalar) an access? It seems that it should be, otherwise
we might contrive to invoke virtual member functions on standard library
classes during construction/destruction, but that isn't access to a glvalue
scalar.
> For reference, here is some previous discussion from 2014:
> >
> >
> https://groups.google.com/a/isocpp.org/g/std-discussion/c/BGsVyc0Ittw/m/atiDfIqnc-YJ
> >
> > And the LWG issue that led to the current wording:
> >
> > https://cplusplus.github.io/LWG/issue2224
> --
> Std-Discussion mailing list
> Std-Discussion_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
>
std-discussion_at_[hidden]> wrote:
> On 20/10/2022 07:11, Eric Schmidt via Std-Discussion wrote:
> > This issue came up on a StackOverflow question:
> >
> >
> https://stackoverflow.com/questions/74080177/double-free-in-the-c-standard-library-using-only-stdfunction-and-stdshared/74080676
> >
> > This led to a discussion here:
> >
> > https://chat.stackoverflow.com/rooms/248862/discuss-lwg2224
> >
> > Now, https://eel.is/c++draft/res.on.objects states
> >
> > "If an object of a standard library type is accessed, and the beginning
> of the object's lifetime does not happen before the access, or the access
> does not happen before the end of the object's lifetime, the behavior is
> undefined unless otherwise specified."
> >
> > According to a non-normative note in
> https://eel.is/c++draft/defns.access,
> >
> > "Only glvalues of scalar type can be used to access objects. ...
> Attempts to read or modify an object of class type typically invoke a
> constructor or assignment operator; such invocations do not themselves
> constitute accesses, although they may involve accesses of scalar
> subobjects."
> >
> > The question is, can objects of class type be accessed?
>
> Can, in some sense. But it would be UB. E.g.
>
> struct S
> {
> char buf[sizeof(int)];
> } s;
>
> reinterpret_cast<int&>(s) = 0; // kinda accesses `s`, but this violates
> strict aliasing, so the behavior is actually undefined
>
It accesses `s`'s storage, sure. But a read would not have undefined
behavior.
> Is it the intent that an access to a scalar subobject also constitutes an
> access to the object itself?
>
> No
>
> > If not, then it is impossible for objects of most library types to be
> accessed.
>
> There are other things which are undefined if an object (or rather a
> pointer/glvalue referring to it) is used before the lifetime has started
> https://timsong-cpp.github.io/cppwp/n4868/basic.life#6.sentence-5
> https://timsong-cpp.github.io/cppwp/n4868/basic.life#7.sentence-4
That doesn't apply to objects of class type within
construction/destruction; it only applies before their constructor starts
or after the destructor ends. During construction/destruction, we are
referred to [class.cdtor].
> If so, then objects of many library types are unavoidably accessed by
> their own destructor, after the end of their lifetime. Either way,
> [res.on.objects] doesn't work.
>
> Treat [res.on.objects] as a non-normative reminder about above-mentioned
> parts from [basic.life]
> (But in general, there are lots of nonsensical wording in the library
> part, if read it from strict core part POV.)
>
The library desires to be more restrictive than the language here. It is
possible to write user code that is robust against recursive invocation (by
ensuring that invariants hold before calling any operation not directly
under the control of the user code, and by ensuring that all and any
recursive modifications called from therein are handled in some defined
way). But we don't want to constrain the library to that extent, so we add
UB under [constraints].
> "11.9.5 [class.cdtor] <https://wg21.link/class.cdtor>/p4 explicitly
permits member function calls during destruction, so the behavior of this
code is well-defined as far as the core language is concerned, despite the
fact that it accesses a library object after the end of the object's
lifetime. If we want this code to have undefined behavior, we need to
specify that at the library level."
In 2414, Tim appears to be following the "scalar subobject" interpretation
(the reference to data races makes this fairly clear). For 2224, surely the
same interpretation is in play ("access" was defined in CWG 1531 the
previous year, and the power of that definition is clearly on respondents'
minds). Perhaps one point that can save us from concluding that library
classes aren't allowed to access their scalar subobjects from their cdtors
would be to note that [res.on.objects] is a subclause of [constraints], so
the restriction should only hold for access caused by user code. But that's
a bit of a reach.
Another concern would be: is invocation of a virtual member function (which
may not access a scalar) an access? It seems that it should be, otherwise
we might contrive to invoke virtual member functions on standard library
classes during construction/destruction, but that isn't access to a glvalue
scalar.
> For reference, here is some previous discussion from 2014:
> >
> >
> https://groups.google.com/a/isocpp.org/g/std-discussion/c/BGsVyc0Ittw/m/atiDfIqnc-YJ
> >
> > And the LWG issue that led to the current wording:
> >
> > https://cplusplus.github.io/LWG/issue2224
> --
> Std-Discussion mailing list
> Std-Discussion_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
>
Received on 2022-10-20 16:12:00