Skip to content

Commit adfb6a7

Browse files
committed
Fix __align_allocation_size for awkward char sizes
The ASan failure in CI complained that a buffer had been allocated as 7 chars (in the case where sizeof(char_type)==5) and freed as only 6. That's because the test was running in a configuration with __endian_factor==2, and __align_allocation_size is supposed to always return an even number in that situation, and it wasn't. Fixed by introducing a new more careful code path for non-power-of-2 char sizes, and also adjusting the caller so that it actually remembers the value it got back from __align_allocation_size, to pass the same value to free later. (I'm also suspicious of the __allocate_at_least call, which isn't using the returned count field. That too might be odd, which would stop us from storing it in the __long size field. At the moment this isn't failing because __allocate_at_least never allocates more than it's asked to.)
1 parent 91cadd5 commit adfb6a7

File tree

1 file changed

+28
-6
lines changed

1 file changed

+28
-6
lines changed

libcxx/include/string

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2329,14 +2329,15 @@ private:
23292329
__allocate_long_buffer(_Allocator& __alloc, size_type __capacity) {
23302330
_LIBCPP_ASSERT_INTERNAL(!__fits_in_sso(__capacity),
23312331
"Trying to allocate long buffer for a capacity what would fit into the small buffer");
2332-
auto __buffer = std::__allocate_at_least(__alloc, __align_allocation_size(__capacity));
2332+
size_type __aligned_capacity = __align_allocation_size(__capacity);
2333+
auto __buffer = std::__allocate_at_least(__alloc, __aligned_capacity);
23332334

23342335
if (__libcpp_is_constant_evaluated()) {
23352336
for (size_type __i = 0; __i != __buffer.count; ++__i)
23362337
std::__construct_at(std::addressof(__buffer.ptr[__i]));
23372338
}
23382339

2339-
return __long(__buffer, __capacity);
2340+
return __long(__buffer, __aligned_capacity);
23402341
}
23412342

23422343
// Replace the current buffer with __new_rep. Deallocate the old long buffer if it exists.
@@ -2422,18 +2423,39 @@ private:
24222423
}
24232424
enum { __alignment = 8 };
24242425

2426+
static _LIBCPP_CONSTEXPR_SINCE_CXX20 bool __is_power_of_2(size_type __s) _NOEXCEPT {
2427+
return __s != 0 && (__s & (__s - 1)) == 0;
2428+
}
2429+
24252430
// This makes sure that we're using a capacity with some extra alignment, since allocators almost always over-align
24262431
// the allocations anyways, improving memory usage. More importantly, this ensures that the lowest bit is never set
24272432
// if __endian_factor == 2, allowing us to store whether we're in the long string inside the lowest bit.
24282433
_LIBCPP_HIDE_FROM_ABI static _LIBCPP_CONSTEXPR_SINCE_CXX20 size_type
24292434
__align_allocation_size(size_type __size) _NOEXCEPT {
24302435
_LIBCPP_ASSERT_INTERNAL(
24312436
!__fits_in_sso(__size), "Trying to align allocation of a size which would fit into the SSO");
2432-
const size_type __boundary = sizeof(value_type) < __alignment ? __alignment / sizeof(value_type) : __endian_factor;
2433-
size_type __guess = __align_it<__boundary>(__size + 1);
2434-
if (__guess == __min_cap + 1)
2435-
__guess += __endian_factor;
24362437

2438+
size_type __guess;
2439+
2440+
if (__is_power_of_2(sizeof(value_type))) {
2441+
// If the size of a _Char is itself a power of 2, then we can align the
2442+
// total allocation size in bytes by aligning the count of characters to
2443+
// an appropriate power of 2.
2444+
const size_type __boundary =
2445+
sizeof(value_type) < __alignment ? __alignment / sizeof(value_type) : __endian_factor;
2446+
__guess = __align_it<__boundary>(__size + 1);
2447+
if (__guess == __min_cap + 1)
2448+
__guess += __endian_factor;
2449+
} else {
2450+
// Otherwise, we must align the size in bytes and then calculate the
2451+
// count of characters by division.
2452+
const size_type __even_size = __align_it<__endian_factor>(__size + 1);
2453+
const size_type __unaligned_bytes = __even_size * sizeof(value_type);
2454+
const size_type __aligned_bytes = __align_it<__alignment>(__unaligned_bytes);
2455+
__guess = (__aligned_bytes / (sizeof(value_type) * __endian_factor)) * __endian_factor;
2456+
}
2457+
2458+
_LIBCPP_ASSERT_INTERNAL(__guess % __endian_factor == 0, "aligned allocation size is odd but __endian_factor == 2");
24372459
_LIBCPP_ASSERT_INTERNAL(__guess >= __size, "aligned allocation size is below the requested size");
24382460
return __guess;
24392461
}

0 commit comments

Comments
 (0)