Skip to content

Commit 7ec607a

Browse files
committed
Add tests to ensure no buffer swapping with equal alloc size
1 parent 16ac549 commit 7ec607a

File tree

2 files changed

+129
-17
lines changed

2 files changed

+129
-17
lines changed

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

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

64-
return true;
65-
}
66-
6764
#if TEST_STD_VER >= 23
68-
// https://github.com/llvm/llvm-project/issues/95161
69-
void test_increasing_allocator() {
70-
std::basic_string<char, std::char_traits<char>, increasing_allocator<char>> s{
71-
"String does not fit in the internal buffer"};
72-
std::size_t capacity = s.capacity();
73-
std::size_t size = s.size();
74-
s.shrink_to_fit();
75-
assert(s.capacity() <= capacity);
76-
assert(s.size() == size);
77-
LIBCPP_ASSERT(is_string_asan_correct(s));
65+
{ // Make sure shrink_to_fit never increases capacity
66+
// See: https://github.com/llvm/llvm-project/issues/95161
67+
std::basic_string<char, std::char_traits<char>, increasing_allocator<char>> s{
68+
"String does not fit in the internal buffer"};
69+
std::size_t capacity = s.capacity();
70+
std::size_t size = s.size();
71+
s.shrink_to_fit();
72+
assert(s.capacity() <= capacity);
73+
assert(s.size() == size);
74+
LIBCPP_ASSERT(is_string_asan_correct(s));
75+
}
76+
#endif
77+
78+
{ // Ensure that the libc++ implementation of shrink_to_fit does NOT swap buffer with equal allocation sizes
79+
{ // Test with custom allocator with a minimum allocation size
80+
std::basic_string<char, std::char_traits<char>, min_size_allocator<128, char> > s(
81+
"A long string exceeding SSO limit but within min alloc size");
82+
std::size_t capacity = s.capacity();
83+
std::size_t size = s.size();
84+
auto data = s.data();
85+
s.shrink_to_fit();
86+
assert(s.capacity() <= capacity);
87+
assert(s.size() == size);
88+
LIBCPP_ASSERT(is_string_asan_correct(s));
89+
if (s.capacity() == capacity)
90+
LIBCPP_ASSERT(s.data() == data);
91+
}
92+
{ // Test with custom allocator with a minimum power of two allocation size
93+
std::basic_string<char, std::char_traits<char>, pow2_allocator<char> > s(
94+
"This is a long string that exceeds the SSO limit");
95+
std::size_t capacity = s.capacity();
96+
std::size_t size = s.size();
97+
auto data = s.data();
98+
s.shrink_to_fit();
99+
assert(s.capacity() <= capacity);
100+
assert(s.size() == size);
101+
LIBCPP_ASSERT(is_string_asan_correct(s));
102+
if (s.capacity() == capacity)
103+
LIBCPP_ASSERT(s.data() == data);
104+
}
105+
}
106+
107+
return true;
78108
}
79-
#endif // TEST_STD_VER >= 23
80109

81110
int main(int, char**) {
82111
test();
83-
#if TEST_STD_VER >= 23
84-
test_increasing_allocator();
85-
#endif
86112
#if TEST_STD_VER > 17
87113
static_assert(test());
88114
#endif

libcxx/test/support/increasing_allocator.h

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#define TEST_SUPPORT_INCREASING_ALLOCATOR_H
1111

1212
#include <cstddef>
13+
#include <limits>
1314
#include <memory>
1415

1516
#include "test_macros.h"
@@ -49,4 +50,89 @@ TEST_CONSTEXPR_CXX20 bool operator==(increasing_allocator<T>, increasing_allocat
4950
return true;
5051
}
5152

53+
template <std::size_t MinAllocSize, typename T>
54+
class min_size_allocator {
55+
public:
56+
using value_type = T;
57+
min_size_allocator() = default;
58+
59+
template <typename U>
60+
TEST_CONSTEXPR_CXX20 min_size_allocator(const min_size_allocator<MinAllocSize, U>&) TEST_NOEXCEPT {}
61+
62+
#if TEST_STD_VER >= 23
63+
TEST_CONSTEXPR_CXX23 std::allocation_result<T*> allocate_at_least(std::size_t n) {
64+
if (n < MinAllocSize)
65+
n = MinAllocSize;
66+
return std::allocator<T>{}.allocate_at_least(n);
67+
}
68+
#endif // TEST_STD_VER >= 23
69+
70+
TEST_NODISCARD TEST_CONSTEXPR_CXX20 T* allocate(std::size_t n) {
71+
if (n < MinAllocSize)
72+
n = MinAllocSize;
73+
return std::allocator<T>().allocate(n);
74+
}
75+
76+
TEST_CONSTEXPR_CXX20 void deallocate(T* p, std::size_t n) TEST_NOEXCEPT { std::allocator<T>().deallocate(p, n); }
77+
78+
template <typename U>
79+
struct rebind {
80+
using other = min_size_allocator<MinAllocSize, U>;
81+
};
82+
};
83+
84+
template <std::size_t MinAllocSize, typename T, typename U>
85+
TEST_CONSTEXPR_CXX20 bool
86+
operator==(const min_size_allocator<MinAllocSize, T>&, const min_size_allocator<MinAllocSize, U>&) {
87+
return true;
88+
}
89+
90+
template <std::size_t MinAllocSize, typename T, typename U>
91+
TEST_CONSTEXPR_CXX20 bool
92+
operator!=(const min_size_allocator<MinAllocSize, T>&, const min_size_allocator<MinAllocSize, U>&) {
93+
return false;
94+
}
95+
96+
template <typename T>
97+
class pow2_allocator {
98+
public:
99+
using value_type = T;
100+
pow2_allocator() = default;
101+
102+
template <typename U>
103+
TEST_CONSTEXPR_CXX20 pow2_allocator(const pow2_allocator<U>&) TEST_NOEXCEPT {}
104+
105+
#if TEST_STD_VER >= 23
106+
TEST_CONSTEXPR_CXX23 std::allocation_result<T*> allocate_at_least(std::size_t n) {
107+
return std::allocator<T>{}.allocate_at_least(next_power_of_two(n));
108+
}
109+
#endif // TEST_STD_VER >= 23
110+
111+
TEST_NODISCARD TEST_CONSTEXPR_CXX20 T* allocate(std::size_t n) {
112+
return std::allocator<T>().allocate(next_power_of_two(n));
113+
}
114+
115+
TEST_CONSTEXPR_CXX20 void deallocate(T* p, std::size_t n) TEST_NOEXCEPT { std::allocator<T>().deallocate(p, n); }
116+
117+
private:
118+
TEST_CONSTEXPR_CXX20 std::size_t next_power_of_two(std::size_t n) const {
119+
if ((n & (n - 1)) == 0)
120+
return n;
121+
for (std::size_t shift = 1; shift < std::numeric_limits<std::size_t>::digits; shift <<= 1) {
122+
n |= n >> shift;
123+
}
124+
return n + 1;
125+
}
126+
};
127+
128+
template <typename T, typename U>
129+
TEST_CONSTEXPR_CXX20 bool operator==(const pow2_allocator<T>&, const pow2_allocator<U>&) {
130+
return true;
131+
}
132+
133+
template <typename T, typename U>
134+
TEST_CONSTEXPR_CXX20 bool operator!=(const pow2_allocator<T>&, const pow2_allocator<U>&) {
135+
return false;
136+
}
137+
52138
#endif // TEST_SUPPORT_INCREASING_ALLOCATOR_H

0 commit comments

Comments
 (0)