Skip to content

[libc++] Fix copy_backward for vector<bool> with small storage types #131560

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
Mar 19, 2025

Conversation

winner245
Copy link
Contributor

@winner245 winner245 commented Mar 17, 2025

This patch fixes an issue in libc++ where std::copy_backward and ranges::copy_backward incorrectly copy std::vector<bool> with small storage types (e.g., uint8_t, uint16_t). The problem arises from flawed bit mask computations involving integral promotions and sign-bit extension, leading to unintended zeroing of bits. This patch corrects the bitwise operations to ensure correct bit-level copying.

Fixes #131718.

@winner245 winner245 force-pushed the fix-copy_backward branch 2 times, most recently from e476d1c to d4e2246 Compare March 17, 2025 21:53
@winner245 winner245 marked this pull request as ready for review March 18, 2025 16:02
@winner245 winner245 requested a review from a team as a code owner March 18, 2025 16:02
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Mar 18, 2025
@llvmbot
Copy link
Member

llvmbot commented Mar 18, 2025

@llvm/pr-subscribers-libcxx

Author: Peng Liu (winner245)

Changes

This patch fixes an issue in libc++ where std::copy_backward and ranges::copy_backward incorrectly copy std::vector&lt;bool&gt; with small storage types (e.g., uint8_t, uint16_t). The problem arises from flawed bit mask computations involving integral promotions and sign-bit extension, leading to unintended zeroing of bits. This patch corrects the bitwise operations to ensure correct bit-level copying.

Fixes #131718.


Patch is 21.53 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/131560.diff

5 Files Affected:

  • (modified) libcxx/include/__algorithm/copy_backward.h (+9-9)
  • (modified) libcxx/include/__bit_reference (+9-2)
  • (modified) libcxx/include/__fwd/bit_reference.h (+3)
  • (modified) libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/copy_backward.pass.cpp (+143)
  • (modified) libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_backward.pass.cpp (+141)
diff --git a/libcxx/include/__algorithm/copy_backward.h b/libcxx/include/__algorithm/copy_backward.h
index 02ffc14361e6a..9f890645a41d3 100644
--- a/libcxx/include/__algorithm/copy_backward.h
+++ b/libcxx/include/__algorithm/copy_backward.h
@@ -52,7 +52,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __bit_iterator<_Cp, false> _
       difference_type __dn = std::min(static_cast<difference_type>(__last.__ctz_), __n);
       __n -= __dn;
       unsigned __clz     = __bits_per_word - __last.__ctz_;
-      __storage_type __m = (~__storage_type(0) << (__last.__ctz_ - __dn)) & (~__storage_type(0) >> __clz);
+      __storage_type __m = std::__middle_mask<__storage_type>(__clz, __last.__ctz_ - __dn);
       __storage_type __b = *__last.__seg_ & __m;
       *__result.__seg_ &= ~__m;
       *__result.__seg_ |= __b;
