https://gcc.gnu.org/g:d4f7d18b3ed83a8265be913bb230f6efa395e527

commit r15-8902-gd4f7d18b3ed83a8265be913bb230f6efa395e527
Author: Jonathan Wakely <jwak...@redhat.com>
Date:   Fri Mar 21 23:21:42 2025 +0000

    libstdc++: Fix std::vector::append_range for overlapping ranges
    
    Unlike insert_range and assign_range, the append_range function does not
    have a precondition that the range doesn't overlap *this. That means we
    need to avoid relocating the existing elements until after copying from
    the range. This means I need to revert r15-8488-g3e1d760bf49d0e which
    made the from_range_t constructor use append_range, because the
    constructor can avoid the additional complexity needed by append_range.
    When relocating the existing elements in append_range we can use
    std::__relocate_a to do it more efficiently, if that's valid.
    
    std::vector<bool>::append_range needs similar treatment, although it's a
    bit simpler as we know that the elements are trivially copyable and so
    we don't need to worry about them throwing. assign_range doesn't allow
    overlapping ranges, so can be rewritten to be more efficient than
    calling append_range for the forward or sized range case.
    
    libstdc++-v3/ChangeLog:
    
            * include/bits/stl_bvector.h (vector::assign_range): More
            efficient implementation for forward/sized ranges.
            (vector::append_range): Handle potentially overlapping range.
            * include/bits/stl_vector.h (vector(from_range_t, R&&, Alloc)):
            Do not use append_range for non-sized input range case.
            (vector::append_range): Handle potentially overlapping range.
            * include/bits/vector.tcc (vector::insert_range): Forward range
            instead of moving it.
            * 
testsuite/23_containers/vector/bool/modifiers/insert/append_range.cc:
            Test overlapping ranges.
            * testsuite/23_containers/vector/modifiers/append_range.cc:
            Likewise.
    
    Reviewed-by: Tomasz KamiƄski <tkami...@redhat.com>

Diff:
---
 libstdc++-v3/include/bits/stl_bvector.h            |  77 ++++++++--
 libstdc++-v3/include/bits/stl_vector.h             | 110 +++++++++++++-
 libstdc++-v3/include/bits/vector.tcc               |   2 +-
 .../vector/bool/modifiers/insert/append_range.cc   |  51 +++++++
 .../23_containers/vector/modifiers/append_range.cc | 165 +++++++++++++++++++++
 5 files changed, 385 insertions(+), 20 deletions(-)

diff --git a/libstdc++-v3/include/bits/stl_bvector.h 
b/libstdc++-v3/include/bits/stl_bvector.h
index 3ee15eaa938c..03f6434604c3 100644
--- a/libstdc++-v3/include/bits/stl_bvector.h
+++ b/libstdc++-v3/include/bits/stl_bvector.h
@@ -899,6 +899,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
 #if __glibcxx_ranges_to_container // C++ >= 23
       /**
        * @brief Construct a vector from a range.
+       * @param __rg A range of values that are convertible to `value_type`.
        * @since C++23
        */
       template<__detail::__container_compatible_range<bool> _Rg>
@@ -1028,6 +1029,8 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
 #if __glibcxx_ranges_to_container // C++ >= 23
       /**
        * @brief Assign a range to the vector.
+       * @param __rg A range of values that are convertible to `value_type`.
+       * @pre `__rg` and `*this` do not overlap.
        * @since C++23
        */
       template<__detail::__container_compatible_range<bool> _Rg>
@@ -1035,8 +1038,25 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
        assign_range(_Rg&& __rg)
        {
          static_assert(assignable_from<bool&, ranges::range_reference_t<_Rg>>);
-         clear();
-         append_range(std::forward<_Rg>(__rg));
+         if constexpr (ranges::forward_range<_Rg> || ranges::sized_range<_Rg>)
+           {
+             if (auto __n = size_type(ranges::distance(__rg)))
+               {
+                 reserve(__n);
+                 this->_M_impl._M_finish
+                     = ranges::copy(std::forward<_Rg>(__rg), begin()).out;
+               }
+             else
+               clear();
+           }
+         else
+           {
+             clear();
+             auto __first = ranges::begin(__rg);
+             const auto __last = ranges::end(__rg);
+             for (; __first != __last; ++__first)
+               emplace_back(*__first);
+           }
        }
 #endif
 
