Skip to content

Commit c5cd826

Browse files
mordantetru
authored andcommitted
[libc++][string] Fixes shrink_to_fit. (llvm#97961)
This ensures that shrink_to_fit does not increase the allocated size. Partly addresses llvm#95161 (cherry picked from commit d0ca9f2)
1 parent a930d1f commit c5cd826

File tree

2 files changed

+55
-3
lines changed

2 files changed

+55
-3
lines changed

libcxx/include/string

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3358,23 +3358,34 @@ basic_string<_CharT, _Traits, _Allocator>::__shrink_or_extend(size_type __target
33583358
__p = __get_long_pointer();
33593359
} else {
33603360
if (__target_capacity > __cap) {
3361+
// Extend
3362+
// - called from reserve should propagate the exception thrown.
33613363
auto __allocation = std::__allocate_at_least(__alloc(), __target_capacity + 1);
33623364
__new_data = __allocation.ptr;
33633365
__target_capacity = __allocation.count - 1;
33643366
} else {
3367+
// Shrink
3368+
// - called from shrink_to_fit should not throw.
3369+
// - called from reserve may throw but is not required to.
33653370
#ifndef _LIBCPP_HAS_NO_EXCEPTIONS
33663371
try {
33673372
#endif // _LIBCPP_HAS_NO_EXCEPTIONS
33683373
auto __allocation = std::__allocate_at_least(__alloc(), __target_capacity + 1);
3374+
3375+
// The Standard mandates shrink_to_fit() does not increase the capacity.
3376+
// With equal capacity keep the existing buffer. This avoids extra work
3377+
// due to swapping the elements.
3378+
if (__allocation.count - 1 > __target_capacity) {
3379+
__alloc_traits::deallocate(__alloc(), __allocation.ptr, __allocation.count);
3380+
__annotate_new(__sz); // Undoes the __annotate_delete()
3381+
return;
3382+
}
33693383
__new_data = __allocation.ptr;
33703384
__target_capacity = __allocation.count - 1;
33713385
#ifndef _LIBCPP_HAS_NO_EXCEPTIONS
33723386
} catch (...) {
33733387
return;
33743388
}
3375-
#else // _LIBCPP_HAS_NO_EXCEPTIONS
3376-
if (__new_data == nullptr)
3377-
return;
33783389
#endif // _LIBCPP_HAS_NO_EXCEPTIONS
33793390
}
33803391
__begin_lifetime(__new_data, __target_capacity + 1);

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,49 @@ TEST_CONSTEXPR_CXX20 bool test() {
6363
return true;
6464
}
6565

66+
#if TEST_STD_VER >= 23
67+
std::size_t min_bytes = 1000;
68+
69+
template <typename T>
70+
struct increasing_allocator {
71+
using value_type = T;
72+
increasing_allocator() = default;
73+
template <typename U>
74+
increasing_allocator(const increasing_allocator<U>&) noexcept {}
75+
std::allocation_result<T*> allocate_at_least(std::size_t n) {
76+
std::size_t allocation_amount = n * sizeof(T);
77+
if (allocation_amount < min_bytes)
78+
allocation_amount = min_bytes;
79+
min_bytes += 1000;
80+
return {static_cast<T*>(::operator new(allocation_amount)), allocation_amount / sizeof(T)};
81+
}
82+
T* allocate(std::size_t n) { return allocate_at_least(n).ptr; }
83+
void deallocate(T* p, std::size_t) noexcept { ::operator delete(static_cast<void*>(p)); }
84+
};
85+
86+
template <typename T, typename U>
87+
bool operator==(increasing_allocator<T>, increasing_allocator<U>) {
88+
return true;
89+
}
90+
91+
// https://github.com/llvm/llvm-project/issues/95161
92+
void test_increasing_allocator() {
93+
std::basic_string<char, std::char_traits<char>, increasing_allocator<char>> s{
94+
"String does not fit in the internal buffer"};
95+
std::size_t capacity = s.capacity();
96+
std::size_t size = s.size();
97+
s.shrink_to_fit();
98+
assert(s.capacity() <= capacity);
99+
assert(s.size() == size);
100+
LIBCPP_ASSERT(is_string_asan_correct(s));
101+
}
102+
#endif // TEST_STD_VER >= 23
103+
66104
int main(int, char**) {
67105
test();
106+
#if TEST_STD_VER >= 23
107+
test_increasing_allocator();
108+
#endif
68109
#if TEST_STD_VER > 17
69110
static_assert(test());
70111
#endif

0 commit comments

Comments
 (0)