Lifetime cleanups for `basic_string` (#4047)

Co-authored-by: Stephan T. Lavavej <stl@nuwen.net>
This commit is contained in:
achabense 2023-10-15 02:51:05 +08:00 коммит произвёл GitHub
Родитель a9123e8383
Коммит 8d6922ae46
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
2 изменённых файлов: 75 добавлений и 37 удалений

Просмотреть файл

@ -2287,6 +2287,19 @@ public:
value_type _Buf[_BUF_SIZE];
pointer _Ptr;
char _Alias[_BUF_SIZE]; // TRANSITION, ABI: _Alias is preserved for binary compatibility (especially /clr)
_CONSTEXPR20 void _Switch_to_buf() noexcept {
_STD _Destroy_in_place(_Ptr);
#if _HAS_CXX20
// start the lifetime of the array elements
if (_STD is_constant_evaluated()) {
for (size_type _Idx = 0; _Idx < _BUF_SIZE; ++_Idx) {
_Buf[_Idx] = value_type();
}
}
#endif // _HAS_CXX20
}
};
_Bxty _Bx;
@ -2484,13 +2497,11 @@ private:
public:
_CONSTEXPR20
basic_string() noexcept(is_nothrow_default_constructible_v<_Alty>) : _Mypair(_Zero_then_variadic_args_t{}) {
_Mypair._Myval2._Alloc_proxy(_GET_PROXY_ALLOCATOR(_Alty, _Getal()));
_Tidy_init();
_Construct_empty();
}
_CONSTEXPR20 explicit basic_string(const _Alloc& _Al) noexcept : _Mypair(_One_then_variadic_args_t{}, _Al) {
_Mypair._Myval2._Alloc_proxy(_GET_PROXY_ALLOCATOR(_Alty, _Getal()));
_Tidy_init();
_Construct_empty();
}
_CONSTEXPR20 basic_string(const basic_string& _Right)
@ -2578,8 +2589,7 @@ public:
auto _UFirst = _Get_unwrapped(_First);
auto _ULast = _Get_unwrapped(_Last);
if (_UFirst == _ULast) {
_Mypair._Myval2._Alloc_proxy(_GET_PROXY_ALLOCATOR(_Alty, _Getal()));
_Tidy_init();
_Construct_empty();
} else {
if constexpr (_Is_elem_cptr<decltype(_UFirst)>::value) {
_Construct<_Construct_strategy::_From_ptr>(
@ -2630,6 +2640,19 @@ private:
_Al.deallocate(_Old_ptr, _Capacity + 1); // +1 for null terminator
}
_CONSTEXPR20 void _Construct_empty() {
auto& _My_data = _Mypair._Myval2;
_My_data._Alloc_proxy(_GET_PROXY_ALLOCATOR(_Alty, _Getal()));
// initialize basic_string data members
_My_data._Mysize = 0;
_My_data._Myres = _Small_string_capacity;
_My_data._Activate_SSO_buffer();
// the _Traits::assign is last so the codegen doesn't think the char write can alias this
_Traits::assign(_My_data._Bx._Buf[0], _Elem());
}
enum class _Construct_strategy : uint8_t { _From_char, _From_ptr, _From_string };
template <_Construct_strategy _Strat, class _Char_or_ptr>
@ -2918,8 +2941,7 @@ public:
basic_string(_String_constructor_rvalue_allocator_tag, _Alloc&& _Al)
: _Mypair(_One_then_variadic_args_t{}, _STD move(_Al)) {
// Used exclusively by basic_stringbuf
_Mypair._Myval2._Alloc_proxy(_GET_PROXY_ALLOCATOR(_Alty, _Getal()));
_Tidy_init();
_Construct_empty();
}
_NODISCARD bool _Move_assign_from_buffer(
@ -2958,10 +2980,13 @@ public:
_Released_buffer _Result;
auto& _My_data = _Mypair._Myval2;
_Result._Size = _My_data._Mysize;
_My_data._Orphan_all();
_ASAN_STRING_REMOVE(*this);
if (_My_data._Large_mode_engaged()) {
_Result._Ptr = _My_data._Bx._Ptr;
_Result._Actual_allocation_size = _My_data._Myres + 1;
_My_data._Bx._Switch_to_buf();
} else {
// use _Least_allocation_size to avoid small mode, if the buffer is assigned back
size_type _Allocated = _Least_allocation_size;
@ -2969,8 +2994,9 @@ public:
_Traits::copy(_Unfancy(_Result._Ptr), _My_data._Bx._Buf, _BUF_SIZE);
_Result._Actual_allocation_size = _Allocated;
}
_My_data._Orphan_all();
_Tidy_init();
_My_data._Mysize = 0;
_My_data._Myres = _Small_string_capacity;
_Traits::assign(_My_data._Bx._Buf[0], _Elem());
return _Result;
}
#endif // _HAS_CXX20
@ -3038,27 +3064,34 @@ private:
const auto _Right_data_mem =
reinterpret_cast<const unsigned char*>(_STD addressof(_Right._Mypair._Myval2)) + _Memcpy_val_offset;
_CSTD memcpy(_My_data_mem, _Right_data_mem, _Memcpy_val_size);
_Right._Tidy_init();
_Right_data._Mysize = 0;
_Right_data._Myres = _Small_string_capacity;
_Right_data._Activate_SSO_buffer();
_Traits::assign(_Right_data._Bx._Buf[0], _Elem());
return;
}
}
#endif // !defined(_INSERT_STRING_ANNOTATION)
if (_Right_data._Large_mode_engaged()) { // steal buffer
_Construct_in_place(_My_data._Bx._Ptr, _Right_data._Bx._Ptr);
_Swap_proxy_and_iterators(_Right);
_Destroy_in_place(_Right_data._Bx._Ptr);
_Construct_in_place(_My_data._Bx._Ptr, _Right_data._Bx._Ptr);
_Right_data._Bx._Switch_to_buf();
} else { // copy small string buffer
_Right_data._Orphan_all();
_My_data._Activate_SSO_buffer();
_Traits::copy(_My_data._Bx._Buf, _Right_data._Bx._Buf, _Right_data._Mysize + 1);
_Right_data._Orphan_all();
}
_My_data._Myres = _Right_data._Myres;
_My_data._Mysize = _Right_data._Mysize;
_Right._Tidy_init();
_Right_data._Mysize = 0;
_Right_data._Myres = _Small_string_capacity;
_Traits::assign(_Right_data._Bx._Buf[0], _Elem());
}
#if _HAS_CXX23
@ -4252,9 +4285,7 @@ public:
static _CONSTEXPR20 void _Swap_bx_large_with_small(_Scary_val& _Starts_large, _Scary_val& _Starts_small) noexcept {
// exchange a string in large mode with one in small mode
const pointer _Ptr = _Starts_large._Bx._Ptr;
_Destroy_in_place(_Starts_large._Bx._Ptr);
_Starts_large._Activate_SSO_buffer();
_Starts_large._Bx._Switch_to_buf();
_Traits::copy(_Starts_large._Bx._Buf, _Starts_small._Bx._Buf, _BUF_SIZE);
_Construct_in_place(_Starts_small._Bx._Ptr, _Ptr);
@ -4802,10 +4833,9 @@ private:
_My_data._Orphan_all();
_ASAN_STRING_REMOVE(*this);
const pointer _Ptr = _My_data._Bx._Ptr;
auto& _Al = _Getal();
_Destroy_in_place(_My_data._Bx._Ptr);
_My_data._Activate_SSO_buffer();
_My_data._Bx._Switch_to_buf();
_Traits::copy(_My_data._Bx._Buf, _Unfancy(_Ptr), _My_data._Mysize + 1);
auto& _Al = _Getal();
_Deallocate_for_capacity(_Al, _Ptr, _My_data._Myres);
_My_data._Myres = _Small_string_capacity;
}
@ -4815,27 +4845,14 @@ private:
_Traits::assign(_Mypair._Myval2._Myptr()[_Mypair._Myval2._Mysize = _New_size], _Elem());
}
_CONSTEXPR20 void _Tidy_init() noexcept {
// initialize basic_string data members
auto& _My_data = _Mypair._Myval2;
_My_data._Mysize = 0;
_My_data._Myres = _Small_string_capacity;
_My_data._Activate_SSO_buffer();
// the _Traits::assign is last so the codegen doesn't think the char write can alias this
_Traits::assign(_My_data._Bx._Buf[0], _Elem());
}
_CONSTEXPR20 void _Tidy_deallocate() noexcept { // initialize buffer, deallocating any storage
auto& _My_data = _Mypair._Myval2;
_My_data._Orphan_all();
if (_My_data._Large_mode_engaged()) {
_ASAN_STRING_REMOVE(*this);
const pointer _Ptr = _My_data._Bx._Ptr;
auto& _Al = _Getal();
_Destroy_in_place(_My_data._Bx._Ptr);
_My_data._Activate_SSO_buffer();
_Deallocate_for_capacity(_Al, _Ptr, _My_data._Myres);
auto& _Al = _Getal();
_Deallocate_for_capacity(_Al, _My_data._Bx._Ptr, _My_data._Myres);
_My_data._Bx._Switch_to_buf();
}
_My_data._Mysize = 0;

Просмотреть файл

@ -3,6 +3,7 @@
#include <algorithm>
#include <cassert>
#include <crtdbg.h>
#include <cstdlib>
#include <cstring>
#include <deque>
@ -15,6 +16,7 @@
#include <memory>
#include <new>
#include <set>
#include <sstream>
#include <string>
#include <type_traits>
#include <unordered_map>
@ -971,6 +973,20 @@ _CONSTEXPR20 void test_string_swap(const size_t id1, const size_t id2) {
assert(dst.get_allocator().id() == id1);
}
#if _HAS_CXX20
void test_string_move_to_stringbuf() {
// GH-4047 fixed a bug where basic_string forgets to destroy the pointer before switching to small
// mode. This will turn problematic if the pointer is non-trivial.
assert(!_CrtDumpMemoryLeaks());
{
using Alloc = StationaryAlloc<char>;
basic_string<char, char_traits<char>, Alloc> str(50, '0', Alloc(10));
basic_stringbuf<char, char_traits<char>, Alloc> strbuf(move(str));
}
assert(!_CrtDumpMemoryLeaks());
}
#endif // _HAS_CXX20
_CONSTEXPR20 bool test_string() {
test_string_copy_ctor();
@ -1002,6 +1018,11 @@ _CONSTEXPR20 bool test_string() {
test_string_swap<SwapAlloc<char32_t>>(11, 22); // POCS, non-equal allocators
test_string_swap<SwapEqualAlloc<char32_t>>(11, 22); // POCS, always-equal allocators
#if _HAS_CXX20
if (!is_constant_evaluated()) {
test_string_move_to_stringbuf();
}
#endif // _HAS_CXX20
return true;
}