Destructor Tombstones by StephanTLavavej 路 Pull Request #5318 路 microsoft/STL (original) (raw)
~_Vector_val()
forvector<T>
demonstrates the most complete pattern:- We guard everything with
#if _MSVC_STL_DESTRUCTOR_TOMBSTONES
, so it completely vanishes when disabled. - We guard against fancy pointers.
- We guard against constant evaluation.
- We use a named
_Tombstone
because_MSVC_STL_UINTPTR_TOMBSTONE_VALUE
might expand to a function call.
- We guard everything with
~_Vb_val()
forvector<bool>
already exists._Vectype _Myvec
provides the storage, which we just tombstoned.- Zeroing out
_Mysize
ensures thatvector<bool>::operator[]
hardening will detect that_Off < this->_Mysize
is false. - We don't need to
reinterpret_cast
here, so we don't need to guard against constant evaluation. - The general pattern is that we use the tombstone value for pointers, and default construction values (typically zero) for other members. This results in the safest state of "I look like I contain nothing after default construction, except that my pointers are radioactive".
- Note that different containers use different pointer types with
is_pointer_v
andreinterpret_cast
. For example,_Deque_val
uses_Mapptr
while_List_val
uses_Nodeptr
. ~_Hash()
for the unordered associative containers:- Doesn't need to deal with
_Mylist _List
, which is a normallist
. - Doesn't need to deal with
_Hash_vec<_Aliter> _Vec
. It's not a normalvector
, but it stores a_Vector_val
which we've tombstoned. - Setting
_Mask
and_Maxidx
back to their default construction values doesn't provide the same benefit as zeroing out size fields, but it's consistent with the pattern and seems worth doing.
- Doesn't need to deal with
This is special because of the Small String Optimization. In small mode, the string would be usable, so we need large mode for the tombstone to have any effect.
I'm setting _Mysize
to zero, to work with hardened preconditions as usual. For the capacity _Myres
,
I'm using the first value that basic_string
would use when entering large mode,
as it has a "roundup mask".
_Large_mode_engaged()
returns _Myres > _Small_string_capacity
, so _Small_string_capacity + 1
would be sufficient,
but it seems safer to use the value that basic_string
would ordinarily use,
as this is less likely to confuse basic_string
logic now and in the future.
Only the tombstone pointer needs to be impossible.
The Small Functor Optimization is much easier to deal with than basic_string
.
_Local()
returns _Getimpl() == static_cast<const void*>(&_Mystorage)
,
so the tombstone pointer will be detected as large.
Comment that _Tidy_deallocate()
already sets _Mysize = 0;
.
With a comment explaining why we're using _Small
mode.
This makes the destructor behave like reset()
.
Setting it to empty will work with precondition hardening to prevent access to the object.
We should not attempt to scribble over T
's bytes.
Everything goes through this pseudo-vtable.
__ExceptionPtrDestroy()
calls ~shared_ptr<const _EXCEPTION_RECORD>()
, but it's separately compiled.
Add an #error
for the future when we enable destructor tombstones by default.
Update the comments to clarify how we feel about this usage.
Add missing alignas
to the buffers - we were being very bad kitties.