diff --git a/stl/inc/sstream b/stl/inc/sstream index 738113dc1a9..9a4aafa910a 100644 --- a/stl/inc/sstream +++ b/stl/inc/sstream @@ -473,11 +473,11 @@ protected: } // finite buffer that can be read or written, set it up - auto [_Ptr, _Size, _Res] = _Str._Release_to_buffer(_Al); - _Elem* const _Pnew = _Unfancy(_Ptr); - _Seekhigh = _Pnew + _Size; - auto _Next = (_State & (_Atend | _Append)) ? _Seekhigh : _Pnew; - auto _End_buffer = _Pnew + _Res; + auto [_Ptr, _Size, _Actual_allocation_size] = _Str._Release_to_buffer(_Al); + _Elem* const _Pnew = _Unfancy(_Ptr); + _Seekhigh = _Pnew + _Size; + auto _Next = (_State & (_Atend | _Append)) ? _Seekhigh : _Pnew; + auto _End_buffer = _Pnew + _Actual_allocation_size; _Mysb::setp(_Pnew, _Next, _End_buffer); if (_State & _Noread) { // maintain "_Allocated == eback() points to buffer base" invariant diff --git a/stl/inc/xstring b/stl/inc/xstring index 6dee3cfb28f..3ba4d362d76 100644 --- a/stl/inc/xstring +++ b/stl/inc/xstring @@ -2212,38 +2212,40 @@ public: _CONSTEXPR20 _String_val() noexcept : _Bx() {} - // length of internal buffer, [1, 16]: + // length of internal buffer, [1, 16] (NB: used by the debugger visualizer) static constexpr size_type _BUF_SIZE = 16 / sizeof(value_type) < 1 ? 1 : 16 / sizeof(value_type); - // roundup mask for allocated buffers, [0, 15]: - static constexpr size_type _ALLOC_MASK = sizeof(value_type) <= 1 ? 15 + // roundup mask for allocated buffers, [0, 15] + static constexpr size_type _Alloc_mask = sizeof(value_type) <= 1 ? 15 : sizeof(value_type) <= 2 ? 7 : sizeof(value_type) <= 4 ? 3 : sizeof(value_type) <= 8 ? 1 : 0; + // capacity in small mode + static constexpr size_type _Small_string_capacity = _BUF_SIZE - 1; - _CONSTEXPR20 value_type* _Myptr() noexcept { + _NODISCARD _CONSTEXPR20 value_type* _Myptr() noexcept { value_type* _Result = _Bx._Buf; - if (_Large_string_engaged()) { + if (_Large_mode_engaged()) { _Result = _Unfancy(_Bx._Ptr); } return _Result; } - _CONSTEXPR20 const value_type* _Myptr() const noexcept { + _NODISCARD _CONSTEXPR20 const value_type* _Myptr() const noexcept { const value_type* _Result = _Bx._Buf; - if (_Large_string_engaged()) { + if (_Large_mode_engaged()) { _Result = _Unfancy(_Bx._Ptr); } return _Result; } - _CONSTEXPR20 bool _Large_string_engaged() const noexcept { - return _BUF_SIZE <= _Myres; + _NODISCARD _CONSTEXPR20 bool _Large_mode_engaged() const noexcept { + return _Myres > _Small_string_capacity; } - constexpr void _Activate_SSO_buffer() noexcept { + _CONSTEXPR20 void _Activate_SSO_buffer() noexcept { // start the lifetime of the array elements #if _HAS_CXX20 if (_STD is_constant_evaluated()) { @@ -2272,7 +2274,7 @@ public: _Xout_of_range("invalid string position"); } - _CONSTEXPR20 size_type _Clamp_suffix_size(const size_type _Off, const size_type _Size) const noexcept { + _NODISCARD _CONSTEXPR20 size_type _Clamp_suffix_size(const size_type _Off, const size_type _Size) const noexcept { // trims _Size to the longest it can be assuming a string at/after _Off return (_STD min)(_Size, _Mysize - _Off); } @@ -2289,8 +2291,10 @@ public: }; _Bxty _Bx; - size_type _Mysize = 0; // current length of string - size_type _Myres = 0; // current storage reserved for string + // invariant: _Myres >= _Mysize, and _Myres >= _Small_string_capacity (after string's construction) + // neither _Mysize nor _Myres takes account of the extra null terminator + size_type _Mysize = 0; // current length of string (size) + size_type _Myres = 0; // current storage reserved for string (capacity) }; // get _Ty's size after being EBCO'd @@ -2360,8 +2364,12 @@ public: using const_reverse_iterator = _STD reverse_iterator; private: - static constexpr auto _BUF_SIZE = _Scary_val::_BUF_SIZE; - static constexpr auto _ALLOC_MASK = _Scary_val::_ALLOC_MASK; + static constexpr size_type _BUF_SIZE = _Scary_val::_BUF_SIZE; + static constexpr size_type _Alloc_mask = _Scary_val::_Alloc_mask; + static constexpr size_type _Small_string_capacity = _Scary_val::_Small_string_capacity; + + // least real allocation size, including space reserved for terminating null + static constexpr size_type _Least_allocation_size = _Small_string_capacity + 1 + 1; // When doing _String_val operations by memcpy, we are touching: // _String_val::_Bx::_Buf (type is array of _Elem) @@ -2421,7 +2429,7 @@ private: } #endif // _HAS_CXX20 // Don't annotate small strings; only annotate on the heap. - if (_Capacity == _BUF_SIZE - 1 || !_Asan_string_should_annotate) { + if (_Capacity <= _Small_string_capacity || !_Asan_string_should_annotate) { return; } @@ -2539,10 +2547,6 @@ public: _Construct<_Construct_strategy::_From_ptr>(_Ptr, _Convert_size(_Traits::length(_Ptr))); } -#if _HAS_CXX23 - basic_string(nullptr_t) = delete; -#endif // _HAS_CXX23 - #if _HAS_CXX17 template ::value, int> = 0> #endif // _HAS_CXX17 @@ -2551,6 +2555,10 @@ public: _Construct<_Construct_strategy::_From_ptr>(_Ptr, _Convert_size(_Traits::length(_Ptr))); } +#if _HAS_CXX23 + basic_string(nullptr_t) = delete; +#endif // _HAS_CXX23 + _CONSTEXPR20 basic_string(_CRT_GUARDOVERFLOW const size_type _Count, const _Elem _Ch) : _Mypair(_Zero_then_variadic_args_t{}) { // construct from _Count * _Ch _Construct<_Construct_strategy::_From_char>(_Ch, _Count); @@ -2588,19 +2596,39 @@ public: } private: - static constexpr void _Start_element_lifetimes(_Elem* const _Ptr, const size_type _Size) { + enum class _Allocation_policy { _At_least, _Exactly }; + + template <_Allocation_policy _Policy = _Allocation_policy::_At_least> + _NODISCARD static _CONSTEXPR20 pointer _Allocate_for_capacity(_Alty& _Al, size_type& _Capacity) { + _STL_INTERNAL_CHECK(_Capacity > _Small_string_capacity); + ++_Capacity; // Take null terminator into consideration + + pointer _Fancy_ptr; + if constexpr (_Policy == _Allocation_policy::_At_least) { + _Fancy_ptr = _Allocate_at_least_helper(_Al, _Capacity); + } else { + _STL_INTERNAL_STATIC_ASSERT(_Policy == _Allocation_policy::_Exactly); + _Fancy_ptr = _Al.allocate(_Capacity); + } + +#if _HAS_CXX20 // Start element lifetimes to avoid UB. This is a more general mechanism than _String_val::_Activate_SSO_buffer, // but likely more impactful to throughput. -#if _HAS_CXX20 if (_STD is_constant_evaluated()) { - for (size_type _Idx = 0; _Idx < _Size; ++_Idx) { + _Elem* const _Ptr = _Unfancy(_Fancy_ptr); + for (size_type _Idx = 0; _Idx < _Capacity; ++_Idx) { _STD construct_at(_Ptr + _Idx); } } -#else // ^^^ C++20-or-later / pre-C++20 vvv - (void) _Ptr; - (void) _Size; #endif // _HAS_CXX20 + --_Capacity; + return _Fancy_ptr; + } + + static _CONSTEXPR20 void _Deallocate_for_capacity( + _Alty& _Al, const pointer _Old_ptr, const size_type _Capacity) noexcept { + _STL_INTERNAL_CHECK(_Capacity > _Small_string_capacity); + _Al.deallocate(_Old_ptr, _Capacity + 1); // +1 for null terminator } enum class _Construct_strategy : uint8_t { _From_char, _From_ptr, _From_string }; @@ -2608,7 +2636,7 @@ private: template <_Construct_strategy _Strat, class _Char_or_ptr> _CONSTEXPR20 void _Construct(const _Char_or_ptr _Arg, _CRT_GUARDOVERFLOW const size_type _Count) { auto& _My_data = _Mypair._Myval2; - _STL_INTERNAL_CHECK(!_My_data._Large_string_engaged()); + _STL_INTERNAL_CHECK(!_My_data._Large_mode_engaged()); if constexpr (_Strat == _Construct_strategy::_From_char) { _STL_INTERNAL_STATIC_ASSERT(is_same_v<_Char_or_ptr, _Elem>); @@ -2624,9 +2652,9 @@ private: auto&& _Alproxy = _GET_PROXY_ALLOCATOR(_Alty, _Al); _Container_proxy_ptr<_Alty> _Proxy(_Alproxy, _My_data); - if (_Count < _BUF_SIZE) { + if (_Count <= _Small_string_capacity) { _My_data._Mysize = _Count; - _My_data._Myres = _BUF_SIZE - 1; + _My_data._Myres = _Small_string_capacity; if constexpr (_Strat == _Construct_strategy::_From_char) { _Traits::assign(_My_data._Bx._Buf, _Count, _Arg); @@ -2646,15 +2674,11 @@ private: return; } - _My_data._Myres = _BUF_SIZE - 1; + _My_data._Myres = _Small_string_capacity; size_type _New_capacity = _Calculate_growth(_Count); - ++_New_capacity; - const pointer _New_ptr = _Allocate_at_least_helper(_Al, _New_capacity); // throws - --_New_capacity; + const pointer _New_ptr = _Allocate_for_capacity(_Al, _New_capacity); // throws _Construct_in_place(_My_data._Bx._Ptr, _New_ptr); - _Start_element_lifetimes(_Unfancy(_New_ptr), _New_capacity + 1); - _My_data._Mysize = _Count; _My_data._Myres = _New_capacity; if constexpr (_Strat == _Construct_strategy::_From_char) { @@ -2676,7 +2700,7 @@ private: // Pre: _First models input_iterator or meets the Cpp17InputIterator requirements // Pre: [_First, _Last) is a valid range // Pre: if is_same_v<_Size, size_type>, _Count is the length of [_First, _Last). - // Pre: *this is in SSO mode; the lifetime of the SSO elements has already begun + // Pre: *this is in small mode; the lifetime of the SSO elements has already begun auto& _My_data = _Mypair._Myval2; auto& _Al = _Getal(); @@ -2684,22 +2708,18 @@ private: _Container_proxy_ptr<_Alty> _Proxy(_Alproxy, _My_data); _My_data._Mysize = 0; - _My_data._Myres = _BUF_SIZE - 1; + _My_data._Myres = _Small_string_capacity; if constexpr (is_same_v<_Size, size_type>) { if (_Count > max_size()) { _Xlen_string(); // result too long } - if (_Count >= _BUF_SIZE) { + if (_Count > _Small_string_capacity) { size_type _New_capacity = _Calculate_growth(_Count); - ++_New_capacity; - const pointer _New_ptr = _Allocate_at_least_helper(_Al, _New_capacity); // throws - --_New_capacity; + const pointer _New_ptr = _Allocate_for_capacity(_Al, _New_capacity); // throws _Construct_in_place(_My_data._Bx._Ptr, _New_ptr); _My_data._Myres = _New_capacity; - - _Start_element_lifetimes(_Unfancy(_New_ptr), _New_capacity + 1); } } @@ -2711,16 +2731,13 @@ private: _Xlen_string(); // result too long } - const auto _Old_ptr = _My_data._Myptr(); + _Elem* const _Old_ptr = _My_data._Myptr(); size_type _New_capacity = _Calculate_growth(_My_data._Mysize); - ++_New_capacity; - const pointer _New_ptr = _Allocate_at_least_helper(_Al, _New_capacity); // throws - --_New_capacity; + const pointer _New_ptr = _Allocate_for_capacity(_Al, _New_capacity); // throws - _Start_element_lifetimes(_Unfancy(_New_ptr), _New_capacity + 1); _Traits::copy(_Unfancy(_New_ptr), _Old_ptr, _My_data._Mysize); - if (_My_data._Myres >= _BUF_SIZE) { // Need to deallocate old storage - _Al.deallocate(_My_data._Bx._Ptr, _My_data._Myres + 1); + if (_My_data._Large_mode_engaged()) { // Need to deallocate old storage + _Deallocate_for_capacity(_Al, _My_data._Bx._Ptr, _My_data._Myres); _My_data._Bx._Ptr = _New_ptr; } else { _Construct_in_place(_My_data._Bx._Ptr, _New_ptr); @@ -2790,21 +2807,17 @@ public: _STL_INTERNAL_CHECK(_Right_size <= max_size()); _STL_INTERNAL_CHECK(_Right_size <= max_size() - _Left_size); const auto _New_size = static_cast(_Left_size + _Right_size); - size_type _New_capacity = _BUF_SIZE - 1; + size_type _New_capacity = _Small_string_capacity; auto& _My_data = _Mypair._Myval2; _Elem* _Ptr = _My_data._Bx._Buf; auto&& _Alproxy = _GET_PROXY_ALLOCATOR(_Alty, _Getal()); _Container_proxy_ptr<_Alty> _Proxy(_Alproxy, _My_data); // throws if (_New_capacity < _New_size) { - _New_capacity = _Calculate_growth(_New_size, _BUF_SIZE - 1, max_size()); - ++_New_capacity; - const pointer _Fancyptr = _Allocate_at_least_helper(_Getal(), _New_capacity); // throws - --_New_capacity; - _Ptr = _Unfancy(_Fancyptr); + _New_capacity = _Calculate_growth(_New_size, _Small_string_capacity, max_size()); + const pointer _Fancyptr = _Allocate_for_capacity(_Getal(), _New_capacity); // throws + _Ptr = _Unfancy(_Fancyptr); _Construct_in_place(_My_data._Bx._Ptr, _Fancyptr); - - _Start_element_lifetimes(_Ptr, _New_capacity + 1); } _My_data._Mysize = _New_size; @@ -2852,8 +2865,8 @@ public: // therefore: (by the distributive property) // (!_Fits_in_left && _Fits_in_right) // implying _Right has more capacity // || (_Right_capacity > _Left_capacity && _Fits_in_right) // tests that _Right has more capacity - // therefore: _Right must have more than the minimum capacity, so it must be _Large_string_engaged() - _STL_INTERNAL_CHECK(_Right_data._Large_string_engaged()); + // therefore: _Right must have more than the minimum capacity, so it must be _Large_mode_engaged() + _STL_INTERNAL_CHECK(_Right_data._Large_mode_engaged()); _My_data._Alloc_proxy(_GET_PROXY_ALLOCATOR(_Alty, _Getal())); // throws, hereafter nothrow in this block _Take_contents(_Right); const auto _Ptr = _Unfancy(_My_data._Bx._Ptr); @@ -2870,14 +2883,11 @@ public: _Xlen_string(); } - auto _New_capacity = _Calculate_growth(_New_size, _BUF_SIZE - 1, _Max); - auto&& _Alproxy = _GET_PROXY_ALLOCATOR(_Alty, _Getal()); + size_type _New_capacity = _Calculate_growth(_New_size, _Small_string_capacity, _Max); + auto&& _Alproxy = _GET_PROXY_ALLOCATOR(_Alty, _Getal()); _Container_proxy_ptr<_Alty> _Proxy(_Alproxy, _My_data); // throws - ++_New_capacity; - const pointer _Fancyptr = _Allocate_at_least_helper(_Getal(), _New_capacity); // throws - --_New_capacity; + const pointer _Fancyptr = _Allocate_for_capacity(_Getal(), _New_capacity); // throws // nothrow hereafter - _Start_element_lifetimes(_Unfancy(_Fancyptr), _New_capacity + 1); _Construct_in_place(_My_data._Bx._Ptr, _Fancyptr); _My_data._Mysize = _New_size; _My_data._Myres = _New_capacity; @@ -2914,24 +2924,25 @@ public: _Tidy_init(); } - _NODISCARD bool _Move_assign_from_buffer(_Elem* const _Right, const size_type _Size, const size_type _Res) { - // Move assign from a buffer, used exclusively by basic_stringbuf; returns _Large_string_engaged() + _NODISCARD bool _Move_assign_from_buffer( + _Elem* const _Right, const size_type _Size, const size_type _Actual_allocation_size) { + // Move assign from a buffer, used exclusively by basic_stringbuf; returns _Large_mode_engaged() auto& _My_data = _Mypair._Myval2; - _STL_INTERNAL_CHECK(!_My_data._Large_string_engaged() && _My_data._Mysize == 0); - _STL_INTERNAL_CHECK(_Size < _Res); // So there is room for null terminator + _STL_INTERNAL_CHECK(!_My_data._Large_mode_engaged() && _My_data._Mysize == 0); + _STL_INTERNAL_CHECK(_Size < _Actual_allocation_size); // So there is room for null terminator _Traits::assign(_Right[_Size], _Elem()); - const bool _Is_large = _Res > _BUF_SIZE; // Note: _BUF_SIZE because _Res now includes the null terminator + const bool _Is_large = _Actual_allocation_size >= _Least_allocation_size; if (_Is_large) { _ASAN_STRING_REMOVE(*this); _Construct_in_place(_My_data._Bx._Ptr, _Refancy(_Right)); _My_data._Mysize = _Size; - _My_data._Myres = _Res - 1; + _My_data._Myres = _Actual_allocation_size - 1; _ASAN_STRING_CREATE(*this); } else { - _Traits::copy(_My_data._Bx._Buf, _Right, _Res); + _Traits::copy(_My_data._Bx._Buf, _Right, _Actual_allocation_size); _My_data._Mysize = _Size; - _My_data._Myres = _BUF_SIZE - 1; + _My_data._Myres = _Small_string_capacity; } return _Is_large; @@ -2941,7 +2952,7 @@ public: struct _Released_buffer { pointer _Ptr; size_type _Size; - size_type _Res; + size_type _Actual_allocation_size; }; _NODISCARD _Released_buffer _Release_to_buffer(_Alloc& _Al) { @@ -2950,15 +2961,15 @@ public: auto& _My_data = _Mypair._Myval2; _Result._Size = _My_data._Mysize; _ASAN_STRING_REMOVE(*this); - if (_My_data._Large_string_engaged()) { - _Result._Ptr = _My_data._Bx._Ptr; - _Result._Res = _My_data._Myres + 1; + if (_My_data._Large_mode_engaged()) { + _Result._Ptr = _My_data._Bx._Ptr; + _Result._Actual_allocation_size = _My_data._Myres + 1; } else { - // use _BUF_SIZE + 1 to avoid SSO, if the buffer is assigned back - size_type _Allocated = _BUF_SIZE + 1; + // use _Least_allocation_size to avoid small mode, if the buffer is assigned back + size_type _Allocated = _Least_allocation_size; _Result._Ptr = _Allocate_at_least_helper(_Al, _Allocated); _Traits::copy(_Unfancy(_Result._Ptr), _My_data._Bx._Buf, _BUF_SIZE); - _Result._Res = _Allocated; + _Result._Actual_allocation_size = _Allocated; } _My_data._Orphan_all(); _Tidy_init(); @@ -3025,7 +3036,7 @@ private: #endif // _HAS_CXX20 { #if _ITERATOR_DEBUG_LEVEL != 0 - if (_Right_data._Large_string_engaged()) { + if (_Right_data._Large_mode_engaged()) { // take ownership of _Right's iterators along with its buffer _Swap_proxy_and_iterators(_Right); } else { @@ -3040,7 +3051,7 @@ private: } #endif // !defined(_INSERT_STRING_ANNOTATION) - if (_Right_data._Large_string_engaged()) { // steal buffer + if (_Right_data._Large_mode_engaged()) { // steal buffer _Construct_in_place(_My_data._Bx._Ptr, _Right_data._Bx._Ptr); _Right_data._Bx._Ptr = nullptr; _Swap_proxy_and_iterators(_Right); @@ -3064,7 +3075,7 @@ private: const auto _Result_size = _Right_data._Clamp_suffix_size(_Roff, _Size_max); const auto _Right_ptr = _Right_data._Myptr(); auto& _Al = _Getal(); - if (_Allocators_equal(_Al, _Right._Getal()) && _Result_size >= _BUF_SIZE) { + if (_Allocators_equal(_Al, _Right._Getal()) && _Result_size > _Small_string_capacity) { if (_Roff != 0) { _Traits::move(_Right_ptr, _Right_ptr + _Roff, _Result_size); } @@ -3137,31 +3148,6 @@ public: static constexpr auto npos{static_cast(-1)}; -private: - _CONSTEXPR20 void _Copy_assign_val_from_small(const basic_string& _Right) { - // TRANSITION, VSO-761321; inline into only caller when that's fixed - _Tidy_deallocate(); -#if !defined(_INSERT_STRING_ANNOTATION) - if constexpr (_Can_memcpy_val) { -#if _HAS_CXX20 - if (!_STD is_constant_evaluated()) -#endif // _HAS_CXX20 - { - _Memcpy_val_from(_Right); - return; - } - } -#endif // !defined(_INSERT_STRING_ANNOTATION) - - auto& _My_data = _Mypair._Myval2; - auto& _Right_data = _Right._Mypair._Myval2; - - _Traits::copy(_My_data._Bx._Buf, _Right_data._Bx._Buf, _Right_data._Mysize + 1); - _My_data._Mysize = _Right_data._Mysize; - _My_data._Myres = _Right_data._Myres; - } - -public: _CONSTEXPR20 basic_string& operator=(const basic_string& _Right) { if (this == _STD addressof(_Right)) { return *this; @@ -3175,23 +3161,23 @@ public: auto&& _Right_alproxy = _GET_PROXY_ALLOCATOR(_Alty, _Right_al); _Container_proxy_ptr<_Alty> _New_proxy(_Right_alproxy, _Leave_proxy_unbound{}); // throws - if (_Right._Mypair._Myval2._Large_string_engaged()) { - const auto _New_size = _Right._Mypair._Myval2._Mysize; - auto _New_capacity = _Calculate_growth(_New_size, 0, _Right.max_size()); + const size_type _Right_size = _Right._Mypair._Myval2._Mysize; + const _Elem* const _Right_ptr = _Right._Mypair._Myval2._Myptr(); + if (_Right_size > _Small_string_capacity) { + size_type _New_capacity = _Calculate_growth(_Right_size, _Small_string_capacity, _Right.max_size()); auto _Right_al_non_const = _Right_al; - ++_New_capacity; - const auto _New_ptr = _Allocate_at_least_helper(_Right_al_non_const, _New_capacity); // throws - --_New_capacity; - - _Start_element_lifetimes(_Unfancy(_New_ptr), _New_capacity + 1); + const pointer _New_ptr = _Allocate_for_capacity(_Right_al_non_const, _New_capacity); // throws - _Traits::copy(_Unfancy(_New_ptr), _Unfancy(_Right._Mypair._Myval2._Bx._Ptr), _New_size + 1); + _Traits::copy(_Unfancy(_New_ptr), _Right_ptr, _Right_size + 1); _Tidy_deallocate(); _Mypair._Myval2._Bx._Ptr = _New_ptr; - _Mypair._Myval2._Mysize = _New_size; + _Mypair._Myval2._Mysize = _Right_size; _Mypair._Myval2._Myres = _New_capacity; } else { - _Copy_assign_val_from_small(_Right); + _Tidy_deallocate(); + _Traits::copy(_Mypair._Myval2._Bx._Buf, _Right_ptr, _Right_size + 1); + _Mypair._Myval2._Mysize = _Right_size; + _Mypair._Myval2._Myres = _Small_string_capacity; } _Pocca(_Al, _Right_al); @@ -3997,26 +3983,25 @@ public: _CONSTEXPR20 void shrink_to_fit() { // reduce capacity auto& _My_data = _Mypair._Myval2; - if (!_My_data._Large_string_engaged()) { // can't shrink from small mode + if (!_My_data._Large_mode_engaged()) { // can't shrink from small mode return; } - if (_My_data._Mysize < _BUF_SIZE) { + if (_My_data._Mysize <= _Small_string_capacity) { _Become_small(); return; } - const size_type _Target_capacity = (_STD min)(_My_data._Mysize | _ALLOC_MASK, max_size()); + size_type _Target_capacity = (_STD min)(_My_data._Mysize | _Alloc_mask, max_size()); if (_Target_capacity < _My_data._Myres) { // worth shrinking, do it - auto& _Al = _Getal(); - const pointer _New_ptr = _Al.allocate(_Target_capacity + 1); // throws + auto& _Al = _Getal(); + const pointer _New_ptr = + _Allocate_for_capacity<_Allocation_policy::_Exactly>(_Al, _Target_capacity); // throws _ASAN_STRING_REMOVE(*this); - _Start_element_lifetimes(_Unfancy(_New_ptr), _Target_capacity + 1); - _My_data._Orphan_all(); _Traits::copy(_Unfancy(_New_ptr), _Unfancy(_My_data._Bx._Ptr), _My_data._Mysize + 1); - _Al.deallocate(_My_data._Bx._Ptr, _My_data._Myres + 1); + _Deallocate_for_capacity(_Al, _My_data._Bx._Ptr, _My_data._Myres); _My_data._Bx._Ptr = _New_ptr; _My_data._Myres = _Target_capacity; _ASAN_STRING_CREATE(*this); @@ -4202,7 +4187,7 @@ public: } _CXX20_DEPRECATE_STRING_RESERVE_WITHOUT_ARGUMENT void reserve() { - if (_Mypair._Myval2._Mysize == 0 && _Mypair._Myval2._Large_string_engaged()) { + if (_Mypair._Myval2._Mysize == 0 && _Mypair._Myval2._Large_mode_engaged()) { _Become_small(); } } @@ -4227,13 +4212,13 @@ public: return; } - if (_BUF_SIZE > _Newcap && _Mypair._Myval2._Large_string_engaged()) { + if (_Newcap <= _Small_string_capacity && _Mypair._Myval2._Large_mode_engaged()) { // deallocate everything; switch back to "small" mode _Become_small(); return; } - // ignore requests to reserve to [_BUF_SIZE, _Myres) + // ignore requests to reserve to [_Small_string_capacity + 1, _Myres) } #endif // _HAS_CXX20 @@ -4277,8 +4262,8 @@ public: auto& _My_data = _Mypair._Myval2; auto& _Right_data = _Right._Mypair._Myval2; - const bool _My_large = _My_data._Large_string_engaged(); - const bool _Right_large = _Right_data._Large_string_engaged(); + const bool _My_large = _My_data._Large_mode_engaged(); + const bool _Right_large = _Right_data._Large_mode_engaged(); #if !defined(_INSERT_STRING_ANNOTATION) if constexpr (_Can_memcpy_val) { @@ -4325,11 +4310,11 @@ public: auto& _My_data = _Mypair._Myval2; auto& _Right_data = _Right._Mypair._Myval2; - if (!_My_data._Large_string_engaged()) { + if (!_My_data._Large_mode_engaged()) { _My_data._Orphan_all(); } - if (!_Right_data._Large_string_engaged()) { + if (!_Right_data._Large_mode_engaged()) { _Right_data._Orphan_all(); } @@ -4724,7 +4709,7 @@ public: private: _NODISCARD static _CONSTEXPR20 size_type _Calculate_growth( const size_type _Requested, const size_type _Old, const size_type _Max) noexcept { - const size_type _Masked = _Requested | _ALLOC_MASK; + const size_type _Masked = _Requested | _Alloc_mask; if (_Masked > _Max) { // the mask overflows, settle for max_size() return _Max; } @@ -4751,18 +4736,15 @@ private: const size_type _Old_capacity = _Mypair._Myval2._Myres; size_type _New_capacity = _Calculate_growth(_New_size); auto& _Al = _Getal(); - ++_New_capacity; - const pointer _New_ptr = _Allocate_at_least_helper(_Al, _New_capacity); // throws - --_New_capacity; + const pointer _New_ptr = _Allocate_for_capacity(_Al, _New_capacity); // throws - _Start_element_lifetimes(_Unfancy(_New_ptr), _New_capacity + 1); _Mypair._Myval2._Orphan_all(); _ASAN_STRING_REMOVE(*this); _Mypair._Myval2._Mysize = _New_size; _Mypair._Myval2._Myres = _New_capacity; _Fn(_Unfancy(_New_ptr), _New_size, _Args...); - if (_BUF_SIZE <= _Old_capacity) { - _Al.deallocate(_Mypair._Myval2._Bx._Ptr, _Old_capacity + 1); + if (_Old_capacity > _Small_string_capacity) { + _Deallocate_for_capacity(_Al, _Mypair._Myval2._Bx._Ptr, _Old_capacity); _Mypair._Myval2._Bx._Ptr = _New_ptr; } else { _Construct_in_place(_Mypair._Myval2._Bx._Ptr, _New_ptr); @@ -4786,20 +4768,17 @@ private: const size_type _Old_capacity = _My_data._Myres; size_type _New_capacity = _Calculate_growth(_New_size); auto& _Al = _Getal(); - ++_New_capacity; - const pointer _New_ptr = _Allocate_at_least_helper(_Al, _New_capacity); // throws - --_New_capacity; + const pointer _New_ptr = _Allocate_for_capacity(_Al, _New_capacity); // throws - _Start_element_lifetimes(_Unfancy(_New_ptr), _New_capacity + 1); _My_data._Orphan_all(); _ASAN_STRING_REMOVE(*this); _My_data._Mysize = _New_size; _My_data._Myres = _New_capacity; _Elem* const _Raw_new = _Unfancy(_New_ptr); - if (_BUF_SIZE <= _Old_capacity) { + if (_Old_capacity > _Small_string_capacity) { const pointer _Old_ptr = _My_data._Bx._Ptr; _Fn(_Raw_new, _Unfancy(_Old_ptr), _Old_size, _Args...); - _Al.deallocate(_Old_ptr, _Old_capacity + 1); + _Deallocate_for_capacity(_Al, _Old_ptr, _Old_capacity); _My_data._Bx._Ptr = _New_ptr; } else { _Fn(_Raw_new, _My_data._Bx._Buf, _Old_size, _Args...); @@ -4813,8 +4792,8 @@ private: _CONSTEXPR20 void _Become_small() { // release any held storage and return to small string mode auto& _My_data = _Mypair._Myval2; - _STL_INTERNAL_CHECK(_My_data._Large_string_engaged()); - _STL_INTERNAL_CHECK(_My_data._Mysize < _BUF_SIZE); + _STL_INTERNAL_CHECK(_My_data._Large_mode_engaged()); + _STL_INTERNAL_CHECK(_My_data._Mysize <= _Small_string_capacity); _My_data._Orphan_all(); _ASAN_STRING_REMOVE(*this); @@ -4823,8 +4802,8 @@ private: _Destroy_in_place(_My_data._Bx._Ptr); _My_data._Activate_SSO_buffer(); _Traits::copy(_My_data._Bx._Buf, _Unfancy(_Ptr), _My_data._Mysize + 1); - _Al.deallocate(_Ptr, _My_data._Myres + 1); - _My_data._Myres = _BUF_SIZE - 1; + _Deallocate_for_capacity(_Al, _Ptr, _My_data._Myres); + _My_data._Myres = _Small_string_capacity; } _CONSTEXPR20 void _Eos(const size_type _New_size) noexcept { // set new length and null terminator @@ -4836,7 +4815,7 @@ private: // initialize basic_string data members auto& _My_data = _Mypair._Myval2; _My_data._Mysize = 0; - _My_data._Myres = _BUF_SIZE - 1; + _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 @@ -4846,17 +4825,17 @@ private: _CONSTEXPR20 void _Tidy_deallocate() noexcept { // initialize buffer, deallocating any storage auto& _My_data = _Mypair._Myval2; _My_data._Orphan_all(); - if (_My_data._Large_string_engaged()) { + 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(); - _Al.deallocate(_Ptr, _My_data._Myres + 1); + _Deallocate_for_capacity(_Al, _Ptr, _My_data._Myres); } _My_data._Mysize = 0; - _My_data._Myres = _BUF_SIZE - 1; + _My_data._Myres = _Small_string_capacity; // 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()); } diff --git a/tests/std/tests/Dev09_181509_tr1_inf_loop_uniform_int_ull/env.lst b/tests/std/tests/Dev09_181509_tr1_inf_loop_uniform_int_ull/env.lst index 2de7aab2959..19f025bd0e6 100644 --- a/tests/std/tests/Dev09_181509_tr1_inf_loop_uniform_int_ull/env.lst +++ b/tests/std/tests/Dev09_181509_tr1_inf_loop_uniform_int_ull/env.lst @@ -1,4 +1,4 @@ # Copyright (c) Microsoft Corporation. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -RUNALL_INCLUDE ..\usual_17_matrix.lst +RUNALL_INCLUDE ..\usual_matrix.lst diff --git a/tests/std/tests/Dev09_181509_tr1_inf_loop_uniform_int_ull/test.cpp b/tests/std/tests/Dev09_181509_tr1_inf_loop_uniform_int_ull/test.cpp index ce2e1988e75..134b48978ca 100644 --- a/tests/std/tests/Dev09_181509_tr1_inf_loop_uniform_int_ull/test.cpp +++ b/tests/std/tests/Dev09_181509_tr1_inf_loop_uniform_int_ull/test.cpp @@ -1,16 +1,13 @@ // Copyright (c) Microsoft Corporation. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#include #include #include -#include #include #include #include using namespace std; -using namespace std::execution; constexpr auto int16_min = numeric_limits::min(); constexpr auto int16_max = numeric_limits::max(); @@ -121,13 +118,13 @@ class Wacky { }; int main() { -#ifndef _M_CEE // TRANSITION, VSO-1659695 vector tests; add_tests(tests); add_tests(tests); add_tests(tests); - for_each(par, tests.begin(), tests.end(), [](fp_t fn) { fn(); }); -#endif // _M_CEE + for (const auto& fn : tests) { + fn(); + } } diff --git a/tests/std/tests/VSO_0000000_allocator_propagation/test.cpp b/tests/std/tests/VSO_0000000_allocator_propagation/test.cpp index fb2a8b15466..d697fc595a2 100644 --- a/tests/std/tests/VSO_0000000_allocator_propagation/test.cpp +++ b/tests/std/tests/VSO_0000000_allocator_propagation/test.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -635,6 +636,31 @@ void test_string_copy_assign(const size_t id1, const size_t id2, const size_t id assert(dst.get_allocator().id() == id3); } +void test_string_copy_assign_pocca_sso() { + // GH-3862 fixed a bug where the POCCA codepath in basic_string's copy assignment operator mishandled + // the scenario where the string on the right hand side has a large capacity but a small size - so while + // the RHS has dynamically allocated memory, the LHS should activate the Small String Optimization. + + using Al = CopyAlloc; + using Str = basic_string, Al>; + + Str left{Al{11}}; + Str right{Al{22}}; + + left.assign(5, 'a'); + + right.assign(1729, 'x'); + right.assign(7, 'y'); + + assert(left == "aaaaa"); + assert(right == "yyyyyyy"); + + left = right; + + assert(left == "yyyyyyy"); + assert(right == "yyyyyyy"); +} + void test_string_move_ctor() { basic_string, StationaryAlloc> src( {5, 10, 20, 30}, StationaryAlloc(11)); @@ -770,6 +796,8 @@ void test_string() { test_string_copy_assign>(11, 22, 11); // POCCA, non-equal allocators test_string_copy_assign>(11, 22, 11); // POCCA, always-equal allocators + test_string_copy_assign_pocca_sso(); + test_string_move_ctor(); test_string_move_alloc_ctor(11, 11); // equal allocators