Skip to content

Commit 0269ecd

Browse files
committed
[libc++] Fix basic_string not allowing max_size() elements to be stored
1 parent 59cbe2f commit 0269ecd

File tree

11 files changed

+170
-50
lines changed

11 files changed

+170
-50
lines changed

libcxx/include/string

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,10 +1302,10 @@ public:
13021302
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 size_type max_size() const _NOEXCEPT {
13031303
size_type __m = __alloc_traits::max_size(__alloc_);
13041304
if (__m <= std::numeric_limits<size_type>::max() / 2) {
1305-
return __m - __alignment;
1305+
return __m - __alignment - 1;
13061306
} else {
13071307
bool __uses_lsb = __endian_factor == 2;
1308-
return __uses_lsb ? __m - __alignment : (__m / 2) - __alignment;
1308+
return __uses_lsb ? __m - __alignment - 1 : (__m / 2) - __alignment - 1;
13091309
}
13101310
}
13111311

@@ -2672,11 +2672,11 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 void basic_string<_CharT, _Traits, _Allocator>::__
26722672
size_type __n_add,
26732673
const value_type* __p_new_stuff) {
26742674
size_type __ms = max_size();
2675-
if (__delta_cap > __ms - __old_cap - 1)
2675+
if (__delta_cap > __ms - __old_cap)
26762676
__throw_length_error();
26772677
pointer __old_p = __get_pointer();
26782678
size_type __cap =
2679-
__old_cap < __ms / 2 - __alignment ? __recommend(std::max(__old_cap + __delta_cap, 2 * __old_cap)) : __ms - 1;
2679+
__old_cap < __ms / 2 - __alignment ? __recommend(std::max(__old_cap + __delta_cap, 2 * __old_cap)) : __ms;
26802680
__annotate_delete();
26812681
auto __guard = std::__make_scope_guard(__annotate_new_size(*this));
26822682
auto __allocation = std::__allocate_at_least(__alloc_, __cap + 1);
@@ -2719,7 +2719,7 @@ _LIBCPP_DEPRECATED_("use __grow_by_without_replace") basic_string<_CharT, _Trait
27192719
__throw_length_error();
27202720
pointer __old_p = __get_pointer();
27212721
size_type __cap =
2722-
__old_cap < __ms / 2 - __alignment ? __recommend(std::max(__old_cap + __delta_cap, 2 * __old_cap)) : __ms - 1;
2722+
__old_cap < __ms / 2 - __alignment ? __recommend(std::max(__old_cap + __delta_cap, 2 * __old_cap)) : __ms;
27232723
auto __allocation = std::__allocate_at_least(__alloc_, __cap + 1);
27242724
pointer __p = __allocation.ptr;
27252725
__begin_lifetime(__p, __allocation.count);

libcxx/test/libcxx/strings/basic.string/string.capacity/max_size.pass.cpp

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,44 +23,44 @@ static const std::size_t alignment = 8;
2323
template <class = int>
2424
TEST_CONSTEXPR_CXX20 void full_size() {
2525
std::string str;
26-
assert(str.max_size() == std::numeric_limits<std::size_t>::max() - alignment);
26+
assert(str.max_size() == std::numeric_limits<std::size_t>::max() - alignment - 1);
2727

2828
#ifndef TEST_HAS_NO_CHAR8_T
2929
std::u8string u8str;
30-
assert(u8str.max_size() == std::numeric_limits<std::size_t>::max() - alignment);
30+
assert(u8str.max_size() == std::numeric_limits<std::size_t>::max() - alignment - 1);
3131
#endif
3232

3333
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
3434
std::wstring wstr;
35-
assert(wstr.max_size() == std::numeric_limits<std::size_t>::max() / sizeof(wchar_t) - alignment);
35+
assert(wstr.max_size() == std::numeric_limits<std::size_t>::max() / sizeof(wchar_t) - alignment - 1);
3636
#endif
3737

3838
std::u16string u16str;
3939
std::u32string u32str;
40-
assert(u16str.max_size() == std::numeric_limits<std::size_t>::max() / 2 - alignment);
41-
assert(u32str.max_size() == std::numeric_limits<std::size_t>::max() / 4 - alignment);
40+
assert(u16str.max_size() == std::numeric_limits<std::size_t>::max() / 2 - alignment - 1);
41+
assert(u32str.max_size() == std::numeric_limits<std::size_t>::max() / 4 - alignment - 1);
4242
}
4343

4444
template <class = int>
4545
TEST_CONSTEXPR_CXX20 void half_size() {
4646
std::string str;
47-
assert(str.max_size() == std::numeric_limits<std::size_t>::max() / 2 - alignment);
47+
assert(str.max_size() == std::numeric_limits<std::size_t>::max() / 2 - alignment - 1);
4848

4949
#ifndef TEST_HAS_NO_CHAR8_T
5050
std::u8string u8str;
51-
assert(u8str.max_size() == std::numeric_limits<std::size_t>::max() / 2 - alignment);
51+
assert(u8str.max_size() == std::numeric_limits<std::size_t>::max() / 2 - alignment - 1);
5252
#endif
5353

5454
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
5555
std::wstring wstr;
5656
assert(wstr.max_size() ==
57-
std::numeric_limits<std::size_t>::max() / std::max<size_t>(2ul, sizeof(wchar_t)) - alignment);
57+
std::numeric_limits<std::size_t>::max() / std::max<size_t>(2ul, sizeof(wchar_t)) - alignment - 1);
5858
#endif
5959

6060
std::u16string u16str;
6161
std::u32string u32str;
62-
assert(u16str.max_size() == std::numeric_limits<std::size_t>::max() / 2 - alignment);
63-
assert(u32str.max_size() == std::numeric_limits<std::size_t>::max() / 4 - alignment);
62+
assert(u16str.max_size() == std::numeric_limits<std::size_t>::max() / 2 - alignment - 1);
63+
assert(u32str.max_size() == std::numeric_limits<std::size_t>::max() / 4 - alignment - 1);
6464
}
6565