@@ -1330,6 +1350,10 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
 #if __glibcxx_ranges_to_container // C++ >= 23
       /**
        * @brief Insert a range into the vector.
+       * @param __rg A range of values that are convertible to `bool`.
+       * @return An iterator that points to the first new element inserted,
+       *         or to `__pos` if `__rg` is an empty range.
+       * @pre `__rg` and `*this` do not overlap.
        * @since C++23
        */
       template<__detail::__container_compatible_range<bool> _Rg>
@@ -1385,24 +1409,53 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
        constexpr void
        append_range(_Rg&& __rg)
        {
+         // N.B. __rg may overlap with *this, so we must copy from __rg before
+         // existing elements or iterators referring to *this are invalidated.
+         // e.g. in v.append_range(views::concat(v, foo)) rg overlaps v.
          if constexpr (ranges::forward_range<_Rg> || ranges::sized_range<_Rg>)
            {
-             reserve(size() + size_type(ranges::distance(__rg)));
-             this->_M_impl._M_finish = ranges::copy(__rg, end()).out;
+             const auto __n = size_type(ranges::distance(__rg));
+
+             // If there is no existing storage, there are no iterators that
+             // can be referring to our storage, so it's safe to allocate now.
+             if (capacity() == 0)
+               reserve(__n);
+
+             const auto __sz = size();
+             const auto __capacity = capacity();
+             if ((__capacity - __sz) >= __n)
+               {
+                 this->_M_impl._M_finish
+                     = ranges::copy(std::forward<_Rg>(__rg), end()).out;
+                 return;
+               }
+
+             vector __tmp(get_allocator());
+             __tmp.reserve(_M_check_len(__n, "vector::append_range"));
+             __tmp._M_impl._M_finish
+                  = _M_copy_aligned(cbegin(), cend(), __tmp.begin());
+             __tmp._M_impl._M_finish
+                  = ranges::copy(std::forward<_Rg>(__rg), __tmp.end()).out;
+             swap(__tmp);
            }
          else
            {
              auto __first = ranges::begin(__rg);
              const auto __last = ranges::end(__rg);
-             size_type __n = size();
-             const size_type __cap = capacity();
-             for (; __first != __last && __n < __cap; ++__first, (void)++__n)
+
+             // Fill up to the end of current capacity.
+             for (auto __free = capacity() - size();
+                  __first != __last && __free > 0;
+                  ++__first, (void) --__free)
                emplace_back(*__first);
-             if (__first != __last)
-               {
-                 ranges::subrange __rest(std::move(__first), __last);
-                 append_range(vector(from_range, __rest, get_allocator()));
-               }
+
+             if (__first == __last)
+               return;
+
+             // Copy the rest of the range to a new vector.
+             ranges::subrange __rest(std::move(__first), __last);
+             vector __tmp(from_range, __rest, get_allocator());
+             insert(end(), __tmp.begin(), __tmp.end());
            }
        }
 #endif // ranges_to_container
diff --git a/libstdc++-v3/include/bits/stl_vector.h 
b/libstdc++-v3/include/bits/stl_vector.h
index 09fd53696d1d..21f6cd04f490 100644
--- a/libstdc++-v3/include/bits/stl_vector.h
+++ b/libstdc++-v3/include/bits/stl_vector.h
@@ -65,7 +65,7 @@
 #if __cplusplus >= 202002L
 # include <compare>
 #endif
-#if __cplusplus > 202002L
+#if __glibcxx_ranges_to_container // C++ >= 23
 # include <bits/ranges_algobase.h>      // ranges::copy
 # include <bits/ranges_util.h>          // ranges::subrange
 #endif
