Hi. I maintain gperftools (https://github.com/gperftools/gperftools). I noticed announcement of release of 14.1 and installed it in VM. When I run gperftools unit tests "debug" configurations pretty much ~all fail due to mismatch between requested allocation size and allocation size passed to sized delete. It should be straighforward to reproduce by simply git cloning from github and running ./autogen.sh && ./configure && make check I don't have capacity to investigate more, but I have backtrace of allocation: #0 MallocBlock::Initialize (this=this@entry=0xc480f0, size=size@entry=25, type=type@entry=-21308287) at src/debugallocation.cc:382 #1 0x0000000000225e4c in MallocBlock::Allocate (size=size@entry=25, type=-21308287) at src/debugallocation.cc:560 #2 0x00000000002245d1 in DebugAllocate (size=25, type=type@entry=-21308287) at src/debugallocation.cc:1051 #3 0x000000000026eff4 in debug_cpp_alloc (size=12878064, new_type=-21308287, nothrow=false) at src/debugallocation.cc:1220 #4 tc_new (size=12878064) at src/debugallocation.cc:1328 #5 0x0000000000256edd in std::__1::__libcpp_operator_new[abi:se180100]<unsigned long>(unsigned long) (__args=25) at /usr/include/c++/v1/new:271 #6 std::__1::__libcpp_allocate[abi:se180100](unsigned long, unsigned long) (__size=25, __align=1) at /usr/include/c++/v1/new:295 #7 std::__1::allocator<char>::allocate[abi:se180100](unsigned long) (this=0x7fffffffd878, __n=25) at /usr/include/c++/v1/__memory/allocator.h:125 #8 std::__1::__allocate_at_least[abi:se180100]<std::__1::allocator<char> >(std::__1::allocator<char>&, unsigned long) (__alloc=..., __n=25) at /usr/include/c++/v1/__memory/allocate_at_least.h:55 #9 std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::__init (this=0x7fffffffd878, __sz=23, __s=<optimized out>) at /usr/include/c++/v1/string:2212 #10 _ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEC2B8se180100ILi0EEEPKc (this=0x7fffffffd878, __s=<optimized out>) at /usr/include/c++/v1/string:954 #11 testing::internal::UnitTestImpl::GetTestSuite (this=0xc50020, test_suite_name=0xc4e021 "MallocExtensionTest", type_param=0x0, set_up_tc=set_up_tc@entry=0x0, tear_down_tc=tear_down_tc@entry=0x0) at vendor/googletest/googletest/src/gtest.cc:5786 #12 0x000000000025d7d2 in testing::internal::UnitTestImpl::AddTestInfo (this=<optimized out>, set_up_tc=0x0, tear_down_tc=0x0, test_info=test_info@entry=0xc4e020) at ./vendor/googletest/googletest/src/gtest-internal-inl.h:690 #13 0x0000000000242ba3 in testing::internal::MakeAndRegisterTestInfo (test_suite_name=<optimized out>, name=0x209287 "Basics", type_param=type_param@entry=0x0, value_param=value_param@entry=0x0, code_location=..., fixture_class_id=fixture_class_id@entry=0x4237f1 <testing::internal::TypeIdHelper<testing::Test>::dummy_>, set_up_tc=0x0, tear_down_tc=0x0, factory=0xc4c020) at vendor/googletest/googletest/src/gtest.cc:2794 #14 0x0000000000221fdb in __cxx_global_var_init () at src/tests/malloc_extension_test.cc:47 #15 0x0000000000221fdb in _GLOBAL__sub_I_malloc_extension_test.cc () #16 0x000000080027df5d in objlist_call_init (list=list@entry=0x7fffffffe9c8, lockstate=lockstate@entry=0x7fffffffe7d8) at /usr/src/libexec/rtld-elf/rtld.c:3150 #17 0x000000080027cb89 in _rtld (sp=<optimized out>, exit_proc=0x7fffffffea40, objp=0x7fffffffea48) at /usr/src/libexec/rtld-elf/rtld.c:990 #18 0x0000000800279df9 in rtld_start () at /usr/src/libexec/rtld-elf/amd64/rtld_start.S:40 (we see it uses size of 25 bytes) and this is backtrace of deallocation (which passes 24 bytes): #0 0x0000000000227ecb in MallocBlock::CheckAndClear (this=0xc480f0, type=<optimized out>, given_size=24) at src/debugallocation.cc:417 #1 0x0000000000227d66 in MallocBlock::Deallocate (this=0x4, type=2, given_size=2138871) at src/debugallocation.cc:338 #2 0x0000000000224152 in DebugDeallocate (ptr=<optimized out>, type=2, given_size=2138871) at src/debugallocation.cc:1065 #3 0x000000000026f460 in tc_delete_sized (p=<optimized out>, size=<optimized out>) at src/debugallocation.cc:1350 #4 0x0000000000256f51 in std::__1::__libcpp_operator_delete[abi:se180100]<void*, unsigned long>(void*, unsigned long) (__args=2, __args=2) at /usr/include/c++/v1/new:280 #5 std::__1::__do_deallocate_handle_size[abi:se180100]<>(void*, unsigned long) (__ptr=0x4, __size=2) at /usr/include/c++/v1/new:304 #6 std::__1::__libcpp_deallocate[abi:se180100](void*, unsigned long, unsigned long) (__ptr=0x4, __size=2, __align=1) at /usr/include/c++/v1/new:317 #7 std::__1::allocator<char>::deallocate[abi:se180100](char*, unsigned long) (this=0x7fffffffd878, __p=0x4 <error: Cannot access memory at address 0x4>, __n=2) at /usr/include/c++/v1/__memory/allocator.h:139 #8 std::__1::allocator_traits<std::__1::allocator<char> >::deallocate[abi:se180100](std::__1::allocator<char>&, char*, unsigned long) (__a=..., __p=0x4 <error: Cannot access memory at address 0x4>, __n=2) at /usr/include/c++/v1/__memory/allocator_traits.h:289 #9 std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::~basic_string (this=0x7fffffffd878) at /usr/include/c++/v1/string:1105 #10 testing::internal::UnitTestImpl::GetTestSuite (this=0xc50020, test_suite_name=0xc4e021 "MallocExtensionTest", type_param=0x0, set_up_tc=set_up_tc@entry=0x0, tear_down_tc=tear_down_tc@entry=0x0) at vendor/googletest/googletest/src/gtest.cc:5786 #11 0x000000000025d7d2 in testing::internal::UnitTestImpl::AddTestInfo (this=<optimized out>, set_up_tc=0x0, tear_down_tc=0x0, test_info=test_info@entry=0xc4e020) at ./vendor/googletest/googletest/src/gtest-internal-inl.h:690 #12 0x0000000000242ba3 in testing::internal::MakeAndRegisterTestInfo (test_suite_name=<optimized out>, name=0x209287 "Basics", type_param=type_param@entry=0x0, value_param=value_param@entry=0x0, code_location=..., fixture_class_id=fixture_class_id@entry=0x4237f1 <testing::internal::TypeIdHelper<testing::Test>::dummy_>, set_up_tc=0x0, tear_down_tc=0x0, factory=0xc4c020) at vendor/googletest/googletest/src/gtest.cc:2794 #13 0x0000000000221fdb in __cxx_global_var_init () at src/tests/malloc_extension_test.cc:47 #14 0x0000000000221fdb in _GLOBAL__sub_I_malloc_extension_test.cc () #15 0x000000080027df5d in objlist_call_init (list=list@entry=0x7fffffffe9c8, lockstate=lockstate@entry=0x7fffffffe7d8) at /usr/src/libexec/rtld-elf/rtld.c:3150 #16 0x000000080027cb89 in _rtld (sp=<optimized out>, exit_proc=0x7fffffffea40, objp=0x7fffffffea48) at /usr/src/libexec/rtld-elf/rtld.c:990 #17 0x0000000800279df9 in rtld_start () at /usr/src/libexec/rtld-elf/amd64/rtld_start.S:40 Most likely, it is some sort of a bug in upstream clang's libc++ in whatever version you ship. Although it is somewhat odd, given that Google runs with sized deallocation enabled and with ~always latest clang/llvm bits, but maybe they exercise different configuration.
> When I run gperftools unit tests "debug" configurations pretty much ~all fail due to mismatch between requested allocation size and allocation size passed to sized delete. I can't reproduce this on FreeBSD 14.1-STABLE stable/14-n267671-9a8a26aefb36. There are various other failures in tests, but most tests pass.
Here is reproduction independent from gperftools. There is likely even simpler reproduction as I haven't looked deeper what gtest bits are doing, but they seem to be doing just general STL bits. Also please note that we're making it a bug by passing -fsized-deallocation to clang. * clone https://github.com/google/googletest * cd googletest * grab https://gist.github.com/alk/f73d13f73d47deaca3921cb8d7660bf2 * then run: c++ -o tt -pthread -std=gnu++17 -Wall -fsized-deallocation -Og -ggdb3 -Igoogletest/include -Igoogletest trivial-sized-test.cc googletest/src/gtest_main.cc googletest/src/gtest-assertion-result.cc googletest/src/gtest-death-test.cc googletest/src/gtest-filepath.cc googletest/src/gtest-matchers.cc googletest/src/gtest-port.cc googletest/src/gtest-printers.cc googletest/src/gtest-test-part.cc googletest/src/gtest-typed-test.cc googletest/src/gtest.cc It fails on my freebsd 14.1 VM (x64).
This is a bit of a false positive due to internal details of libc++ std::string. Normally the size of a std::string is 24 bytes on amd64, but only if it contains a 'short' string. When the contained string is too long to fit in the object itself, it's reallocated on the heap instead. In this particular case some TCMalloc test string comes out as 25 bytes, and that is what libc++ eventually calls malloc() with. However due to internal administration issues, it calculates the wrong size for the corresponding delete. This was also noticed by AddressSanitizer after <https://github.com/llvm/llvm-project/pull/83774> ("Enable sized deallocation by default in C++14 onwards"), and fixed by Vitaly Buka in <https://github.com/llvm/llvm-project/commit/d129ea8d2fa3>. I will merge that fix into -CURRENT, with one prerequisite fix added, so we can see if there's any fallout (but it's not likely). Then I will MFC it, and we can think about a 14.1 erratum.
A commit in branch main references this bug: URL: https://cgit.FreeBSD.org/src/commit/?id=ead8e4c081e5c4de4d508fc353f381457b058ca6 commit ead8e4c081e5c4de4d508fc353f381457b058ca6 Author: Dimitry Andric <dim@FreeBSD.org> AuthorDate: 2024-06-07 18:42:53 +0000 Commit: Dimitry Andric <dim@FreeBSD.org> CommitDate: 2024-06-07 18:43:46 +0000 Merge commit 382f70a877f0 from llvm-project (by Louis Dionne): [libc++][NFC] Rewrite function call on two lines for clarity (#79141) Previously, there was a ternary conditional with a less-than comparison appearing inside a template argument, which was really confusing because of the <...> of the function template. This patch rewrites the same statement on two lines for clarity. Merge commit d129ea8d2fa3 from llvm-project (by Vitaly Buka): [libcxx] Align `__recommend() + 1` by __endian_factor (#90292) This is detected by asan after #83774 Allocation size will be divided by `__endian_factor` before storing. If it's not aligned, we will not be able to recover allocation size to pass into `__alloc_traits::deallocate`. we have code like this ``` auto __allocation = std::__allocate_at_least(__alloc(), __recommend(__sz) + 1); __p = __allocation.ptr; __set_long_cap(__allocation.count); void __set_long_cap(size_type __s) _NOEXCEPT { __r_.first().__l.__cap_ = __s / __endian_factor; __r_.first().__l.__is_long_ = true; } size_type __get_long_cap() const _NOEXCEPT { return __r_.first().__l.__cap_ * __endian_factor; } inline ~basic_string() { __annotate_delete(); if (__is_long()) __alloc_traits::deallocate(__alloc(), __get_long_pointer(), __get_long_cap()); } ``` 1. __recommend() -> even size 2. `std::__allocate_at_least(__alloc(), __recommend(__sz) + 1)` - > not even size 3. ` __set_long_cap() `- > lose one bit of size for __endian_factor == 2 (see `/ __endian_factor`) 4. `__alloc_traits::deallocate(__alloc(), __get_long_pointer(), __get_long_cap())` -> uses even size (see `__get_long_cap`) This should fix incorrect deallocation sizes for some instances of std::string. Memory profiling or debugging tools like AddressSanitizer, LeakSanitizer or TCMalloc could then complain about the the size passed to a deallocation not matching the size originally passed to the allocation. Reported by: Aliaksei Kandratsenka <alkondratenko@gmail.com> PR: 279560 MFC after: 3 days contrib/llvm-project/libcxx/include/string | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
After this change, with a rebuilt and reinstalled libc++ (headers + so), almost all gperftools tests succeed now, except for one test that is dependent on /proc filesystem layout: make check-TESTS PASS: unique_path_unittest PASS: generic_writer_test FAIL: proc_maps_iterator_test PASS: low_level_alloc_unittest PASS: stacktrace_unittest PASS: check_address_test PASS: addressmap_unittest PASS: packed_cache_test PASS: safe_strerror_test PASS: cleanup_test PASS: function_ref_test PASS: pagemap_unittest PASS: page_heap_test PASS: stack_trace_table_test PASS: malloc_hook_test PASS: mmap_hook_test PASS: sampler_test PASS: tcmalloc_minimal_unittest PASS: tcm_min_asserts_unittest PASS: tcmalloc_minimal_large_unittest PASS: tcmalloc_minimal_large_heap_fragmentation_unittest PASS: system_alloc_unittest PASS: frag_unittest PASS: markidle_unittest PASS: current_allocated_bytes_test PASS: malloc_extension_test PASS: malloc_extension_c_test PASS: memalign_unittest PASS: realloc_unittest PASS: thread_dealloc_unittest PASS: min_per_thread_cache_size_test PASS: tcmalloc_minimal_debug_unittest PASS: malloc_extension_debug_test PASS: memalign_debug_unittest PASS: realloc_debug_unittest PASS: debugallocation_test PASS: tcmalloc_unittest PASS: tcm_asserts_unittest PASS: tcmalloc_both_unittest PASS: tcmalloc_large_unittest PASS: tcmalloc_large_heap_fragmentation_unittest PASS: sampling_test rm -f heap-profiler_unittest.sh cp -p /home/dim/src/gperftools/src/tests/heap-profiler_unittest.sh heap-profiler_unittest.sh PASS: heap-profiler_unittest.sh PASS: tcmalloc_debug_unittest PASS: sampling_debug_test rm -f heap-profiler_debug_unittest.sh cp -p /home/dim/src/gperftools/src/tests/heap-profiler_unittest.sh heap-profiler_debug_unittest.sh PASS: heap-profiler_debug_unittest.sh ============================================================================ Testsuite summary for gperftools 2.15 ============================================================================ # TOTAL: 46 # PASS: 45 # SKIP: 0 # XFAIL: 0 # FAIL: 1 # XPASS: 0 # ERROR: 0 ============================================================================ This is with gperftools master, gperftools-2.15-179-gaddf751.
Right, the procmaps failure was because I had no /proc mounted at all on this 14.1-R VM. :) After mounting procfs on /proc, all gperftools tests succeed now.
Thanks for the update. I am frankly shocked, because the issue appears to be just std::string, so Google should have caught this internally. And somehow they haven't. Do you have any details on the age of that bug? (I.e. what bad sizing was introduced in std::string, not when it was caught by asan changing defaults) Also curious what makes you say "This is a bit of a false positive". It is a proper bug, as mallocs that implement sized delete and actually trust passed size will crash. Notably, our behavior by default is to not trust the passed size exactly for fear of bugs like this, but our debugging malloc is checking things (as is "abseil" tcmalloc's). When/if I can expect the fixed version ? I don't have to bother, but I do prefer to have 14.1 testing VM and if it simply fails, I cannot test on freebsd 14.1. And I am not eager to add workarounds. On the other hand, I won't be surprised if this bug is reproducible on some other libcxx configs (i.e. you can ask for it on GNU/Linux)
So looking at this more, indeed, at least libc++-18-dev on Debian is broken as well.
(In reply to Aliaksei Kandratsenka from comment #7) > Thanks for the update. I am frankly shocked, because the issue appears to be just std::string, so Google should have caught this internally. And somehow they haven't. Look at https://github.com/llvm/llvm-project/commit/cf5a8b489464, which was committed on 2024-04-26 so just a few weeks ago. This was a retry of https://reviews.llvm.org/D112921 (from 2021) which also attempted to enable sized deallocation by default. Apparently, it was not that easy to fix all the fallout it caused, which is likely why it took so long to get this into llvm main. The day after that commit, the ASan people (Vitaly is one of them) noticed the std::string mismatches, and submitted https://github.com/llvm/llvm-project/pull/90292 . There is a bit of discussion in there which may be interesting. Long story short: the Google people did notice it, but only a few weeks ago. And it only applies to relatively cutting edge llvm (19.x only). And of course to people who turned on -fsized-deallocation manually! > Do you have any details on the age of that bug? (I.e. what bad sizing was introduced in std::string, not when it was caught by asan changing defaults) I'm not entirely sure, but since the beginning libc++'s string has been a SSO variant, with a __get_long_cap() function to retrieve the 'long' capacity if the contents was allocated on the heap. Possibly https://github.com/llvm/llvm-project/commit/29c8c070a1770fc510ccad3be753f6f50336f8cc (from 2022-04-21) could have introduced a bug, but maybe it is even older. I guess the only way to really check is to build older libc++'s and run gperftools against them... Maybe if I'm bored this weekend. :-) > Also curious what makes you say "This is a bit of a false positive". It is a proper bug, as mallocs that implement sized delete and actually trust passed size will crash. Yes, apologies for that, this was too strong. It is indeed a real bug, but since sized deallocation is not on by default, and you must also use an allocator that even cares about the free'd size it will not affect many people. > When/if I can expect the fixed version ? I will merge the fix to stable/14 and stable/13 after 3 days, then I can send a EN request to the re@ people for post-14.1-RELEASE, but generally that takes a while. My guess is that there are quite a few fixes lined up, but they were eager to get 14.1-RELEASE out the door.
Some further digging shows that at least for amd64, this regressed with https://github.com/llvm/llvm-project/commit/04ce0baf015c ("Unconditionally lower std::string's alignment requirement from 16 to 8"), which went in very soon after 18.x branched. And it was merged to 18.x in https://github.com/llvm/llvm-project/commit/5de1bcb70db2, so a bit after 18.1.0-rc1. Therefore I think this problem will only occur for libc++ 18.1.0 and later.
A commit in branch stable/14 references this bug: URL: https://cgit.FreeBSD.org/src/commit/?id=55c5dad2f305f74d1ff5ca85c453635511aab9b2 commit 55c5dad2f305f74d1ff5ca85c453635511aab9b2 Author: Dimitry Andric <dim@FreeBSD.org> AuthorDate: 2024-06-07 18:42:53 +0000 Commit: Dimitry Andric <dim@FreeBSD.org> CommitDate: 2024-06-10 07:29:25 +0000 Merge commit 382f70a877f0 from llvm-project (by Louis Dionne): [libc++][NFC] Rewrite function call on two lines for clarity (#79141) Previously, there was a ternary conditional with a less-than comparison appearing inside a template argument, which was really confusing because of the <...> of the function template. This patch rewrites the same statement on two lines for clarity. Merge commit d129ea8d2fa3 from llvm-project (by Vitaly Buka): [libcxx] Align `__recommend() + 1` by __endian_factor (#90292) This is detected by asan after #83774 Allocation size will be divided by `__endian_factor` before storing. If it's not aligned, we will not be able to recover allocation size to pass into `__alloc_traits::deallocate`. we have code like this ``` auto __allocation = std::__allocate_at_least(__alloc(), __recommend(__sz) + 1); __p = __allocation.ptr; __set_long_cap(__allocation.count); void __set_long_cap(size_type __s) _NOEXCEPT { __r_.first().__l.__cap_ = __s / __endian_factor; __r_.first().__l.__is_long_ = true; } size_type __get_long_cap() const _NOEXCEPT { return __r_.first().__l.__cap_ * __endian_factor; } inline ~basic_string() { __annotate_delete(); if (__is_long()) __alloc_traits::deallocate(__alloc(), __get_long_pointer(), __get_long_cap()); } ``` 1. __recommend() -> even size 2. `std::__allocate_at_least(__alloc(), __recommend(__sz) + 1)` - > not even size 3. ` __set_long_cap() `- > lose one bit of size for __endian_factor == 2 (see `/ __endian_factor`) 4. `__alloc_traits::deallocate(__alloc(), __get_long_pointer(), __get_long_cap())` -> uses even size (see `__get_long_cap`) This should fix incorrect deallocation sizes for some instances of std::string. Memory profiling or debugging tools like AddressSanitizer, LeakSanitizer or TCMalloc could then complain about the the size passed to a deallocation not matching the size originally passed to the allocation. Reported by: Aliaksei Kandratsenka <alkondratenko@gmail.com> PR: 279560 MFC after: 3 days (cherry picked from commit ead8e4c081e5c4de4d508fc353f381457b058ca6) contrib/llvm-project/libcxx/include/string | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
A commit in branch stable/13 references this bug: URL: https://cgit.FreeBSD.org/src/commit/?id=ef4d145057c10e452ac07b3111b4b4d0c5382fb5 commit ef4d145057c10e452ac07b3111b4b4d0c5382fb5 Author: Dimitry Andric <dim@FreeBSD.org> AuthorDate: 2024-06-07 18:42:53 +0000 Commit: Dimitry Andric <dim@FreeBSD.org> CommitDate: 2024-06-10 07:29:30 +0000 Merge commit 382f70a877f0 from llvm-project (by Louis Dionne): [libc++][NFC] Rewrite function call on two lines for clarity (#79141) Previously, there was a ternary conditional with a less-than comparison appearing inside a template argument, which was really confusing because of the <...> of the function template. This patch rewrites the same statement on two lines for clarity. Merge commit d129ea8d2fa3 from llvm-project (by Vitaly Buka): [libcxx] Align `__recommend() + 1` by __endian_factor (#90292) This is detected by asan after #83774 Allocation size will be divided by `__endian_factor` before storing. If it's not aligned, we will not be able to recover allocation size to pass into `__alloc_traits::deallocate`. we have code like this ``` auto __allocation = std::__allocate_at_least(__alloc(), __recommend(__sz) + 1); __p = __allocation.ptr; __set_long_cap(__allocation.count); void __set_long_cap(size_type __s) _NOEXCEPT { __r_.first().__l.__cap_ = __s / __endian_factor; __r_.first().__l.__is_long_ = true; } size_type __get_long_cap() const _NOEXCEPT { return __r_.first().__l.__cap_ * __endian_factor; } inline ~basic_string() { __annotate_delete(); if (__is_long()) __alloc_traits::deallocate(__alloc(), __get_long_pointer(), __get_long_cap()); } ``` 1. __recommend() -> even size 2. `std::__allocate_at_least(__alloc(), __recommend(__sz) + 1)` - > not even size 3. ` __set_long_cap() `- > lose one bit of size for __endian_factor == 2 (see `/ __endian_factor`) 4. `__alloc_traits::deallocate(__alloc(), __get_long_pointer(), __get_long_cap())` -> uses even size (see `__get_long_cap`) This should fix incorrect deallocation sizes for some instances of std::string. Memory profiling or debugging tools like AddressSanitizer, LeakSanitizer or TCMalloc could then complain about the the size passed to a deallocation not matching the size originally passed to the allocation. Reported by: Aliaksei Kandratsenka <alkondratenko@gmail.com> PR: 279560 MFC after: 3 days (cherry picked from commit ead8e4c081e5c4de4d508fc353f381457b058ca6) contrib/llvm-project/libcxx/include/string | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
A commit in branch releng/14.1 references this bug: URL: https://cgit.FreeBSD.org/src/commit/?id=8e0e6b428cb8ba01941e53256880f7993671e786 commit 8e0e6b428cb8ba01941e53256880f7993671e786 Author: Dimitry Andric <dim@FreeBSD.org> AuthorDate: 2024-06-07 18:42:53 +0000 Commit: Gordon Tetlow <gordon@FreeBSD.org> CommitDate: 2024-06-18 17:36:40 +0000 Merge commit 382f70a877f0 from llvm-project (by Louis Dionne): [libc++][NFC] Rewrite function call on two lines for clarity (#79141) Previously, there was a ternary conditional with a less-than comparison appearing inside a template argument, which was really confusing because of the <...> of the function template. This patch rewrites the same statement on two lines for clarity. Merge commit d129ea8d2fa3 from llvm-project (by Vitaly Buka): [libcxx] Align `__recommend() + 1` by __endian_factor (#90292) This is detected by asan after #83774 Allocation size will be divided by `__endian_factor` before storing. If it's not aligned, we will not be able to recover allocation size to pass into `__alloc_traits::deallocate`. we have code like this ``` auto __allocation = std::__allocate_at_least(__alloc(), __recommend(__sz) + 1); __p = __allocation.ptr; __set_long_cap(__allocation.count); void __set_long_cap(size_type __s) _NOEXCEPT { __r_.first().__l.__cap_ = __s / __endian_factor; __r_.first().__l.__is_long_ = true; } size_type __get_long_cap() const _NOEXCEPT { return __r_.first().__l.__cap_ * __endian_factor; } inline ~basic_string() { __annotate_delete(); if (__is_long()) __alloc_traits::deallocate(__alloc(), __get_long_pointer(), __get_long_cap()); } ``` 1. __recommend() -> even size 2. `std::__allocate_at_least(__alloc(), __recommend(__sz) + 1)` - > not even size 3. ` __set_long_cap() `- > lose one bit of size for __endian_factor == 2 (see `/ __endian_factor`) 4. `__alloc_traits::deallocate(__alloc(), __get_long_pointer(), __get_long_cap())` -> uses even size (see `__get_long_cap`) This should fix incorrect deallocation sizes for some instances of std::string. Memory profiling or debugging tools like AddressSanitizer, LeakSanitizer or TCMalloc could then complain about the the size passed to a deallocation not matching the size originally passed to the allocation. Reported by: Aliaksei Kandratsenka <alkondratenko@gmail.com> PR: 279560 Approved by: so Security: FreeBSD-EN-24:13.libc++ (cherry picked from commit ead8e4c081e5c4de4d508fc353f381457b058ca6) (cherry picked from commit 55c5dad2f305f74d1ff5ca85c453635511aab9b2) contrib/llvm-project/libcxx/include/string | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)