@@ -69,7 +69,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __bit_iterator<_Cp, false> _
     __n -= __nw * __bits_per_word;
     // do last word
     if (__n > 0) {
-      __storage_type __m = ~__storage_type(0) << (__bits_per_word - __n);
+      __storage_type __m = std::__leading_mask<__storage_type>(__bits_per_word - __n);
       __storage_type __b = *--__last.__seg_ & __m;
       *--__result.__seg_ &= ~__m;
       *__result.__seg_ |= __b;
@@ -94,12 +94,12 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __bit_iterator<_Cp, false> _
       difference_type __dn = std::min(static_cast<difference_type>(__last.__ctz_), __n);
       __n -= __dn;
       unsigned __clz_l     = __bits_per_word - __last.__ctz_;
-      __storage_type __m   = (~__storage_type(0) << (__last.__ctz_ - __dn)) & (~__storage_type(0) >> __clz_l);
+      __storage_type __m   = std::__middle_mask<__storage_type>(__clz_l, __last.__ctz_ - __dn);
       __storage_type __b   = *__last.__seg_ & __m;
       unsigned __clz_r     = __bits_per_word - __result.__ctz_;
       __storage_type __ddn = std::min(__dn, static_cast<difference_type>(__result.__ctz_));
       if (__ddn > 0) {
-        __m = (~__storage_type(0) << (__result.__ctz_ - __ddn)) & (~__storage_type(0) >> __clz_r);
+        __m = std::__middle_mask<__storage_type>(__clz_r, __result.__ctz_ - __ddn);
         *__result.__seg_ &= ~__m;
         if (__result.__ctz_ > __last.__ctz_)
           *__result.__seg_ |= __b << (__result.__ctz_ - __last.__ctz_);
@@ -112,7 +112,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __bit_iterator<_Cp, false> _
         // __result.__ctz_ == 0
         --__result.__seg_;
         __result.__ctz_ = static_cast<unsigned>(-__dn & (__bits_per_word - 1));
-        __m             = ~__storage_type(0) << __result.__ctz_;
+        __m             = std::__leading_mask<__storage_type>(__result.__ctz_);
         *__result.__seg_ &= ~__m;
         __last.__ctz_ -= __dn + __ddn;
         *__result.__seg_ |= __b << (__result.__ctz_ - __last.__ctz_);
@@ -123,7 +123,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __bit_iterator<_Cp, false> _
     // __result.__ctz_ != 0 || __n == 0
     // do middle words
     unsigned __clz_r   = __bits_per_word - __result.__ctz_;
-    __storage_type __m = ~__storage_type(0) >> __clz_r;
+    __storage_type __m = std::__trailing_mask<__storage_type>(__clz_r);
     for (; __n >= __bits_per_word; __n -= __bits_per_word) {
       __storage_type __b = *--__last.__seg_;
       *__result.__seg_ &= ~__m;
@@ -133,11 +133,11 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __bit_iterator<_Cp, false> _
     }
     // do last word
     if (__n > 0) {
-      __m                 = ~__storage_type(0) << (__bits_per_word - __n);
+      __m                 = std::__leading_mask<__storage_type>(__bits_per_word - __n);
       __storage_type __b  = *--__last.__seg_ & __m;
       __clz_r             = __bits_per_word - __result.__ctz_;
       __storage_type __dn = std::min(__n, static_cast<difference_type>(__result.__ctz_));
-      __m                 = (~__storage_type(0) << (__result.__ctz_ - __dn)) & (~__storage_type(0) >> __clz_r);
+      __m                 = std::__middle_mask<__storage_type>(__clz_r, __result.__ctz_ - __dn);
       *__result.__seg_ &= ~__m;
       *__result.__seg_ |= __b >> (__bits_per_word - __result.__ctz_);
       __result.__ctz_ = static_cast<unsigned>(((-__dn & (__bits_per_word - 1)) + __result.__ctz_) % __bits_per_word);
@@ -146,7 +146,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __bit_iterator<_Cp, false> _
         // __result.__ctz_ == 0
         --__result.__seg_;
         __result.__ctz_ = static_cast<unsigned>(-__n & (__bits_per_word - 1));
-        __m             = ~__storage_type(0) << __result.__ctz_;
+        __m             = std::__leading_mask<__storage_type>(__result.__ctz_);
         *__result.__seg_ &= ~__m;
         *__result.__seg_ |= __b << (__result.__ctz_ - (__bits_per_word - __n - __dn));
       }
diff --git a/libcxx/include/__bit_reference b/libcxx/include/__bit_reference
index 58cbd4d51c88e..f5c22fc0a3ade 100644
--- a/libcxx/include/__bit_reference
+++ b/libcxx/include/__bit_reference
@@ -82,13 +82,20 @@ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _StorageType __trailing_mask
   return static_cast<_StorageType>(~static_cast<_StorageType>(0)) >> __clz;
 }
 
+// Creates a mask of type `_StorageType` with a specified number of trailing zeros (__ctz) and sets all remaining
+// bits to one.
+template <class _StorageType>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _StorageType __leading_mask(unsigned __ctz) {
+  static_assert(is_unsigned<_StorageType>::value, "__leading_mask only works with unsigned types");
+  return static_cast<_StorageType>(~static_cast<_StorageType>(0)) << __ctz;
+}
+
 // Creates a mask of type `_StorageType` with a specified number of leading zeros (__clz), a specified number of
 // trailing zeros (__ctz), and sets all bits in between to one.
 template <class _StorageType>
 _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _StorageType __middle_mask(unsigned __clz, unsigned __ctz) {
   static_assert(is_unsigned<_StorageType>::value, "__middle_mask only works with unsigned types");
-  return (static_cast<_StorageType>(~static_cast<_StorageType>(0)) << __ctz) &
-         std::__trailing_mask<_StorageType>(__clz);
+  return std::__leading_mask<_StorageType>(__ctz) & std::__trailing_mask<_StorageType>(__clz);
 }
 
 // This function is designed to operate correctly even for smaller integral types like `uint8_t`, `uint16_t`,
diff --git a/libcxx/include/__fwd/bit_reference.h b/libcxx/include/__fwd/bit_reference.h
index e212667f3de1f..99d1fdbc41959 100644
--- a/libcxx/include/__fwd/bit_reference.h
+++ b/libcxx/include/__fwd/bit_reference.h
@@ -33,6 +33,9 @@ __fill_masked_range(_StoragePointer __word, unsigned __ctz, unsigned __clz, bool
 template <class _StorageType>
 _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _StorageType __trailing_mask(unsigned __clz);
 
+template <class _StorageType>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _StorageType __leading_mask(unsigned __ctz);
+
 template <class _StorageType>
 _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _StorageType __middle_mask(unsigned __clz, unsigned __ctz);
 
diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/copy_backward.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/copy_backward.pass.cpp
index 8a528a96f5294..def192d4d6637 100644
--- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/copy_backward.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/copy_backward.pass.cpp
@@ -13,10 +13,13 @@
 //   constexpr OutIter   // constexpr after C++17
 //   copy_backward(InIter first, InIter last, OutIter result);
 
+// XFAIL: FROZEN-CXX03-HEADERS-FIXME
+
 #include <algorithm>
 #include <cassert>
 #include <vector>
 
+#include "sized_allocator.h"
 #include "test_macros.h"
 #include "test_iterators.h"
 #include "type_algorithms.h"
@@ -111,6 +114,146 @@ TEST_CONSTEXPR_CXX20 bool test() {
     assert(test_vector_bool(256));
   }
 
+  // Validate std::copy_backward with std::vector<bool> iterators and custom storage types.
+  // Ensure that assigned bits hold the intended values, while unassigned bits stay unchanged.
+  // Related issue: https://github.com/llvm/llvm-project/issues/131718.
+  {
+    //// Tests for std::copy_backward with aligned bits
+
+    { // Test the first (partial) word for uint8_t
+      using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+      std::vector<bool, Alloc> in(7, false, Alloc(1));
+      std::vector<bool, Alloc> out(8, true, Alloc(1));
+      std::copy_backward(in.begin(), in.begin() + 1, out.begin() + 1);
+      assert(out[0] == false);
+      for (std::size_t i = 1; i < out.size(); ++i)
+        assert(out[i] == true);
+    }
+    { // Test the last (partial) word for uint8_t
+      using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+      std::vector<bool, Alloc> in(8, false, Alloc(1));
+      for (std::size_t i = 0; i < in.size(); i += 2)
+        in[i] = true;
+      std::vector<bool, Alloc> out(8, true, Alloc(1));
+      std::copy_backward(in.end() - 4, in.end(), out.end());
+      for (std::size_t i = 0; i < static_cast<std::size_t>(in.size() - 4); ++i)
+        assert(out[i] == true);
+      for (std::size_t i = in.size() + 4; i < out.size(); ++i)
+        assert(in[i] == out[i]);
+    }
+    { // Test the middle (whole) words for uint8_t
+      using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+      std::vector<bool, Alloc> in(17, false, Alloc(1));
+      for (std::size_t i = 0; i < in.size(); i += 2)
+        in[i] = true;
+      std::vector<bool, Alloc> out(24, true, Alloc(1));
+      std::copy_backward(in.begin(), in.end(), out.begin() + in.size());
+      for (std::size_t i = 0; i < in.size(); ++i)
+        assert(in[i] == out[i]);
+      for (std::size_t i = in.size(); i < out.size(); ++i)
+        assert(out[i] == true);
+    }
+
+    { // Test the first (partial) word for uint16_t
+      using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+      std::vector<bool, Alloc> in(14, false, Alloc(1));
+      std::vector<bool, Alloc> out(16, true, Alloc(1));
+      std::copy_backward(in.begin(), in.begin() + 2, out.begin() + 2);
+      assert(out[0] == false);
+      assert(out[1] == false);
+      for (std::size_t i = 2; i < out.size(); ++i)
+        assert(out[i] == true);
+    }
+    { // Test the last (partial) word for uint16_t
+      using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+      std::vector<bool, Alloc> in(16, false, Alloc(1));
+      for (std::size_t i = 0; i < in.size(); i += 2)
+        in[i] = true;
+      std::vector<bool, Alloc> out(16, true, Alloc(1));
+      std::copy_backward(in.end() - 8, in.end(), out.end());
+      for (std::size_t i = 0; i < static_cast<std::size_t>(in.size() - 8); ++i)
+        assert(out[i] == true);
+      for (std::size_t i = in.size() + 8; i < out.size(); ++i)
+        assert(in[i] == out[i]);
+    }
+    { // Test the middle (whole) words for uint16_t
+      using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+      std::vector<bool, Alloc> in(34, false, Alloc(1));
+      for (std::size_t i = 0; i < in.size(); i += 2)
+        in[i] = true;
+      std::vector<bool, Alloc> out(48, true, Alloc(1));
+      std::copy_backward(in.begin(), in.end(), out.begin() + in.size());
+      for (std::size_t i = 0; i < in.size(); ++i)
+        assert(in[i] == out[i]);
+      for (std::size_t i = in.size(); i < out.size(); ++i)
+        assert(out[i] == true);
+    }
+
+    //// Tests for std::copy_backward with unaligned bits
+
+    { // Test the first (partial) word for uint8_t
+      using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+      std::vector<bool, Alloc> in(8, false, Alloc(1));
+      std::vector<bool, Alloc> out(8, true, Alloc(1));
+      std::copy_backward(in.begin(), in.begin() + 1, out.begin() + 1);
+      assert(out[0] == false);
+      for (std::size_t i = 1; i < out.size(); ++i)
+        assert(out[i] == true);
+    }
+    { // Test the last (partial) word for uint8_t
+      using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+      std::vector<bool, Alloc> in(8, false, Alloc(1));
+      std::vector<bool, Alloc> out(8, true, Alloc(1));
+      std::copy_backward(in.end() - 1, in.end(), out.begin() + 1);
+      assert(out[0] == false);
+      for (std::size_t i = 1; i < out.size(); ++i)
+        assert(out[i] == true);
+    }
+    { // Test the middle (whole) words for uint8_t
+      using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+      std::vector<bool, Alloc> in(16, false, Alloc(1));
+      for (std::size_t i = 0; i < in.size(); i += 2)
+        in[i] = true;
+      std::vector<bool, Alloc> out(17, true, Alloc(1));
+      std::copy_backward(in.begin(), in.end(), out.end());
+      assert(out[0] == true);
+      for (std::size_t i = 0; i < in.size(); ++i)
+        assert(in[i] == out[i + 1]);
+    }
+
+    { // Test the first (partial) word for uint16_t
+      using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+      std::vector<bool, Alloc> in(16, false, Alloc(1));
+      std::vector<bool, Alloc> out(16, true, Alloc(1));
+      std::copy_backward(in.begin(), in.begin() + 2, out.begin() + 2);
+      assert(out[0] == false);
+      assert(out[1] == false);
+      for (std::size_t i = 2; i < out.size(); ++i)
+        assert(out[i] == true);
+    }
+    { // Test the last (partial) word for uint16_t
+      using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+      std::vector<bool, Alloc> in(16, false, Alloc(1));
+      std::vector<bool, Alloc> out(16, true, Alloc(1));
+      std::copy_backward(in.end() - 2, in.end(), out.begin() + 2);
+      assert(out[0] == false);
+      assert(out[1] == false);
+      for (std::size_t i = 2; i < out.size(); ++i)
+        assert(out[i] == true);
+    }
+    { // Test the middle (whole) words for uint16_t
+      using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+      std::vector<bool, Alloc> in(32, false, Alloc(1));
+      for (std::size_t i = 0; i < in.size(); i += 2)
+        in[i] = true;
+      std::vector<bool, Alloc> out(33, true, Alloc(1));
+      std::copy_backward(in.begin(), in.end(), out.end());
+      assert(out[0] == true);
+      for (std::size_t i = 0; i < in.size(); ++i)
+        assert(in[i] == out[i + 1]);
+    }
+  }
+
   return true;
 }
 
diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_backward.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_backward.pass.cpp
index a7fa3db23e6ba..e7251ab905db5 100644
--- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_backward.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_backward.pass.cpp
@@ -28,6 +28,7 @@
 #include <vector>
 
 #include "almost_satisfies_types.h"
+#include "sized_allocator.h"
 #include "test_iterators.h"
 #include "test_macros.h"
 
@@ -355,6 +356,146 @@ constexpr bool test() {
     assert(test_vector_bool(199));
     assert(test_vector_bool(256));
   }
+
+  // Validate std::ranges::copy_backward with std::vector<bool> iterators and custom storage types.
+  // Ensure that assigned bits hold the intended values, while unassigned bits stay unchanged.
+  // Related issue: https://github.com/llvm/llvm-project/issues/131718.
+  {
+    //// Tests for std::ranges::copy_backward with aligned bits
+
+    { // Test the first (partial) word for uint8_t
+      using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+      std::vector<bool, Alloc> in(7, false, Alloc(1));
+      std::vector<bool, Alloc> out(8, true, Alloc(1));
+      std::ranges::copy_backward(std::ranges::subrange(in.begin(), in.begin() + 1), out.begin() + 1);
+      assert(out[0] == false);
+      for (std::size_t i = 1; i < out.size(); ++i)
+        assert(out[i] == true);
+    }
+    { // Test the last (partial) word for uint8_t
+      using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+      std::vector<bool, Alloc> in(8, false, Alloc(1));
+      for (std::size_t i = 0; i < in.size(); i += 2)
+        in[i] = true;
+      std::vector<bool, Alloc> out(8, true, Alloc(1));
+      std::ranges::copy_backward(std::ranges::subrange(in.end() - 4, in.end()), out.end());
+      for (std::size_t i = 0; i < static_cast<std::size_t>(in.size() - 4); ++i)
+        assert(out[i] == true);
+      for (std::size_t i = in.size() + 4; i < out.size(); ++i)
+        assert(in[i] == out[i]);
+    }
+    { // Test the middle (whole) words for uint8_t
+      using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+      std::vector<bool, Alloc> in(17, false, Alloc(1));
+      for (std::size_t i = 0; i < in.size(); i += 2)
+        in[i] = true;
+      std::vector<bool, Alloc> out(24, true, Alloc(1));
+      std::ranges::copy_backward(std::ranges::subrange(in.begin(), in.end()), out.begin() + in.size());
+      for (std::size_t i = 0; i < in.size(); ++i)
+        assert(in[i] == out[i]);
+      for (std::size_t i = in.size(); i < out.size(); ++i)
+        assert(out[i] == true);
+    }
+
+    { // Test the first (partial) word for uint16_t
+      using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+      std::vector<bool, Alloc> in(14, false, Alloc(1));
+      std::vector<bool, Alloc> out(16, true, Alloc(1));
+      std::ranges::copy_backward(std::ranges::subrange(in.begin(), in.begin() + 2), out.begin() + 2);
+      assert(out[0] == false);
+      assert(out[1] == false);
+      for (std::size_t i = 2; i < out.size(); ++i)
+        assert(out[i] == true);
+    }
+    { // Test the last (partial) word for uint16_t
+      using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+      std::vector<bool, Alloc> in(16, false, Alloc(1));
+      for (std::size_t i = 0; i < in.size(); i += 2)
+        in[i] = true;
+      std::vector<bool, Alloc> out(16, true, Alloc(1));
+      std::ranges::copy_backward(std::ranges::subrange(in.end() - 8, in.end()), out.end());
+      for (std::size_t i = 0; i < static_cast<std::size_t>(in.size() - 8); ++i)
+        assert(out[i] == true);
+      for (std::size_t i = in.size() + 8; i < out.size(); ++i)
+        assert(in[i] == out[i]);
+    }
+    { // Test the middle (whole) words for uint16_t
+      using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+      std::vector<bool, Alloc> in(34, false, Alloc(1));
+      for (std::size_t i = 0; i < in.size(); i += 2)
+        in[i] = true;
+      std::vector<bool, Alloc> out(48, true, Alloc(1));
+      std::ranges::copy_backward(std::ranges::subrange(in.begin(), in.end()), out.begin() + in.size());
+      for (std::size_t i = 0; i < in.size(); ++i)
+        assert(in[i] == out[i]);
+      for (std::size_t i = in.size(); i < out.size(); ++i)
+        assert(out[i] == true);
+    }
+
+    //// Tests for std::ranges::copy_backward with unaligned bits
+
+    { // Test the first (partial) word for uint8_t
+      using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+      std::vector<bool, Alloc> in(8, false, Alloc(1));
+      std::vector<bool, Alloc> out(8, true, Alloc(1));
+      std::ranges::copy_backward(std::ranges::subrange(in.begin(), in.begin() + 1), out.begin() + 1);
+      assert(out[0] == false);
+      for (std::size_t i = 1; i < out.size(); ++i)
+        assert(out[i] == true);
+    }
+    { // Test the last (partial) word for uint8_t
+      using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+      std::vector<bool, Alloc> in(8, false, Alloc(1));
+      std::vector<bool, Alloc> out(8, true, Alloc(1));
+      std::ranges::copy_backward(std::ranges::subrange(in.end() - 1, in.end()), out.begin() + 1);
+      assert(out[0] == false);
+      for (std::size_t i = 1; i < out.size(); ++i)
+        assert(out[i] == true);
+    }
+    { // Test the middle (whole) words for uint8_t
+      using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+      std::vector<bool, Alloc> in(16, false, Alloc(1));
+      for (std::size_t i = 0; i < in.size(); i += 2)
+        in[i] = true;
+      std::vector<bool, Alloc> out(17, true, Alloc(1));
+      std::ranges::copy_backward(std::ranges::subrange(in.begin(), in.end()), out.end());
+      assert(out[0] == true);
+      for (std::size_t i = 0; i < in.size(); ++i)
+        assert(in[i] == out[i + 1]...
[truncated]

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.

Thanks for the patches, and especially for the structured approach to fixing all of these issues! This makes reviewing a lot easier and it also makes it easier to focus on a single problem at a time, which often has the side effect of increased quality of the fix + its tests.

@ldionne ldionne merged commit 029cb8a into llvm:main Mar 19, 2025
86 checks passed
@winner245 winner245 deleted the fix-copy_backward branch March 19, 2025 17:11
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.

[libc++] {std, ranges}::copy_backward fails to copy vector<bool> correctly with small storage types
3 participants