Skip to content

[libc++] Fix basic_string::shrink_to_fit for constant evaluation #142712

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

frederick-vs-ja
Copy link
Contributor

@frederick-vs-ja frederick-vs-ja commented Jun 4, 2025

Currently, when the string shrink into the SSO buffer, the __rep_.__s member isn't activated before the traits_type::copy call
yet, so internal __builtin_memmove call writing to the buffer causes constant evaluation failure. The existing test coverage seems a bit defective and doesn't cover this case - shrink_to_fit is called on the copy of string after erasure, not the original string object.

This PR reorders the __set_short_size call, which starts the lifetime of the SSO buffer, before the copy operation. Test coverage is achieved by calling shrink_to_fit on the original erased string.

Currently, when the string shrink into the SSO buffer, the lifetime of
the buffer hasn't begun before copying characters into it, so the
subsequent copy operation raises UB and thus causes constant evaluation
failure.

The existing test coverage seems a bit defective  - `shrink_to_fit` is
called on the copy of string after erasure, not the original string
object.

This PR reorders the `__set_short_size` call, which starts the lifetime
of the SSO buffer,  before the copy operation. Test coverage is achieved
by calling `shrink_to_fit` on the original erased string.
@frederick-vs-ja frederick-vs-ja requested a review from a team as a code owner June 4, 2025 02:26
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Jun 4, 2025
@llvmbot
Copy link
Member

llvmbot commented Jun 4, 2025

@llvm/pr-subscribers-libcxx

Author: A. Jiang (frederick-vs-ja)

Changes

Currently, when the string shrink into the SSO buffer, the lifetime of the buffer hasn't begun before copying characters into it, so the subsequent copy operation raises UB and thus causes constant evaluation failure.

The existing test coverage seems a bit defective - shrink_to_fit is called on the copy of string after erasure, not the original string object.

This PR reorders the __set_short_size call, which starts the lifetime of the SSO buffer, before the copy operation. Test coverage is achieved by calling shrink_to_fit on the original erased string.


Full diff: https://github.com/llvm/llvm-project/pull/142712.diff

2 Files Affected:

  • (modified) libcxx/include/string (+1-1)
  • (modified) libcxx/test/std/strings/basic.string/string.capacity/shrink_to_fit.pass.cpp (+1-1)
diff --git a/libcxx/include/string b/libcxx/include/string
index 4f05e211919f3..67c4c537eb3b5 100644
--- a/libcxx/include/string
+++ b/libcxx/include/string
@@ -3434,8 +3434,8 @@ inline _LIBCPP_CONSTEXPR_SINCE_CXX20 void basic_string<_CharT, _Traits, _Allocat
 
   if (__fits_in_sso(__target_capacity)) {
     __annotation_guard __g(*this);
-    traits_type::copy(std::__to_address(__get_short_pointer()), std::__to_address(__ptr), __size + 1);
     __set_short_size(__size);
+    traits_type::copy(std::__to_address(__get_short_pointer()), std::__to_address(__ptr), __size + 1);
     __alloc_traits::deallocate(__alloc_, __ptr, __cap);
     return;
   }
diff --git a/libcxx/test/std/strings/basic.string/string.capacity/shrink_to_fit.pass.cpp b/libcxx/test/std/strings/basic.string/string.capacity/shrink_to_fit.pass.cpp
index 9ca7825a4ba92..a3b16c8da16cb 100644
--- a/libcxx/test/std/strings/basic.string/string.capacity/shrink_to_fit.pass.cpp
+++ b/libcxx/test/std/strings/basic.string/string.capacity/shrink_to_fit.pass.cpp
@@ -19,7 +19,7 @@
 #include "test_macros.h"
 
 template <class S>
-TEST_CONSTEXPR_CXX20 void test(S s) {
+TEST_CONSTEXPR_CXX20 void test(S& s) {
   typename S::size_type old_cap = s.capacity();
   S s0                          = s;
   s.shrink_to_fit();

Copy link
Contributor

@philnik777 philnik777 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, when the string shrink into the SSO buffer, the lifetime of the buffer hasn't begun before copying characters into it, so the subsequent copy operation raises UB and thus causes constant evaluation failure.

This is incorrect. It's not UB, since we know that CharT is an implicit lifetime type, so we start the lifetime when copying the elements.

LGTM with updated commit message.

@frederick-vs-ja
Copy link
Contributor Author

Currently, when the string shrink into the SSO buffer, the lifetime of the buffer hasn't begun before copying characters into it, so the subsequent copy operation raises UB and thus causes constant evaluation failure.

This is incorrect. It's not UB, since we know that CharT is an implicit lifetime type, so we start the lifetime when copying the elements.

LGTM with updated commit message.

Yeah. Changed to the following. Is it OK enough?

Currently, when the string shrink into the SSO buffer, the __rep_.__s member isn't activated before the traits_type::copy call
yet, so the __get_short_pointer reads an inactive member and then causes constant evaluation failure.

@philnik777
Copy link
Contributor

Currently, when the string shrink into the SSO buffer, the lifetime of the buffer hasn't begun before copying characters into it, so the subsequent copy operation raises UB and thus causes constant evaluation failure.

This is incorrect. It's not UB, since we know that CharT is an implicit lifetime type, so we start the lifetime when copying the elements.
LGTM with updated commit message.

Yeah. Changed to the following. Is it OK enough?

Currently, when the string shrink into the SSO buffer, the __rep_.__s member isn't activated before the traits_type::copy call
yet, so the __get_short_pointer reads an inactive member and then causes constant evaluation failure.

Does the error occur in __get_short_pointer? IIRC that should only return a pointer to an inactive member, not actually read it. That might not be allowed during constant evaluation either, I can't remember.

@frederick-vs-ja
Copy link
Contributor Author

Currently, when the string shrink into the SSO buffer, the lifetime of the buffer hasn't begun before copying characters into it, so the subsequent copy operation raises UB and thus causes constant evaluation failure.

This is incorrect. It's not UB, since we know that CharT is an implicit lifetime type, so we start the lifetime when copying the elements.
LGTM with updated commit message.

Yeah. Changed to the following. Is it OK enough?

Currently, when the string shrink into the SSO buffer, the __rep_.__s member isn't activated before the traits_type::copy call
yet, so the __get_short_pointer reads an inactive member and then causes constant evaluation failure.

Does the error occur in __get_short_pointer? IIRC that should only return a pointer to an inactive member, not actually read it. That might not be allowed during constant evaluation either, I can't remember.

Oops, I was confused and wrong. The error can be reproduced here: https://godbolt.org/z/MjMsf488W.

so internal __builtin_memmove call writing to the buffer causes constant evaluation failure

@frederick-vs-ja frederick-vs-ja merged commit 479f992 into llvm:main Jun 4, 2025
136 of 137 checks passed
@frederick-vs-ja frederick-vs-ja deleted the fix-constexpr-string-shrink_to_fit branch June 4, 2025 23:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants