Patch v2 fixes the bug in the slow path for swap, and improves the
test so that it fails with the old buggy code.
commit 44214429d428f4fe5a148c7636b844600a10f9a4
Author: Jonathan Wakely <jwak...@redhat.com>
Date:   Thu Nov 28 13:59:09 2024

    libstdc++: Make std::basic_stacktrace swappable with unequal allocators
    
    The standard says that it's undefined to swap two containers if the
    allocators are not equal and do not propagate. This ensures that swap is
    always O(1) and non-throwing, but has other undesirable consequences
    such as LWG 2152. The 2016 paper P0178 ("Allocators and swap") proposed
    making the non-member swap handle non-equal allocators, by performing an
    O(n) deep copy when needed. This ensures that a.swap(b) is still O(1)
    and non-throwing, but swap(a, b) is valid for all values of the type.
    
    This change implements that for std::basic_stacktrace. The member swap
    is changed so that for the undefined case (where we can't swap the
    allocators, but can't swap storage separately from the allocators) we
    just return without changing either object. This ensures that with
    assertions disabled we don't separate allocated storage from the
    allocator that can free it.
    
    For the non-member swap, perform deep copies of the two ranges, avoiding
    reallocation if there is already sufficient capacity.
    
    libstdc++-v3/ChangeLog:
    
            * include/std/stacktrace (basic_stacktrace::swap): Refactor so
            that the undefined case is a no-op when assertions are disabled.
            (swap): Remove precondition and perform deep copies when member
            swap would be undefined.
            * testsuite/19_diagnostics/stacktrace/stacktrace.cc: Check
            swapping with unequal, non-propagating allocators.

diff --git a/libstdc++-v3/include/std/stacktrace 
b/libstdc++-v3/include/std/stacktrace
index f94a424e4cf..a7d4810e886 100644
--- a/libstdc++-v3/include/std/stacktrace
+++ b/libstdc++-v3/include/std/stacktrace
@@ -476,15 +476,80 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        }
 
       // [stacktrace.basic.mod], modifiers
+
+      /** Exchange the contents of two stacktrace objects
+       *
+       * @pre The allocators must propagate on swap or must be equal.
+       */
       void
       swap(basic_stacktrace& __other) noexcept
       {
-       std::swap(_M_impl, __other._M_impl);
        if constexpr (_AllocTraits::propagate_on_container_swap::value)
-         std::swap(_M_alloc, __other._M_alloc);
+         {
+           using std::swap;
+           swap(_M_alloc, __other._M_alloc);
+         }
        else if constexpr (!_AllocTraits::is_always_equal::value)
          {
-           __glibcxx_assert(_M_alloc == __other._M_alloc);
+           if (_M_alloc != __other._M_alloc)
+             {
+               __glibcxx_assert(_M_alloc == __other._M_alloc);
+               // If assertions are disabled but the allocators are unequal,
+               // we can't swap pointers, so just erroneously return.
+               return;
+             }
+         }
+       std::swap(_M_impl, __other._M_impl);
+      }
+
+      // [stacktrace.basic.nonmem], non-member functions
+
+      /** Exchange the contents of two stacktrace objects
+       *
+       * Unlike the `swap` member function, this can be used with unequal
+       * and non-propagating allocators. If the storage cannot be efficiently
+       * swapped then the stacktrace_entry elements will be exchanged
+       * one-by-one, reallocating if needed.
+       */
+      friend void
+      swap(basic_stacktrace& __a, basic_stacktrace& __b)
+      noexcept(_AllocTraits::propagate_on_container_swap::value
+                || _AllocTraits::is_always_equal::value)
+      {
+       if constexpr (_AllocTraits::propagate_on_container_swap::value
+                       || _AllocTraits::is_always_equal::value)
+         __a.swap(__b);
+       else if (__a._M_alloc == __b._M_alloc) [[likely]]
+         __a.swap(__b);
+       else // O(N) swap for non-equal non-propagating allocators
+         {
+           basic_stacktrace* __p[2]{ std::__addressof(__a),
+                                     std::__addressof(__b) };
+           if (__p[0]->size() > __p[1]->size())
+             std::swap(__p[0], __p[1]);
+           basic_stacktrace& __a = *__p[0]; // shorter sequence
+           basic_stacktrace& __b = *__p[1]; // longer sequence
+
+           const auto __a_sz = __a.size();
+           auto __a_begin = __a._M_impl._M_frames;
+           auto __a_end = __a._M_impl._M_frames + __a_sz;
+           auto __b_begin = __b._M_impl._M_frames;
+
+           if (__a._M_impl._M_capacity < __b.size())
+             {
+               // Reallocation needed.
+               basic_stacktrace __tmp(__b, __a._M_alloc);
+               std::copy(__a_begin, __a_end, __b_begin);
+               __b._M_impl._M_resize(__a_sz, __b._M_alloc);
+               std::swap(__tmp._M_impl, __a._M_impl);
+               return;
+             }
+
+           // Exchange contents in-place.
+           auto __mid = std::swap_ranges(__a_begin, __a_end, __b_begin);
+           std::__relocate_a(__mid, __b_begin + __b.size(), __a_end,
+                             __a._M_alloc);
+           std::swap(__a._M_impl._M_size, __b._M_impl._M_size);
          }
       }
 
@@ -659,13 +724,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   // basic_stacktrace typedef names
   using stacktrace = basic_stacktrace<allocator<stacktrace_entry>>;
 
-  // [stacktrace.basic.nonmem], non-member functions
-  template<typename _Allocator>
-    inline void
-    swap(basic_stacktrace<_Allocator>& __a, basic_stacktrace<_Allocator>& __b)
-    noexcept(noexcept(__a.swap(__b)))
-    { __a.swap(__b); }
-
   inline ostream&
   operator<<(ostream& __os, const stacktrace_entry& __f)
   {
diff --git a/libstdc++-v3/testsuite/19_diagnostics/stacktrace/stacktrace.cc 
b/libstdc++-v3/testsuite/19_diagnostics/stacktrace/stacktrace.cc
index ee1a6d221e3..57453c4c9b5 100644
--- a/libstdc++-v3/testsuite/19_diagnostics/stacktrace/stacktrace.cc
+++ b/libstdc++-v3/testsuite/19_diagnostics/stacktrace/stacktrace.cc
@@ -393,6 +393,30 @@ test_swap()
     VERIFY( s1.get_allocator().get_personality() == 2 );
     VERIFY( s2.get_allocator().get_personality() == 1 );
   }
+
+  {
+    using Alloc
+      = __gnu_test::propagating_allocator<std::stacktrace_entry, false>;
+    using Stacktrace = std::basic_stacktrace<Alloc>;
+
+    Stacktrace s1 = Stacktrace::current(Alloc{1});
+    Stacktrace s2(Alloc{2});
+    const Stacktrace s3 = s1;
+    swap(s1, s2);
+    VERIFY( s1.empty() );
+    VERIFY( s2 == s3 );
+    VERIFY( s1.get_allocator().get_personality() == 1 );
+    VERIFY( s2.get_allocator().get_personality() == 2 );
+
+    s1 = s3;
+    s2 = Stacktrace::current(s2.get_allocator()); // Overwrite old content
+    s2 = Stacktrace(); // Clear content, capacity will be unchanged.
+    swap(s1, s2);
+    VERIFY( s1.empty() );
+    VERIFY( s2 == s3 );
+    VERIFY( s1.get_allocator().get_personality() == 1 );
+    VERIFY( s2.get_allocator().get_personality() == 2 );
+  }
 }
 
 void

Reply via email to