@@ -753,6 +753,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
 #if __glibcxx_ranges_to_container // C++ >= 23
       /**
        * @brief Construct a vector from a range.
+       * @param __rg A range of values that are convertible to `bool`.
        * @since C++23
        */
       template<__detail::__container_compatible_range<_Tp> _Rg>
@@ -771,7 +772,12 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
              _Base::_M_append_range(__rg);
            }
          else
-           append_range(std::move(__rg));
+           {
+             auto __first = ranges::begin(__rg);
+             const auto __last = ranges::end(__rg);
+             for (; __first != __last; ++__first)
+               emplace_back(*__first);
+           }
        }
 #endif
 
@@ -914,6 +920,8 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
 #if __glibcxx_ranges_to_container // C++ >= 23
       /**
        * @brief Assign a range to the vector.
+       * @param __rg A range of values that are convertible to `value_type`.
+       * @pre `__rg` and `*this` do not overlap.
        * @since C++23
        */
       template<__detail::__container_compatible_range<_Tp> _Rg>
@@ -1634,6 +1642,10 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
 #if __glibcxx_ranges_to_container // C++ >= 23
       /**
        * @brief Insert a range into the vector.
+       * @param __rg A range of values that are convertible to `value_type`.
+       * @return An iterator that points to the first new element inserted,
+       *         or to `__pos` if `__rg` is an empty range.
+       * @pre `__rg` and `*this` do not overlap.
        * @since C++23
        */
       template<__detail::__container_compatible_range<_Tp> _Rg>
@@ -1642,26 +1654,110 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
 
       /**
        * @brief Append a range at the end of the vector.
+       * @param __rg A range of values that are convertible to `value_type`.
        * @since C++23
        */
       template<__detail::__container_compatible_range<_Tp> _Rg>
        constexpr void
        append_range(_Rg&& __rg)
        {
+         // N.B. __rg may overlap with *this, so we must copy from __rg before
+         // existing elements or iterators referring to *this are invalidated.
+         // e.g. in v.append_range(views::concat(v, foo)) rg overlaps v.
          if constexpr (ranges::forward_range<_Rg> || ranges::sized_range<_Rg>)
            {
              const auto __n = size_type(ranges::distance(__rg));
-             reserve(size() + __n);
-             _GLIBCXX_ASAN_ANNOTATE_GROW(__n);
-             _Base::_M_append_range(__rg);
-             _GLIBCXX_ASAN_ANNOTATE_GREW(__n);
+
+             // If there is no existing storage, there are no iterators that
+             // can be referring to our storage, so it's safe to allocate now.
+             if (capacity() == 0)
+               reserve(__n);
+
+             const auto __sz = size();
+             const auto __capacity = capacity();
+             if ((__capacity - __sz) >= __n)
+               {
+                 _GLIBCXX_ASAN_ANNOTATE_GROW(__n);
+                 _Base::_M_append_range(__rg);
+                 _GLIBCXX_ASAN_ANNOTATE_GREW(__n);
+                 return;
+               }
+
+             const size_type __len = _M_check_len(__n, "vector::append_range");
+
+             pointer __old_start = this->_M_impl._M_start;
+             pointer __old_finish = this->_M_impl._M_finish;
+
+             allocator_type& __a = _M_get_Tp_allocator();
+             const pointer __start = this->_M_allocate(__len);
+             const pointer __mid = __start + __sz;
+             const pointer __back = __mid + __n;
+             _Guard_alloc __guard(__start, __len, *this);
+             std::__uninitialized_copy_a(ranges::begin(__rg),
+                                         ranges::end(__rg),
+                                         __mid, __a);
+
+             if constexpr (_S_use_relocate())
+               _S_relocate(__old_start, __old_finish, __start, __a);
+             else
+               {
+                 // RAII type to destroy initialized elements.
+                 struct _Guard_elts
+                 {
+                   pointer _M_first, _M_last;  // Elements to destroy
+                   _Tp_alloc_type& _M_alloc;
+
+                   constexpr
+                   _Guard_elts(pointer __f, pointer __l, _Tp_alloc_type& __a)
+                   : _M_first(__f), _M_last(__l), _M_alloc(__a)
+                   { }
+
+                   constexpr
+                   ~_Guard_elts()
+                   { std::_Destroy(_M_first, _M_last, _M_alloc); }
+
+                   _Guard_elts(_Guard_elts&&) = delete;
+                 };
+                 _Guard_elts __guard_elts{__mid, __back, __a};
+
+                 std::__uninitialized_move_a(__old_start, __old_finish,
+                                             __start, __a);
+
+                 // Let old elements get destroyed by __guard_elts:
+                 __guard_elts._M_first = __old_start;
+                 __guard_elts._M_last = __old_finish;
+               }
+
+             // Now give ownership of old storage to __guard:
+             __guard._M_storage = __old_start;
+             __guard._M_len = __capacity;
+             // Finally, take ownership of new storage:
+             this->_M_impl._M_start = __start;
+             this->_M_impl._M_finish = __back;
+             this->_M_impl._M_end_of_storage = __start + __len;
            }
          else
            {
              auto __first = ranges::begin(__rg);
              const auto __last = ranges::end(__rg);
-             for (; __first != __last; ++__first)
+
+             // Fill up to the end of current capacity.
+             for (auto __free = capacity() - size();
+                  __first != __last && __free > 0;
+                  ++__first, (void) --__free)
                emplace_back(*__first);
+
+             if (__first == __last)
+               return;
+
+             // Copy the rest of the range to a new vector.
+             vector __tmp(_M_get_Tp_allocator());
+             for (; __first != __last; ++__first)
+               __tmp.emplace_back(*__first);
+             reserve(_M_check_len(__tmp.size(), "vector::append_range"));
+             ranges::subrange __r(std::make_move_iterator(__tmp.begin()),
+                                  std::make_move_iterator(__tmp.end()));
+             append_range(__r); // This will take the fast path above.
            }
        }
 #endif // ranges_to_container
