diff --git a/libcxx/include/string b/libcxx/include/string index e2968621bb314..19f977ddf9f11 100644 --- a/libcxx/include/string +++ b/libcxx/include/string @@ -1261,12 +1261,20 @@ public: _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 size_type length() const _NOEXCEPT { return size(); } _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 size_type max_size() const _NOEXCEPT { - size_type __m = __alloc_traits::max_size(__alloc_); - if (__m <= std::numeric_limits::max() / 2) { - return __m - __alignment; + if (size_type __m = __alloc_traits::max_size(__alloc_); __m <= std::numeric_limits::max() / 2) { + size_type __res = __m - __alignment; + + // When the __endian_factor == 2, our string representation assumes that the capacity + // (including the null terminator) is always even, so we have to make sure the lowest bit isn't set when the + // string grows to max_size() + if (__endian_factor == 2) + __res &= ~size_type(1); + + // We have to allocate space for the null terminator, but max_size() doesn't include it. + return __res - 1; } else { bool __uses_lsb = __endian_factor == 2; - return __uses_lsb ? __m - __alignment : (__m / 2) - __alignment; + return __uses_lsb ? __m - __alignment - 1 : (__m / 2) - __alignment - 1; } } @@ -2607,11 +2615,11 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 void basic_string<_CharT, _Traits, _Allocator>::__ size_type __n_add, const value_type* __p_new_stuff) { size_type __ms = max_size(); - if (__delta_cap > __ms - __old_cap - 1) - this->__throw_length_error(); + if (__delta_cap > __ms - __old_cap) + __throw_length_error(); pointer __old_p = __get_pointer(); size_type __cap = - __old_cap < __ms / 2 - __alignment ? __recommend(std::max(__old_cap + __delta_cap, 2 * __old_cap)) : __ms - 1; + __old_cap < __ms / 2 - __alignment ? __recommend(std::max(__old_cap + __delta_cap, 2 * __old_cap)) : __ms; __annotate_delete(); auto __guard = std::__make_scope_guard(__annotate_new_size(*this)); auto __allocation = std::__allocate_at_least(__alloc_, __cap + 1); @@ -2654,7 +2662,7 @@ _LIBCPP_DEPRECATED_("use __grow_by_without_replace") basic_string<_CharT, _Trait this->__throw_length_error(); pointer __old_p = __get_pointer(); size_type __cap = - __old_cap < __ms / 2 - __alignment ? __recommend(std::max(__old_cap + __delta_cap, 2 * __old_cap)) : __ms - 1; + __old_cap < __ms / 2 - __alignment ? __recommend(std::max(__old_cap + __delta_cap, 2 * __old_cap)) : __ms; auto __allocation = std::__allocate_at_least(__alloc_, __cap + 1); pointer __p = __allocation.ptr; __begin_lifetime(__p, __allocation.count); diff --git a/libcxx/test/libcxx/strings/basic.string/string.capacity/max_size.pass.cpp b/libcxx/test/libcxx/strings/basic.string/string.capacity/max_size.pass.cpp index 726570beb6d1a..7b3f81e21d8a8 100644 --- a/libcxx/test/libcxx/strings/basic.string/string.capacity/max_size.pass.cpp +++ b/libcxx/test/libcxx/strings/basic.string/string.capacity/max_size.pass.cpp @@ -6,6 +6,8 @@ // //===----------------------------------------------------------------------===// +// XFAIL: FROZEN-CXX03-HEADERS-FIXME + // // This test ensures that the correct max_size() is returned depending on the platform. @@ -23,44 +25,45 @@ static const std::size_t alignment = 8; template TEST_CONSTEXPR_CXX20 void full_size() { std::string str; - assert(str.max_size() == std::numeric_limits::max() - alignment); + assert(str.max_size() == std::numeric_limits::max() - alignment - 1); #ifndef TEST_HAS_NO_CHAR8_T std::u8string u8str; - assert(u8str.max_size() == std::numeric_limits::max() - alignment); + assert(u8str.max_size() == std::numeric_limits::max() - alignment - 1); #endif #ifndef TEST_HAS_NO_WIDE_CHARACTERS std::wstring wstr; - assert(wstr.max_size() == std::numeric_limits::max() / sizeof(wchar_t) - alignment); + assert(wstr.max_size() == + ((std::numeric_limits::max() / sizeof(wchar_t) - alignment) & ~std::size_t(1)) - 1); #endif std::u16string u16str; std::u32string u32str; - assert(u16str.max_size() == std::numeric_limits::max() / 2 - alignment); - assert(u32str.max_size() == std::numeric_limits::max() / 4 - alignment); + assert(u16str.max_size() == ((std::numeric_limits::max() / 2 - alignment) & ~std::size_t(1)) - 1); + assert(u32str.max_size() == ((std::numeric_limits::max() / 4 - alignment) & ~std::size_t(1)) - 1); } template TEST_CONSTEXPR_CXX20 void half_size() { std::string str; - assert(str.max_size() == std::numeric_limits::max() / 2 - alignment); + assert(str.max_size() == std::numeric_limits::max() / 2 - alignment - 1); #ifndef TEST_HAS_NO_CHAR8_T std::u8string u8str; - assert(u8str.max_size() == std::numeric_limits::max() / 2 - alignment); + assert(u8str.max_size() == std::numeric_limits::max() / 2 - alignment - 1); #endif #ifndef TEST_HAS_NO_WIDE_CHARACTERS std::wstring wstr; assert(wstr.max_size() == - std::numeric_limits::max() / std::max(2ul, sizeof(wchar_t)) - alignment); + std::numeric_limits::max() / std::max(2ul, sizeof(wchar_t)) - alignment - 1); #endif std::u16string u16str; std::u32string u32str; - assert(u16str.max_size() == std::numeric_limits::max() / 2 - alignment); - assert(u32str.max_size() == std::numeric_limits::max() / 4 - alignment); + assert(u16str.max_size() == std::numeric_limits::max() / 2 - alignment - 1); + assert(u32str.max_size() == std::numeric_limits::max() / 4 - alignment - 1); } TEST_CONSTEXPR_CXX20 bool test() { diff --git a/libcxx/test/std/strings/basic.string/string.capacity/max_size.pass.cpp b/libcxx/test/std/strings/basic.string/string.capacity/max_size.pass.cpp index b9ffffc0993af..f68f9e9d5fe29 100644 --- a/libcxx/test/std/strings/basic.string/string.capacity/max_size.pass.cpp +++ b/libcxx/test/std/strings/basic.string/string.capacity/max_size.pass.cpp @@ -8,6 +8,8 @@ // UNSUPPORTED: no-exceptions +// XFAIL: FROZEN-CXX03-HEADERS-FIXME + // After changing the alignment of the allocated pointer from 16 to 8, the exception // thrown is no longer `bad_alloc` but instead length_error on systems using new // headers but a dylib that doesn't contain 04ce0ba. @@ -53,7 +55,7 @@ TEST_CONSTEXPR_CXX20 void test_resize_max_size(const S& s) { } catch (const std::bad_alloc&) { return; } - assert(s.size() == sz); + assert(s2.size() == sz); } template @@ -91,8 +93,16 @@ TEST_CONSTEXPR_CXX20 bool test() { test_string(); #if TEST_STD_VER >= 11 test_string, min_allocator > >(); + test_string, tiny_size_allocator<64, char> > >(); #endif + { // Test resizing where we can assume that the allocation succeeds + std::basic_string, tiny_size_allocator<32, char> > str; + auto max_size = str.max_size(); + str.resize(max_size); + assert(str.size() == max_size); + } + return true; } diff --git a/libcxx/test/std/strings/basic.string/string.capacity/resize_size.pass.cpp b/libcxx/test/std/strings/basic.string/string.capacity/resize_size.pass.cpp index 7cf4b7ca3b6ef..e23b6cf40691f 100644 --- a/libcxx/test/std/strings/basic.string/string.capacity/resize_size.pass.cpp +++ b/libcxx/test/std/strings/basic.string/string.capacity/resize_size.pass.cpp @@ -14,56 +14,73 @@ #include #include -#include "test_macros.h" -#include "min_allocator.h" #include "asan_testing.h" +#include "make_string.h" +#include "min_allocator.h" +#include "test_macros.h" template TEST_CONSTEXPR_CXX20 void test(S s, typename S::size_type n, S expected) { - if (n <= s.max_size()) { - s.resize(n); - LIBCPP_ASSERT(s.__invariants()); - assert(s == expected); - LIBCPP_ASSERT(is_string_asan_correct(s)); + s.resize(n); + LIBCPP_ASSERT(s.__invariants()); + assert(s == expected); + LIBCPP_ASSERT(is_string_asan_correct(s)); +} + +template +TEST_CONSTEXPR_CXX20 void test_string() { + { + using string_type = std::basic_string, Alloc>; + test(string_type(), 0, string_type()); + test(string_type(), 1, string_type(1, '\0')); + test(string_type(), 10, string_type(10, '\0')); + test(string_type(), 100, string_type(100, '\0')); + test(string_type(MAKE_CSTRING(CharT, "12345")), 0, string_type()); + test(string_type(MAKE_CSTRING(CharT, "12345")), 2, string_type(MAKE_CSTRING(CharT, "12"))); + test(string_type(MAKE_CSTRING(CharT, "12345")), 5, string_type(MAKE_CSTRING(CharT, "12345"))); + test(string_type(MAKE_CSTRING(CharT, "12345")), + 15, + string_type(MAKE_CSTRING(CharT, "12345\0\0\0\0\0\0\0\0\0\0"), 15)); + test(string_type(MAKE_CSTRING(CharT, "12345678901234567890123456789012345678901234567890")), 0, string_type()); + test(string_type(MAKE_CSTRING(CharT, "12345678901234567890123456789012345678901234567890")), + 10, + string_type(MAKE_CSTRING(CharT, "1234567890"))); + test(string_type(MAKE_CSTRING(CharT, "12345678901234567890123456789012345678901234567890")), + 50, + string_type(MAKE_CSTRING(CharT, "12345678901234567890123456789012345678901234567890"))); + test( + string_type(MAKE_CSTRING(CharT, "12345678901234567890123456789012345678901234567890")), + 60, + string_type(MAKE_CSTRING(CharT, "12345678901234567890123456789012345678901234567890\0\0\0\0\0\0\0\0\0\0"), 60)); } + #ifndef TEST_HAS_NO_EXCEPTIONS - else if (!TEST_IS_CONSTANT_EVALUATED) { + if (!TEST_IS_CONSTANT_EVALUATED) { + std::basic_string, Alloc> str; try { - s.resize(n); + str.resize(std::string::npos); assert(false); - } catch (std::length_error&) { - assert(n > s.max_size()); + } catch (const std::length_error&) { } } #endif -} -template -TEST_CONSTEXPR_CXX20 void test_string() { - test(S(), 0, S()); - test(S(), 1, S(1, '\0')); - test(S(), 10, S(10, '\0')); - test(S(), 100, S(100, '\0')); - test(S("12345"), 0, S()); - test(S("12345"), 2, S("12")); - test(S("12345"), 5, S("12345")); - test(S("12345"), 15, S("12345\0\0\0\0\0\0\0\0\0\0", 15)); - test(S("12345678901234567890123456789012345678901234567890"), 0, S()); - test(S("12345678901234567890123456789012345678901234567890"), 10, S("1234567890")); - test(S("12345678901234567890123456789012345678901234567890"), - 50, - S("12345678901234567890123456789012345678901234567890")); - test(S("12345678901234567890123456789012345678901234567890"), - 60, - S("12345678901234567890123456789012345678901234567890\0\0\0\0\0\0\0\0\0\0", 60)); - test(S(), S::npos, S("not going to happen")); + { // check that string can grow to max_size() + std::basic_string, tiny_size_allocator<32, CharT> > str; + str.resize(str.max_size()); + assert(str.size() == str.max_size()); + } } TEST_CONSTEXPR_CXX20 bool test() { - test_string(); + test_string >(); #if TEST_STD_VER >= 11 - test_string, min_allocator>>(); - test_string, safe_allocator>>(); + test_string>(); + test_string>(); +#endif + +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_string >(); #endif return true; diff --git a/libcxx/test/std/strings/basic.string/string.modifiers/string_append/pointer_size.pass.cpp b/libcxx/test/std/strings/basic.string/string.modifiers/string_append/pointer_size.pass.cpp index 41d4f1114c05b..425a481b71639 100644 --- a/libcxx/test/std/strings/basic.string/string.modifiers/string_append/pointer_size.pass.cpp +++ b/libcxx/test/std/strings/basic.string/string.modifiers/string_append/pointer_size.pass.cpp @@ -6,6 +6,8 @@ // //===----------------------------------------------------------------------===// +// XFAIL: FROZEN-CXX03-HEADERS-FIXME + // // basic_string& @@ -87,6 +89,16 @@ TEST_CONSTEXPR_CXX20 bool test() { assert(s_long == "Lorem ipsum dolor sit amet, consectetur/Lorem ipsum dolor sit amet, consectetur/"); } + { // check that growing to max_size() works + using string_type = std::basic_string, tiny_size_allocator<29, char> >; + string_type str; + auto max_size = str.max_size(); + str.resize(max_size / 2 + max_size % 2); + str.append(str.c_str(), max_size / 2); + assert(str.capacity() >= str.size()); + assert(str.size() == str.max_size()); + } + return true; } diff --git a/libcxx/test/std/strings/basic.string/string.nonmembers/string_op+/char_string.pass.cpp b/libcxx/test/std/strings/basic.string/string.nonmembers/string_op+/char_string.pass.cpp index 68a250c593318..333787a0a0766 100644 --- a/libcxx/test/std/strings/basic.string/string.nonmembers/string_op+/char_string.pass.cpp +++ b/libcxx/test/std/strings/basic.string/string.nonmembers/string_op+/char_string.pass.cpp @@ -51,6 +51,17 @@ TEST_CONSTEXPR_CXX20 bool test() { test_string, safe_allocator > >(); #endif + { // check that growing to max_size() works + using string_type = std::basic_string, tiny_size_allocator<29, char> >; + string_type str; + str.resize(str.max_size() - 1); + string_type result = 'a' + str; + + assert(result.size() == result.max_size()); + assert(result.front() == 'a'); + assert(result.capacity() <= result.get_allocator().max_size()); + } + return true; } diff --git a/libcxx/test/std/strings/basic.string/string.nonmembers/string_op+/pointer_string.pass.cpp b/libcxx/test/std/strings/basic.string/string.nonmembers/string_op+/pointer_string.pass.cpp index 434f7292c149c..400b5165036ed 100644 --- a/libcxx/test/std/strings/basic.string/string.nonmembers/string_op+/pointer_string.pass.cpp +++ b/libcxx/test/std/strings/basic.string/string.nonmembers/string_op+/pointer_string.pass.cpp @@ -61,6 +61,17 @@ TEST_CONSTEXPR_CXX20 bool test() { test_string, min_allocator>>(); #endif + { // check that growing to max_size() works + using string_type = std::basic_string, tiny_size_allocator<29, char> >; + string_type str; + str.resize(str.max_size() - 1); + string_type result = "a" + str; + + assert(result.size() == result.max_size()); + assert(result.front() == 'a'); + assert(result.capacity() <= result.get_allocator().max_size()); + } + return true; } diff --git a/libcxx/test/std/strings/basic.string/string.nonmembers/string_op+/string_char.pass.cpp b/libcxx/test/std/strings/basic.string/string.nonmembers/string_op+/string_char.pass.cpp index 5c3989ae304e9..20ac1fb4cc723 100644 --- a/libcxx/test/std/strings/basic.string/string.nonmembers/string_op+/string_char.pass.cpp +++ b/libcxx/test/std/strings/basic.string/string.nonmembers/string_op+/string_char.pass.cpp @@ -51,6 +51,17 @@ TEST_CONSTEXPR_CXX20 bool test() { test_string, safe_allocator > >(); #endif + { // check that growing to max_size() works + using string_type = std::basic_string, tiny_size_allocator<29, char> >; + string_type str; + str.resize(str.max_size() - 1); + string_type result = str + 'a'; + + assert(result.size() == result.max_size()); + assert(result.back() == 'a'); + assert(result.capacity() <= result.get_allocator().max_size()); + } + return true; } diff --git a/libcxx/test/std/strings/basic.string/string.nonmembers/string_op+/string_pointer.pass.cpp b/libcxx/test/std/strings/basic.string/string.nonmembers/string_op+/string_pointer.pass.cpp index 589c737348a11..215a98223814b 100644 --- a/libcxx/test/std/strings/basic.string/string.nonmembers/string_op+/string_pointer.pass.cpp +++ b/libcxx/test/std/strings/basic.string/string.nonmembers/string_op+/string_pointer.pass.cpp @@ -76,6 +76,17 @@ TEST_CONSTEXPR_CXX20 bool test() { test_string, safe_allocator > >(); #endif + { // check that growing to max_size() works + using string_type = std::basic_string, tiny_size_allocator<29, char> >; + string_type str; + str.resize(str.max_size() - 1); + string_type result = str + "a"; + + assert(result.size() == result.max_size()); + assert(result.back() == 'a'); + assert(result.capacity() <= result.get_allocator().max_size()); + } + return true; } diff --git a/libcxx/test/std/strings/basic.string/string.nonmembers/string_op+/string_string.pass.cpp b/libcxx/test/std/strings/basic.string/string.nonmembers/string_op+/string_string.pass.cpp index 2badb51753395..082b0007498f5 100644 --- a/libcxx/test/std/strings/basic.string/string.nonmembers/string_op+/string_string.pass.cpp +++ b/libcxx/test/std/strings/basic.string/string.nonmembers/string_op+/string_string.pass.cpp @@ -86,6 +86,20 @@ TEST_CONSTEXPR_CXX20 bool test() { test_string, safe_allocator>>(); #endif + { // check that growing to max_size() works + using string_type = std::basic_string, tiny_size_allocator<29, char> >; + string_type lhs; + lhs.resize(lhs.max_size() - 1); + + string_type rhs = "a"; + + string_type result = lhs + rhs; + + assert(result.size() == result.max_size()); + assert(result.back() == 'a'); + assert(result.capacity() <= result.get_allocator().max_size()); + } + return true; } diff --git a/libcxx/test/support/min_allocator.h b/libcxx/test/support/min_allocator.h index d3ee27a23bc89..0be7ee0554ca9 100644 --- a/libcxx/test/support/min_allocator.h +++ b/libcxx/test/support/min_allocator.h @@ -474,4 +474,32 @@ class safe_allocator { TEST_CONSTEXPR_CXX20 friend bool operator!=(safe_allocator x, safe_allocator y) { return !(x == y); } }; +template +struct tiny_size_allocator { + using value_type = T; + using size_type = unsigned; + + template + struct rebind { + using other = tiny_size_allocator; + }; + + tiny_size_allocator() = default; + + template + TEST_CONSTEXPR_CXX20 tiny_size_allocator(tiny_size_allocator) {} + + TEST_CONSTEXPR_CXX20 T* allocate(std::size_t n) { + assert(n <= MaxSize); + return std::allocator().allocate(n); + } + + TEST_CONSTEXPR_CXX20 void deallocate(T* ptr, std::size_t n) { std::allocator().deallocate(ptr, n); } + + TEST_CONSTEXPR_CXX20 size_type max_size() const { return MaxSize; } + + friend bool operator==(tiny_size_allocator, tiny_size_allocator) { return true; } + friend bool operator!=(tiny_size_allocator, tiny_size_allocator) { return false; } +}; + #endif // MIN_ALLOCATOR_H