Date: Sat, 23 Aug 2025 01:59:15 -0700
> On Aug 23, 2025, at 1:42 AM, organicoman <organicoman_at_[hidden]> wrote:
>> On Sat, 23 Aug 2025, 06:59 organicoman via Std-Proposals, <std-proposals_at_[hidden] <mailto:std-proposals_at_[hidden]>> wrote:
>>>
>>>
>>>
>>>
>>> Actually it does work without breaking any code.
>>> You have to overload the delete operator in global scope.
>>>
>>> template <typename T>
>>> void delete(void*, T* &p)
>>> {
>>> delete p;
>>> p= nullptr;
>>> }
>>>
>>> And you do the same with the delete[ ].
>>
>>
>> This is not necessary.
>>
>> When you write `delete p` the compiler already does more than just call `operator delete(p)`, because it also destroys `*p`
>>
>> The compiler can easily turn `delete p` into:
>>
>> std::destroy_at(p);
>> operator delete(p);
>> p = nullptr;
>>
> Is there a flag to turn this on?
No but you could provide a proof of concept, and use that to try to demonstrate a general improvement. It’s not an unheard of behavior: I’m aware of many frameworks that wrap deallocation functions with macros that do what you are suggesting.
This is what I mean by suggesting you could have the delete expression set that value of the delete target to nullptr if it was an lvalue. This would still cause problems for reference parameters as it is UB for a reference to by null.
>
>> This is already allowed today, you don't need to change the standard. It's not*required* by the standard because it adds a store that is usually redundant, but compilers could offer an option to do this.
>>
>> As others have pointed out, doing that is unlikely to prevent very many bugs in practice.
>>
>> Use after free, double free...etc will be more like to be caught in code
>> {
>> m_ptr = new T;
>>
>> // if all the following take m_ptr by reference
>> inspect_maybe_free(m_ptr);
>> transform_maybe_free(m_ptr);
>> maybe_free(m_ptr);
>>
>> // i can detect if delete was called before.
>> if(m_ptr) delete m_ptr;
>> }
>>
>> With the current implementation, you cannot do this.
>>
>> That doesn't matter. Zeroing the pointer can be done by the delete operator, not by the `operator delete` function.
>
> As far as I know, no compiler does this!
> Is there?
No, but again you could suggest it.
In practice it would be a dead store - it’s UB to try and use a ptr after deletion as I think once the lifetime of the pointed-to object has ended the value of the pointer is no longer usable for any purpose, and in principle code won’t be using that pointer post deletion. This is the issue with what you’re proposing: in general UaFs are not trivially straight-line to UaF.
An example of a basic non-obvious UaF:
for (auto [key, value]: someCache)
doSomething(key, value);
doSomething can easily lead to a mutation of the cache object, making the iterator being used invalid, and so leading to a UaF. This is in practice what I would consider an actual trivial UaF, because straight line examples you’ve presented are so easily automatically detected and presented.
What you need to do is think about how to resolve the behavior when you do not have linear lifetime. There is a lot of research, and many people working on, the topic of lifetime analysis and management in C++, and if you’re interested in the topic many people on this list can point you to the right places.
Cheers,
—Oliver
>> On Sat, 23 Aug 2025, 06:59 organicoman via Std-Proposals, <std-proposals_at_[hidden] <mailto:std-proposals_at_[hidden]>> wrote:
>>>
>>>
>>>
>>>
>>> Actually it does work without breaking any code.
>>> You have to overload the delete operator in global scope.
>>>
>>> template <typename T>
>>> void delete(void*, T* &p)
>>> {
>>> delete p;
>>> p= nullptr;
>>> }
>>>
>>> And you do the same with the delete[ ].
>>
>>
>> This is not necessary.
>>
>> When you write `delete p` the compiler already does more than just call `operator delete(p)`, because it also destroys `*p`
>>
>> The compiler can easily turn `delete p` into:
>>
>> std::destroy_at(p);
>> operator delete(p);
>> p = nullptr;
>>
> Is there a flag to turn this on?
No but you could provide a proof of concept, and use that to try to demonstrate a general improvement. It’s not an unheard of behavior: I’m aware of many frameworks that wrap deallocation functions with macros that do what you are suggesting.
This is what I mean by suggesting you could have the delete expression set that value of the delete target to nullptr if it was an lvalue. This would still cause problems for reference parameters as it is UB for a reference to by null.
>
>> This is already allowed today, you don't need to change the standard. It's not*required* by the standard because it adds a store that is usually redundant, but compilers could offer an option to do this.
>>
>> As others have pointed out, doing that is unlikely to prevent very many bugs in practice.
>>
>> Use after free, double free...etc will be more like to be caught in code
>> {
>> m_ptr = new T;
>>
>> // if all the following take m_ptr by reference
>> inspect_maybe_free(m_ptr);
>> transform_maybe_free(m_ptr);
>> maybe_free(m_ptr);
>>
>> // i can detect if delete was called before.
>> if(m_ptr) delete m_ptr;
>> }
>>
>> With the current implementation, you cannot do this.
>>
>> That doesn't matter. Zeroing the pointer can be done by the delete operator, not by the `operator delete` function.
>
> As far as I know, no compiler does this!
> Is there?
No, but again you could suggest it.
In practice it would be a dead store - it’s UB to try and use a ptr after deletion as I think once the lifetime of the pointed-to object has ended the value of the pointer is no longer usable for any purpose, and in principle code won’t be using that pointer post deletion. This is the issue with what you’re proposing: in general UaFs are not trivially straight-line to UaF.
An example of a basic non-obvious UaF:
for (auto [key, value]: someCache)
doSomething(key, value);
doSomething can easily lead to a mutation of the cache object, making the iterator being used invalid, and so leading to a UaF. This is in practice what I would consider an actual trivial UaF, because straight line examples you’ve presented are so easily automatically detected and presented.
What you need to do is think about how to resolve the behavior when you do not have linear lifetime. There is a lot of research, and many people working on, the topic of lifetime analysis and management in C++, and if you’re interested in the topic many people on this list can point you to the right places.
Cheers,
—Oliver
Received on 2025-08-23 08:59:29