diff --git a/libstdc++-v3/include/bits/vector.tcc 
b/libstdc++-v3/include/bits/vector.tcc
index acb2f5fca1e7..f197278d52e0 100644
--- a/libstdc++-v3/include/bits/vector.tcc
+++ b/libstdc++-v3/include/bits/vector.tcc
@@ -1094,7 +1094,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
            return begin() + __ins_idx;
          }
        else
-         return insert_range(__pos, vector(from_range, std::move(__rg),
+         return insert_range(__pos, vector(from_range, std::forward<_Rg>(__rg),
                                            _M_get_Tp_allocator()));
       }
 #endif // ranges_to_container
diff --git 
a/libstdc++-v3/testsuite/23_containers/vector/bool/modifiers/insert/append_range.cc
 
b/libstdc++-v3/testsuite/23_containers/vector/bool/modifiers/insert/append_range.cc
index a35ed0f20266..43a698f65c41 100644
--- 
a/libstdc++-v3/testsuite/23_containers/vector/bool/modifiers/insert/append_range.cc
+++ 
b/libstdc++-v3/testsuite/23_containers/vector/bool/modifiers/insert/append_range.cc
@@ -83,6 +83,57 @@ test_constexpr()
 {
   // XXX: this doesn't test the non-forward_range code paths are constexpr.
   do_test<std::span<short>, std::allocator<bool>>();
+
+  // Some basic tests for overlapping ranges in constant expressions.
+  using I = std::vector<bool>::iterator;
+
+  struct InputRange
+  {
+    struct Sent { I end; };
+
+    struct Iter
+    {
+      using value_type = bool;
+      using difference_type = int;
+      constexpr explicit Iter(I i) : i(i) { }
+      constexpr Iter& operator++() { ++i; return *this; }
+      constexpr Iter operator++(int) { auto i = *this; ++i; return i; }
+      constexpr int operator*() const { return *i; }
+      constexpr bool operator==(const Iter&) const = default;
+      constexpr bool operator==(const Sent& s) const { return i == s.end; }
+      I i;
+    };
+
+    Iter iter;
+    Sent sent;
+
+    constexpr InputRange(I f, I l) : iter{f}, sent{l} { }
+    constexpr Iter begin() const { return iter; }
+    constexpr Sent end() const { return sent; }
+  };
+  static_assert( std::ranges::input_range<InputRange> );
+  static_assert( ! std::ranges::forward_range<InputRange> );
+
+  std::vector<bool> vec(5);
+
+  // Test overlapping input ranges
+  vec.resize(vec.capacity());
+  vec.append_range(InputRange(vec.begin(), vec.begin() + 3)); // no capacity
+  vec.reserve(vec.capacity() + 2);
+  vec.append_range(InputRange(vec.begin(), vec.begin() + 4)); // some capacity
+  vec.reserve(vec.capacity() + 6);
+  vec.append_range(InputRange(vec.begin(), vec.begin() + 5)); // enough 
capacity
+
+  using R = std::ranges::subrange<I>;
+
+  // Test overlapping forward ranges
+  vec.resize(vec.capacity());
+  vec.append_range(R(vec.begin(), vec.begin() + 3)); // no capacity
+  vec.reserve(vec.size() + 2);
+  vec.append_range(R(vec.begin(), vec.begin() + 4)); // some capacity
+  vec.reserve(vec.size() + 6);
+  vec.append_range(R(vec.begin(), vec.begin() + 5)); // enough capacity
+
   return true;
 }
 
diff --git 
a/libstdc++-v3/testsuite/23_containers/vector/modifiers/append_range.cc 
b/libstdc++-v3/testsuite/23_containers/vector/modifiers/append_range.cc
index 5725cd2ad483..be097e2b1310 100644
--- a/libstdc++-v3/testsuite/23_containers/vector/modifiers/append_range.cc
+++ b/libstdc++-v3/testsuite/23_containers/vector/modifiers/append_range.cc
@@ -82,16 +82,181 @@ test_ranges()
   return true;
 }
 
