Skip to content

[libc++] Fix improper static_cast in std::deque and __split_buffer #119106

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

Merged
merged 1 commit into from
Dec 13, 2024

Conversation

winner245
Copy link
Contributor

@winner245 winner245 commented Dec 8, 2024

This PR addresses the improper use of static_cast to size_t where size_type is intended. Although the size_type member type of STL containers is usually a synonym of std::size_t, there is no guarantee that they are always equivalent. The C++ standard does not mandate this equivalence.

In libc++'s implementations of std::deque, std::vector, and __split_buffer, the size_type member type is defined as std::allocator_traits<allocator_type>::size_type, which is either allocator_type::size_type if available or std::make_unsigned<difference_type>::type. While it is true for std::allocator that the size_type member type is std::size_t, for user-defined allocator types, they may mismatch. This justifies the need to replace static_cast<size_t> with static_cast<size_type> in this PR.

@winner245 winner245 force-pushed the fix-deque-static_cast branch 2 times, most recently from b5ebd35 to 2fbcddc Compare December 8, 2024 12:25
@winner245 winner245 force-pushed the fix-deque-static_cast branch from 2fbcddc to aa3cfef Compare December 8, 2024 15:44
@winner245 winner245 marked this pull request as ready for review December 9, 2024 14:43
@winner245 winner245 requested a review from a team as a code owner December 9, 2024 14:43
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Dec 9, 2024
@llvmbot
Copy link
Member

llvmbot commented Dec 9, 2024

@llvm/pr-subscribers-libcxx

Author: Peng Liu (winner245)

Changes

This PR addresses the improper use of static_cast to size_t where size_type is intended. Although the size_type member type of STL containers is usually a synonym of std::size_t, there is no guarantee that they are always equivalent. The C++ standard does not mandate this equivalence.

In libc++'s implementations of std::deque, std::vector, and __split_buffer, the size_type member type is defined as std::allocator_traits&lt;allocator_type&gt;::size_type, which is either allocator_type::size_type if available or std::make_unsigned&lt;difference_type&gt;::type. While it is true for std::allocator that the size_type member type is std::size_t, for user-defined allocator types, they may mismatch. This justifies the need to replace static_cast&lt;size_t&gt; with static_cast&lt;size_type&gt; in this PR.


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

2 Files Affected:

  • (modified) libcxx/include/__split_buffer (+2-2)
  • (modified) libcxx/include/deque (+2-2)
diff --git a/libcxx/include/__split_buffer b/libcxx/include/__split_buffer
index a637c83d17d107..5ee70220b55ab1 100644
--- a/libcxx/include/__split_buffer
+++ b/libcxx/include/__split_buffer
@@ -435,7 +435,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 void __split_buffer<_Tp, _Allocator>::emplace_fron
       __begin_            = std::move_backward(__begin_, __end_, __end_ + __d);
       __end_ += __d;
     } else {
-      size_type __c = std::max<size_type>(2 * static_cast<size_t>(__cap_ - __first_), 1);
+      size_type __c = std::max<size_type>(2 * static_cast<size_type>(__cap_ - __first_), 1);
       __split_buffer<value_type, __alloc_rr&> __t(__c, (__c + 3) / 4, __alloc_);
       __t.__construct_at_end(move_iterator<pointer>(__begin_), move_iterator<pointer>(__end_));
       std::swap(__first_, __t.__first_);
@@ -458,7 +458,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 void __split_buffer<_Tp, _Allocator>::emplace_back
       __end_              = std::move(__begin_, __end_, __begin_ - __d);
       __begin_ -= __d;
     } else {
-      size_type __c = std::max<size_type>(2 * static_cast<size_t>(__cap_ - __first_), 1);
+      size_type __c = std::max<size_type>(2 * static_cast<size_type>(__cap_ - __first_), 1);
       __split_buffer<value_type, __alloc_rr&> __t(__c, __c / 4, __alloc_);
       __t.__construct_at_end(move_iterator<pointer>(__begin_), move_iterator<pointer>(__end_));
       std::swap(__first_, __t.__first_);
diff --git a/libcxx/include/deque b/libcxx/include/deque
index ad667503489741..f44eeb3b2de81a 100644
--- a/libcxx/include/deque
+++ b/libcxx/include/deque
@@ -2431,7 +2431,7 @@ typename deque<_Tp, _Allocator>::iterator deque<_Tp, _Allocator>::erase(const_it
   difference_type __pos = __f - __b;
   iterator __p          = __b + __pos;
   allocator_type& __a   = __alloc();
-  if (static_cast<size_t>(__pos) <= (size() - 1) / 2) { // erase from front
+  if (static_cast<size_type>(__pos) <= (size() - 1) / 2) { // erase from front
     std::move_backward(__b, __p, std::next(__p));
     __alloc_traits::destroy(__a, std::addressof(*__b));
     --__size();
@@ -2459,7 +2459,7 @@ typename deque<_Tp, _Allocator>::iterator deque<_Tp, _Allocator>::erase(const_it
   iterator __p          = __b + __pos;
   if (__n > 0) {
     allocator_type& __a = __alloc();
-    if (static_cast<size_t>(__pos) <= (size() - __n) / 2) { // erase from front
+    if (static_cast<size_type>(__pos) <= (size() - __n) / 2) { // erase from front
       iterator __i = std::move_backward(__b, __p, __p + __n);
       for (; __b != __i; ++__b)
         __alloc_traits::destroy(__a, std::addressof(*__b));

@winner245 winner245 changed the title Fix improper static_cast in std::deque and __split_buffer [libc++] Fix improper static_cast in std::deque and __split_buffer Dec 9, 2024
Copy link
Member

@ldionne ldionne left a comment

Choose a reason for hiding this comment

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

This makes sense to me, but would it make sense to try to exercise this with a test?

@winner245
Copy link
Contributor Author

Thank you for the feedback. I am not sure how to add a suitable test for this purpose. I have tried to find examples that can trigger issues due to the improper static_cast, but I couldn't find a good one. The reason is that casting an integral value to std::size_t instead of size_type generally only enlarges the data range (since any practical size_type cannot exceed std::size_t), which does not cause data loss.

However, the improper static_cast I pointed out in this PR is indeed redundant. For example, in the following code, the explicit static_cast<size_t> first casts to size_t. But it then implicitly casts to size_type due to the explicit template argument specified as size_type:

size_type __c = std::max<size_type>(2 * static_cast<size_t>(__cap_ - __first_), 1);

I have also experimented with some custom allocators with different size_type definitions, e.g., uint_8, uint_16 (https://godbolt.org/z/cdse8Gh48). I found that std::vector strictly respects max_size() when calling reserve() and resize(), but std::deque seems to have completely ignored max_size() (perhaps because it is not a contiguous container). I am not sure if we should add a test similar to what I have done with the custom allocator.

@ldionne ldionne merged commit 979e936 into llvm:main Dec 13, 2024
62 of 64 checks passed
@winner245 winner245 deleted the fix-deque-static_cast branch December 13, 2024 16:36
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