6666
TEST_CONSTEXPR_CXX20 bool test() {

libcxx/test/std/strings/basic.string/string.capacity/max_size.pass.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ TEST_CONSTEXPR_CXX20 void test_resize_max_size(const S& s) {
5353
} catch (const std::bad_alloc&) {
5454
return;
5555
}
56-
assert(s.size() == sz);
56+
assert(s2.size() == sz);
5757
}
5858

5959
template <class S>
@@ -91,8 +91,16 @@ TEST_CONSTEXPR_CXX20 bool test() {
9191
test_string<std::string>();
9292
#if TEST_STD_VER >= 11
9393
test_string<std::basic_string<char, std::char_traits<char>, min_allocator<char> > >();
94+
test_string<std::basic_string<char, std::char_traits<char>, tiny_size_allocator<64, char> > >();
9495
#endif
9596

97+
{ // Test resizing where we can assume that the allocation succeeds
98+
std::basic_string<char, std::char_traits<char>, tiny_size_allocator<32, char> > str;
99+
auto max_size = str.max_size();
100+
str.resize(max_size);
101+
assert(str.size() == max_size);
102+
}
103+
96104
return true;
97105
}
98106

libcxx/test/std/strings/basic.string/string.capacity/resize_size.pass.cpp

Lines changed: 51 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14,56 +14,73 @@
1414
#include <stdexcept>
1515
#include <cassert>
1616

17-
#include "test_macros.h"
18-
#include "min_allocator.h"
1917
#include "asan_testing.h"
18+
#include "make_string.h"
19+
#include "min_allocator.h"
20+
#include "test_macros.h"
2021

2122
template <class S>
2223
TEST_CONSTEXPR_CXX20 void test(S s, typename S::size_type n, S expected) {
23-
if (n <= s.max_size()) {
24-
s.resize(n);
25-
LIBCPP_ASSERT(s.__invariants());
26-
assert(s == expected);
27-
LIBCPP_ASSERT(is_string_asan_correct(s));
24+
s.resize(n);
25+
LIBCPP_ASSERT(s.__invariants());
26+
assert(s == expected);
27+
LIBCPP_ASSERT(is_string_asan_correct(s));
28+
}
29+
30+
template <class CharT, class Alloc>
31+
TEST_CONSTEXPR_CXX20 void test_string() {
32+
{
33+
using string_type = std::basic_string<CharT, std::char_traits<CharT>, Alloc>;
34+
test(string_type(), 0, string_type());
35+
test(string_type(), 1, string_type(1, '\0'));
36+
test(string_type(), 10, string_type(10, '\0'));
37+
test(string_type(), 100, string_type(100, '\0'));
38+
test(string_type(MAKE_CSTRING(CharT, "12345")), 0, string_type());
39+
test(string_type(MAKE_CSTRING(CharT, "12345")), 2, string_type(MAKE_CSTRING(CharT, "12")));
40+
test(string_type(MAKE_CSTRING(CharT, "12345")), 5, string_type(MAKE_CSTRING(CharT, "12345")));
41+
test(string_type(MAKE_CSTRING(CharT, "12345")),
42+
15,
43+
string_type(MAKE_CSTRING(CharT, "12345\0\0\0\0\0\0\0\0\0\0"), 15));
44+
test(string_type(MAKE_CSTRING(CharT, "12345678901234567890123456789012345678901234567890")), 0, string_type());
45+
test(string_type(MAKE_CSTRING(CharT, "12345678901234567890123456789012345678901234567890")),
46+
10,
47+
string_type(MAKE_CSTRING(CharT, "1234567890")));
48+
test(string_type(MAKE_CSTRING(CharT, "12345678901234567890123456789012345678901234567890")),
49+
50,
50+
string_type(MAKE_CSTRING(CharT, "12345678901234567890123456789012345678901234567890")));
51+
test(
52+
string_type(MAKE_CSTRING(CharT, "12345678901234567890123456789012345678901234567890")),
53+
60,
54+
string_type(MAKE_CSTRING(CharT, "12345678901234567890123456789012345678901234567890\0\0\0\0\0\0\0\0\0\0"), 60));
2855
}
56+
2957
#ifndef TEST_HAS_NO_EXCEPTIONS
30-
else if (!TEST_IS_CONSTANT_EVALUATED) {
58+
if (!TEST_IS_CONSTANT_EVALUATED) {
59+
std::basic_string<CharT, std::char_traits<CharT>, Alloc> str;
3160
try {
32-
s.resize(n);
61+
str.resize(std::string::npos);
3362
assert(false);
34-
} catch (std::length_error&) {
35-
assert(n > s.max_size());
63+
} catch (const std::length_error&) {
3664
}
3765
}
3866
#endif
39-
}
4067

41-
template <class S>
42-
TEST_CONSTEXPR_CXX20 void test_string() {
43-
test(S(), 0, S());
44-
test(S(), 1, S(1, '\0'));
45-
test(S(), 10, S(10, '\0'));
46-
test(S(), 100, S(100, '\0'));
47-
test(S("12345"), 0, S());
48-
test(S("12345"), 2, S("12"));
49-
test(S("12345"), 5, S("12345"));
50-
test(S("12345"), 15, S("12345\0\0\0\0\0\0\0\0\0\0", 15));
51-
test(S("12345678901234567890123456789012345678901234567890"), 0, S());
52-
test(S("12345678901234567890123456789012345678901234567890"), 10, S("1234567890"));
53-
test(S("12345678901234567890123456789012345678901234567890"),
54-
50,
55-
S("12345678901234567890123456789012345678901234567890"));
56-
test(S("12345678901234567890123456789012345678901234567890"),
57-
60,
58-
S("12345678901234567890123456789012345678901234567890\0\0\0\0\0\0\0\0\0\0", 60));
59-
test(S(), S::npos, S("not going to happen"));
68+
{ // check that string can grow to max_size()
69+
std::basic_string<CharT, std::char_traits<CharT>, tiny_size_allocator<32, CharT>> str;
70+
str.resize(str.max_size());
71+
assert(str.size() == str.max_size());
72+
}
6073
}
6174

6275
TEST_CONSTEXPR_CXX20 bool test() {
63-
test_string<std::string>();
76+
test_string<char, std::allocator<char> >();
6477
#if TEST_STD_VER >= 11
65-
test_string<std::basic_string<char, std::char_traits<char>, min_allocator<char>>>();
66-
test_string<std::basic_string<char, std::char_traits<char>, safe_allocator<char>>>();
78+
test_string<char, min_allocator<char>>();
79+
test_string<char, safe_allocator<char>>();
80+
#endif
81+
82+
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
83+
test_string<wchar_t, std::allocator<wchar_t> >();
6784
#endif
6885

6986
return true;

libcxx/test/std/strings/basic.string/string.modifiers/string_append/pointer_size.pass.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,15 @@ TEST_CONSTEXPR_CXX20 bool test() {
8787
assert(s_long == "Lorem ipsum dolor sit amet, consectetur/Lorem ipsum dolor sit amet, consectetur/");
8888
}
8989

90+
{ // check that growing to max_size() works
91+
using string_type = std::basic_string<char, std::char_traits<char>, tiny_size_allocator<29, char>>;
92+
string_type str;
93+
auto max_size = str.max_size();
94+
str.resize(max_size / 2 + max_size % 1);
95+
str.append(str.c_str(), max_size / 2);
96+
assert(str.size() == str.max_size());
97+
}
98+
9099
return true;
91100
}
92101

libcxx/test/std/strings/basic.string/string.nonmembers/string_op+/char_string.pass.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,17 @@ TEST_CONSTEXPR_CXX20 bool test() {
5151
test_string<std::basic_string<char, std::char_traits<char>, safe_allocator<char> > >();
5252
#endif
5353

54+
{ // check that growing to max_size() works
55+
using string_type = std::basic_string<char, std::char_traits<char>, tiny_size_allocator<29, char> >;
56+
string_type str;
57+
str.resize(str.max_size() - 1);
58+
string_type result = 'a' + str;
59+
60+
assert(result.size() == result.max_size());
61+
assert(result.front() == 'a');
62+
assert(result.capacity() <= result.get_allocator().max_size());
63+
}
64+
5465
return true;
5566
}
5667

libcxx/test/std/strings/basic.string/string.nonmembers/string_op+/pointer_string.pass.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,17 @@ TEST_CONSTEXPR_CXX20 bool test() {
6161
test_string<std::basic_string<char, std::char_traits<char>, min_allocator<char>>>();
6262
#endif
6363

64+
{ // check that growing to max_size() works
65+
using string_type = std::basic_string<char, std::char_traits<char>, tiny_size_allocator<29, char> >;
66+
string_type str;
67+
str.resize(str.max_size() - 1);
68+
string_type result = "a" + str;
69+
70+
assert(result.size() == result.max_size());
71+
assert(result.front() == 'a');
72+
assert(result.capacity() <= result.get_allocator().max_size());
73+
}
74+
6475
return true;
6576
}
6677

libcxx/test/std/strings/basic.string/string.nonmembers/string_op+/string_char.pass.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,17 @@ TEST_CONSTEXPR_CXX20 bool test() {
5151
test_string<std::basic_string<char, std::char_traits<char>, safe_allocator<char> > >();
5252
#endif
5353

54+
{ // check that growing to max_size() works
55+
using string_type = std::basic_string<char, std::char_traits<char>, tiny_size_allocator<29, char> >;
56+
string_type str;
57+
str.resize(str.max_size() - 1);
58+
string_type result = str + 'a';
59+
60+
assert(result.size() == result.max_size());
61+
assert(result.back() == 'a');
62+
assert(result.capacity() <= result.get_allocator().max_size());
63+
}
64+
5465
return true;
5566
}
5667

libcxx/test/std/strings/basic.string/string.nonmembers/string_op+/string_pointer.pass.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,17 @@ TEST_CONSTEXPR_CXX20 bool test() {
7676
test_string<std::basic_string<char, std::char_traits<char>, safe_allocator<char> > >();
7777
#endif
7878

79+
{ // check that growing to max_size() works
80+
using string_type = std::basic_string<char, std::char_traits<char>, tiny_size_allocator<29, char> >;
81+
string_type str;
82+
str.resize(str.max_size() - 1);
83+
string_type result = str + "a";
84+
85+
assert(result.size() == result.max_size());
86+
assert(result.back() == 'a');
87+
assert(result.capacity() <= result.get_allocator().max_size());
88+
}
89+
7990
return true;
8091
}
8192

libcxx/test/std/strings/basic.string/string.nonmembers/string_op+/string_string.pass.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,20 @@ TEST_CONSTEXPR_CXX20 bool test() {
8686
test_string<std::basic_string<char, std::char_traits<char>, safe_allocator<char>>>();
8787
#endif
8888

89+
{ // check that growing to max_size() works
90+
using string_type = std::basic_string<char, std::char_traits<char>, tiny_size_allocator<29, char> >;
91+
string_type lhs;
92+
lhs.resize(lhs.max_size() - 1);
93+
94+
string_type rhs = "a";
95+
96+
string_type result = lhs + rhs;
97+
98+
assert(result.size() == result.max_size());
99+
assert(result.back() == 'a');
100+
assert(result.capacity() <= result.get_allocator().max_size());
101+
}
102+
89103
return true;
90104
}
91105

libcxx/test/support/min_allocator.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,4 +474,32 @@ class safe_allocator {
474474
TEST_CONSTEXPR_CXX20 friend bool operator!=(safe_allocator x, safe_allocator y) { return !(x == y); }
475475
};
476476

477+
template <std::size_t MaxSize, class T>
478+
struct tiny_size_allocator {
479+
using value_type = T;
480+
using size_type = unsigned;
481+
482+
template <class U>
483+
struct rebind {
484+
using other = tiny_size_allocator<MaxSize, T>;
485+
};
486+
487+
tiny_size_allocator() = default;
488+
489+
template <class U>
490+
TEST_CONSTEXPR_CXX20 tiny_size_allocator(tiny_size_allocator<MaxSize, U>) {}
491+
492+
TEST_CONSTEXPR_CXX20 T* allocate(std::size_t n) {
493+
assert(n <= MaxSize);
494+
return std::allocator<T>().allocate(n);
495+
}
496+
497+
TEST_CONSTEXPR_CXX20 void deallocate(T* ptr, std::size_t n) { std::allocator<T>().deallocate(ptr, n); }
498+
499+
TEST_CONSTEXPR_CXX20 size_type max_size() const { return MaxSize; }
500+
501+
friend bool operator==(tiny_size_allocator, tiny_size_allocator) { return true; }
502+
friend bool operator!=(tiny_size_allocator, tiny_size_allocator) { return false; }
503+
};
504+
477505
#endif // MIN_ALLOCATOR_H

0 commit comments

Comments
 (0)