On Thu, 20 Oct 2022 at 18:04, Edward Catmur <ecatmur@googlemail.com> wrote:


On Thu, 20 Oct 2022 at 17:38, <language.lawyer@gmail.com> 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.

What do you mean by explicitly writing «storage»? You disagree with that the expression accesses `s`?

Yes, since `s` is not a glvalue of scalar type.

> But a read would not have undefined behavior.

[basic.lval]/11 apply uniformly to both kinds of access: reads and writes.

Oh oops, didn't spot that the reinterpret_cast was to int. If it was to a storage-like type, that would be well-defined, no?

>> 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].

I know.

>> 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].

AFAIK, recursive invocations are handled by https://timsong-cpp.github.io/cppwp/n4868/reentrancy (plus some LWG issue saying that invoking method A on a library object during execution of method B on the same object is also (should also be) covered by this paragraph as a recursive invocation)

That's LWG 2414, no?

So you do not need [res.on.objects] to restrict a user destructor in calling a vector's methods during that vector's destruction which caused the invocation of the user's destructor.

You're saying that with LWG 2414 resolved, [res.on.objects]/2 becomes a dead letter? If so, agree that this looks like an improvement, and fixes the issue of a library class being able to access its internals during cdestruction.

One issue is that ctdors are not non-static member functions, so that proposed resolution (to LWG 2414) would need to be extended to mention those as well.

Another is the virtual member function issue (though that's probably not a problem in practice; I don't *think* the Standard has any virtual member functions not accessing object state).

But most crucially, we're still left with the issue of whether invoking a non-static member function (or cdtor) is or causes access. Again, the proposed language in LWG 2414 says "During the execution of a standard library non-static member function F on an object, if that object is accessed [...]", implying that that object is of a class type, so not a scalar, so per se cannot be accessed.

So, what I would suggest would be:

1. Adopt the proposed resolution to LWG 2414, with the following amendments:
"During the execution of a standard library ^constructor, destructor or^ non-static member function F on an object, if ^any of the subobjects of^ that object is accessed through a glvalue [...]"
2. Change [res.on.objects]/2 to a /Note:/
3. Add language (after [res.on.objects]/2?) to forbid dynamic polymorphic access during cdestruction:
"Before the beginning of the lifetime of an object x of standard library type, and after the end of its lifetime, the following have undefined behavior unless otherwise specified:
- forming a pointer or reference to a direct or indirect virtual base class subobject of x;
- forming a pointer or reference to a direct or indirect non-static data member of a direct or indirect virtual base class subobject of x;
- directly or indirectly calling a virtual member function on x;
- invoking the `typeid` operator with operand `x`;
- invoking `dynamic_cast` with operand `x`."