+void
+test_overlapping()
+{
+  using __gnu_test::test_input_range;
+  using __gnu_test::test_forward_range;
+
+  struct X {
+    unsigned* p;
+    constexpr X(int i = 0) : p(new unsigned(i)) { }
+    constexpr X(const X& m) : p(new unsigned(*m.p)) { }
+    constexpr X(X&& m) noexcept : p(m.p) { m.p = nullptr; }
+    constexpr ~X() { delete p; }
+  };
+
+  std::vector<X> vec;
+  unsigned size = 5;
+  vec.reserve(size);
+  for (unsigned i = 0; i < size; ++i)
+    vec.emplace_back(i);
+
+  // Append an input range that overlaps with vec.
+  {
+    __gnu_test::test_input_range<X> r(vec.data(), vec.data() + size);
+    vec.append_range(r);
+    VERIFY( vec.size() == 2 * size );
+    for (unsigned i = 0; i < size; ++i)
+    {
+      VERIFY( *vec[i].p == i );
+      VERIFY( *vec[i+size].p == i );
+    }
+  }
+
+  size = vec.size() - 2;
+  vec.resize(size);
+  for (unsigned i = 0; i < size; ++i)
+    *vec[i].p = i;
+
+  // Repeat with unused capacity in the vector.
+  {
+    __gnu_test::test_input_range<X> r(vec.data(), vec.data() + size);
+    vec.append_range(r);
+    VERIFY( vec.size() == 2 * size );
+    for (unsigned i = 0; i < size; ++i)
+    {
+      VERIFY( *vec[i].p == i );
+      VERIFY( *vec[i+size].p == i );
+    }
+  }
+
+  size = vec.size() - 2;
+  vec.resize(size);
+  for (unsigned i = 0; i < size; ++i)
+    *vec[i].p = i;
+
+  // Repeat with input range that doesn't overlap full vector.
+  {
+    __gnu_test::test_input_range<X> r(vec.data() + 1, vec.data() + 4);
+    vec.append_range(r);
+    VERIFY( vec.size() == size + 3 );
+    for (unsigned i = 0; i < size; ++i)
+    {
+      VERIFY( *vec[i].p == i );
+      if (i < 3)
+       VERIFY( *vec[i+size].p == i+1 );
+    }
+  }
+
+  size = 5;
+  vec.resize(size);
+  for (unsigned i = 0; i < size; ++i)
+    *vec[i].p = i;
+
+  // Append a forward range that overlaps with vec.
+  {
+    __gnu_test::test_forward_range<X> r(vec.data(), vec.data() + size);
+    vec.append_range(r);
+    VERIFY( vec.size() == 2 * size );
+    for (unsigned i = 0; i < size; ++i)
+    {
+      VERIFY( *vec[i].p == i );
+      VERIFY( *vec[i+size].p == i );
+    }
+  }
+
+  size = vec.size() - 2;
+  vec.resize(size);
+  for (unsigned i = 0; i < size; ++i)
+    *vec[i].p = i;
+
+  // Repeat with insufficient unused capacity in the vector.
+  {
+    __gnu_test::test_forward_range<X> r(vec.data(), vec.data() + size);
+    vec.append_range(r);
+    VERIFY( vec.size() == 2 * size );
+    for (unsigned i = 0; i < size; ++i)
+    {
+      VERIFY( *vec[i].p == i );
+      VERIFY( *vec[i+size].p == i );
+    }
+  }
+
+  size = vec.size() / 2;
+  vec.resize(size);
+
+  // Repeat with sufficient unused capacity in the vector.
+  {
+    __gnu_test::test_forward_range<X> r(vec.data(), vec.data() + size);
+    vec.append_range(r);
+    VERIFY( vec.size() == 2 * size );
+    for (unsigned i = 0; i < size; ++i)
+    {
+      VERIFY( *vec[i].p == i );
+      VERIFY( *vec[i+size].p == i );
+    }
+  }
+}
+
 constexpr bool
 test_constexpr()
 {
   // XXX: this doesn't test the non-forward_range code paths are constexpr.
   do_test<std::span<short>, std::allocator<int>>();
+
+  // Some basic tests for overlapping ranges in constant expressions.
+  struct InputRange
+  {
+    struct Sent { const void* end; };
+
+    struct Iter
+    {
+      using value_type = int;
+      using difference_type = int;
+      constexpr explicit Iter(int* p) : ptr(p) { }
+      constexpr Iter& operator++() { ++ptr; return *this; }
+      constexpr Iter operator++(int) { auto i = *this; ++ptr; return i; }
+      constexpr int operator*() const { return *ptr; }
+      constexpr bool operator==(const Iter&) const = default;
+      constexpr bool operator==(const Sent& s) const { return ptr == s.end; }
+      int* ptr;
+    };
+
+    Iter iter;
+    Sent sent;
+
+    constexpr InputRange(int* f, int* l) : iter{f}, sent{l} { }
+    constexpr Iter begin() const { return iter; }
+    constexpr Sent end() const { return sent; }
+  };
+  static_assert( std::ranges::input_range<InputRange> );
+  static_assert( ! std::ranges::forward_range<InputRange> );
+
+  std::vector<int> vec(5);
+
+  // Test overlapping input ranges
+  vec.resize(vec.capacity());
+  vec.append_range(InputRange(vec.data(), vec.data() + 3)); // no capacity
+  vec.reserve(vec.capacity() + 2);
+  vec.append_range(InputRange(vec.data(), vec.data() + 4)); // some capacity
+  vec.reserve(vec.capacity() + 6);
+  vec.append_range(InputRange(vec.data(), vec.data() + 5)); // enough capacity
+
+  // Test overlapping forward ranges
+  vec.resize(vec.capacity());
+  vec.append_range(std::span<int>(vec));               // no capacity
+  vec.reserve(vec.size() + 2);
+  vec.append_range(std::span<int>(vec).subspan(1, 4)); // some capacity
+  vec.reserve(vec.size() + 6);
+  vec.append_range(std::span<int>(vec).subspan(1, 5)); // enough capacity
+
   return true;
 }
 
 int main()
 {
   test_ranges();
+  test_overlapping();
   static_assert( test_constexpr() );
 }

Reply via email to