PR libstdc++/79254 fix exception-safety in std::string::operator=
authorJonathan Wakely <jwakely@redhat.com>
Fri, 27 Jan 2017 16:17:04 +0000 (16:17 +0000)
committerJonathan Wakely <redi@gcc.gnu.org>
Fri, 27 Jan 2017 16:17:04 +0000 (16:17 +0000)
PR libstdc++/79254
* config/abi/pre/gnu.ver: Add new symbols.
* include/bits/basic_string.h [_GLIBCXX_USE_CXX11_ABI]
(basic_string::_M_copy_assign): New overloaded functions to perform
copy assignment.
(basic_string::operator=(const basic_string&)): Dispatch to
_M_copy_assign.
* include/bits/basic_string.tcc [_GLIBCXX_USE_CXX11_ABI]
(basic_string::_M_copy_assign(const basic_string&, true_type)):
Define, performing rollback on exception.
* testsuite/21_strings/basic_string/allocator/char/copy_assign.cc:
Test exception-safety guarantee.
* testsuite/21_strings/basic_string/allocator/wchar_t/copy_assign.cc:
Likewise.
* testsuite/util/testsuite_allocator.h (uneq_allocator::swap): Make
std::swap visible.

From-SVN: r244986

libstdc++-v3/ChangeLog
libstdc++-v3/config/abi/pre/gnu.ver
libstdc++-v3/include/bits/basic_string.h
libstdc++-v3/include/bits/basic_string.tcc
libstdc++-v3/testsuite/21_strings/basic_string/allocator/char/copy_assign.cc
libstdc++-v3/testsuite/21_strings/basic_string/allocator/wchar_t/copy_assign.cc
libstdc++-v3/testsuite/util/testsuite_allocator.h

index caa49007dfb7d944f165011bfed505797e44cfb6..7b0031e211a97072200c1f41dbca89200b240519 100644 (file)
@@ -1,3 +1,22 @@
+2017-01-27  Jonathan Wakely  <jwakely@redhat.com>
+
+       PR libstdc++/79254
+       * config/abi/pre/gnu.ver: Add new symbols.
+       * include/bits/basic_string.h [_GLIBCXX_USE_CXX11_ABI]
+       (basic_string::_M_copy_assign): New overloaded functions to perform
+       copy assignment.
+       (basic_string::operator=(const basic_string&)): Dispatch to
+       _M_copy_assign.
+       * include/bits/basic_string.tcc [_GLIBCXX_USE_CXX11_ABI]
+       (basic_string::_M_copy_assign(const basic_string&, true_type)):
+       Define, performing rollback on exception.
+       * testsuite/21_strings/basic_string/allocator/char/copy_assign.cc:
+       Test exception-safety guarantee.
+       * testsuite/21_strings/basic_string/allocator/wchar_t/copy_assign.cc:
+       Likewise.
+       * testsuite/util/testsuite_allocator.h (uneq_allocator::swap): Make
+       std::swap visible.
+
 2017-01-26  Jonathan Wakely  <jwakely@redhat.com>
 
        PR libstdc++/70607
index 3e6e70b6cff6c82398df30fd5d4a91e6f8cdc494..1bea4b4d7fec3c0f0894e52d0965fa1e3171c49f 100644 (file)
@@ -1700,7 +1700,9 @@ GLIBCXX_3.4.21 {
     _ZNSt7__cxx1112basic_stringI[cw]St11char_traitsI[cw]ESaI[cw]EE1[01]**;
     _ZNSt7__cxx1112basic_stringI[cw]St11char_traitsI[cw]ESaI[cw]EE12_Alloc_hiderC[12]EP[cw]RKS3_;
     _ZNSt7__cxx1112basic_stringI[cw]St11char_traitsI[cw]ESaI[cw]EE12_M*;
-    _ZNSt7__cxx1112basic_stringI[cw]St11char_traitsI[cw]ESaI[cw]EE1[3-9]*;
+    _ZNSt7__cxx1112basic_stringI[cw]St11char_traitsI[cw]ESaI[cw]EE13*;
+    _ZNSt7__cxx1112basic_stringI[cw]St11char_traitsI[cw]ESaI[cw]EE14_M_replace_aux*;
+    _ZNSt7__cxx1112basic_stringI[cw]St11char_traitsI[cw]ESaI[cw]EE1[5-9]*;
     _ZNSt7__cxx1112basic_stringI[cw]St11char_traitsI[cw]ESaI[cw]EE[2-9]*;
     _ZNSt7__cxx1112basic_stringI[cw]St11char_traitsI[cw]ESaI[cw]EEC[12]EOS4_*;
     _ZNSt7__cxx1112basic_stringI[cw]St11char_traitsI[cw]ESaI[cw]EEC[12]EPK*;
@@ -1953,6 +1955,9 @@ GLIBCXX_3.4.23 {
     _ZNSsC[12]ERKSs[jmy]RKSaIcE;
     _ZNSbIwSt11char_traitsIwESaIwEEC[12]ERKS2_mRKS1_;
 
+    # basic_string<C, T, A>::_M_copy_assign(const basic_string&, {true,false}_type)
+    _ZNSt7__cxx1112basic_stringI[cw]St11char_traitsI[cw]ESaI[cw]EE14_M_copy_assign*;
+
 #ifndef HAVE_EXCEPTION_PTR_SINCE_GCC46
     # std::future symbols are exported in the first version to support
     # std::exception_ptr
index 9dffcf946945471de210e541d55a00dfbf6c38a0..97fe797d4b44340a53908898be434a68c33ba884 100644 (file)
@@ -384,7 +384,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
       }
 
       void
-      _M_assign(const basic_string& __rcs);
+      _M_assign(const basic_string&);
 
       void
       _M_mutate(size_type __pos, size_type __len1, const _CharT* __s,
@@ -393,6 +393,15 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
       void
       _M_erase(size_type __pos, size_type __n);
 
+#if __cplusplus >= 201103L
+      void
+      _M_copy_assign(const basic_string& __str, /* pocca = */ true_type);
+
+      void
+      _M_copy_assign(const basic_string& __str, /* pocca = */ false_type)
+      { this->_M_assign(__str); }
+#endif
+
     public:
       // Construct/copy/destroy:
       // NB: We overload ctors in some cases instead of using default
@@ -627,20 +636,12 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
       operator=(const basic_string& __str)
       {
 #if __cplusplus >= 201103L
-       if (_Alloc_traits::_S_propagate_on_copy_assign())
-         {
-           if (!_Alloc_traits::_S_always_equal() && !_M_is_local()
-               && _M_get_allocator() != __str._M_get_allocator())
-             {
-               // replacement allocator cannot free existing storage
-               _M_destroy(_M_allocated_capacity);
-               _M_data(_M_local_data());
-               _M_set_length(0);
-             }
-           std::__alloc_on_copy(_M_get_allocator(), __str._M_get_allocator());
-         }
+       _M_copy_assign(__str,
+           typename _Alloc_traits::propagate_on_container_copy_assignment());
+#else
+       this->_M_assign(__str);
 #endif
-       return this->assign(__str);
+       return *this;
       }
 
       /**
index 41b7fa196b0be530325b3faab5d627ca09c46042..adc8b853137a439c59f4e0016b5d77b01ced667c 100644 (file)
@@ -275,6 +275,70 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        }
     }
 
+#if __cplusplus >= 201103L
+  template<typename _CharT, typename _Traits, typename _Alloc>
+    void
+    basic_string<_CharT, _Traits, _Alloc>::
+    _M_copy_assign(const basic_string& __str, true_type)
+    {
+      struct _Guard // RAII type for strong exception-safety guarantee.
+      {
+       // Takes ownership of string's original state.
+       _Guard(basic_string* __self)
+       : _M_self(__self), _M_alloc(std::move(__self->_M_get_allocator())),
+         _M_ptr(__self->_M_data()),
+         _M_capacity(__self->_M_allocated_capacity), _M_len(__self->length())
+       {
+         __self->_M_data(__self->_M_local_data());
+         __self->_M_length(0);
+       }
+
+       // Restores string's original state if _M_release() was not called.
+       ~_Guard()
+       {
+         if (_M_ptr)
+           {
+             _M_self->_M_get_allocator() = std::move(_M_alloc);
+             _M_self->_M_data(_M_ptr);
+             _M_self->_M_capacity(_M_capacity);
+             _M_self->_M_length(_M_len);
+           }
+       }
+
+       _Guard(const _Guard&) = delete;
+       _Guard& operator=(const _Guard&) = delete;
+
+       void _M_release()
+       {
+         // Original state can be freed now.
+         _Alloc_traits::deallocate(_M_alloc, _M_ptr, _M_capacity + 1);
+         _M_ptr = nullptr;
+       }
+
+       basic_string*   _M_self;
+       allocator_type  _M_alloc;
+       pointer         _M_ptr;
+       size_type       _M_capacity;
+       size_type       _M_len;
+      };
+
+      if (!_Alloc_traits::_S_always_equal() && !_M_is_local()
+         && _M_get_allocator() != __str._M_get_allocator())
+       {
+         // The propagating allocator cannot free existing storage.
+         _Guard __guard(this);
+         _M_get_allocator() = __str._M_get_allocator();
+         this->_M_assign(__str);
+         __guard._M_release();
+       }
+      else
+       {
+         _M_get_allocator() = __str._M_get_allocator();
+         this->_M_assign(__str);
+       }
+    }
+#endif
+
   template<typename _CharT, typename _Traits, typename _Alloc>
     void
     basic_string<_CharT, _Traits, _Alloc>::
index 32ee708f3c6671a3ab5f9378871c7543f560b767..0fc80d7fb4da7235b401497f37de469511e83bf3 100644 (file)
@@ -22,6 +22,7 @@
 #include <string>
 #include <testsuite_hooks.h>
 #include <testsuite_allocator.h>
+#include <ext/throw_allocator.h>
  
 using C = char;
 const C c = 'a';
@@ -99,9 +100,33 @@ void test02()
   VERIFY(1 == v5.get_allocator().get_personality());
 }
 
+void test03()
+{
+  // PR libstdc++/79254
+  using throw_alloc = __gnu_cxx::throw_allocator_limit<C>;
+  typedef propagating_allocator<C, true, throw_alloc> alloc_type;
+  typedef std::basic_string<C, traits, alloc_type> test_type;
+  alloc_type a1(1), a2(2);
+  throw_alloc::set_limit(2); // Throw on third allocation (during assignment).
+  const C* s1 = "a string that is longer than a small string";
+  const C* s2 = "another string that is longer than a small string";
+  test_type v1(s1, a1);
+  test_type v2(s2, a2);
+  bool caught = false;
+  try {
+    v1 = v2;
+  } catch (__gnu_cxx::forced_error&) {
+    caught = true;
+  }
+  VERIFY( caught );
+  VERIFY( v1 == s1 );
+  VERIFY( v1.get_allocator() == a1 );
+}
+
 int main()
 {
   test01();
   test02();
+  test03();
   return 0;
 }
index 89593ba8faa9e18abd4ed98cd93dfcd65fcf7808..c35e001934d9d571db77b45dbd5f86d7fca7b4b6 100644 (file)
@@ -22,6 +22,7 @@
 #include <string>
 #include <testsuite_hooks.h>
 #include <testsuite_allocator.h>
+#include <ext/throw_allocator.h>
  
 using C = wchar_t;
 const C c = L'a';
@@ -99,9 +100,33 @@ void test02()
   VERIFY(1 == v5.get_allocator().get_personality());
 }
 
+void test03()
+{
+  // PR libstdc++/79254
+  using throw_alloc = __gnu_cxx::throw_allocator_limit<C>;
+  typedef propagating_allocator<C, true, throw_alloc> alloc_type;
+  typedef std::basic_string<C, traits, alloc_type> test_type;
+  alloc_type a1(1), a2(2);
+  throw_alloc::set_limit(2); // Throw on third allocation (during assignment).
+  const C* s1 = L"a string that is longer than a small string";
+  const C* s2 = L"another string that is longer than a small string";
+  test_type v1(s1, a1);
+  test_type v2(s2, a2);
+  bool caught = false;
+  try {
+    v1 = v2;
+  } catch (__gnu_cxx::forced_error&) {
+    caught = true;
+  }
+  VERIFY( caught );
+  VERIFY( v1 == s1 );
+  VERIFY( v1.get_allocator() == a1 );
+}
+
 int main()
 {
   test01();
   test02();
+  test03();
   return 0;
 }
index 20387a86986ddb505ef52974073172434f3ccc8f..813fc81088ad5955eba842d711662ace69f43637 100644 (file)
@@ -287,7 +287,7 @@ namespace __gnu_test
 
       Alloc& base() { return *this; }
       const Alloc& base() const  { return *this; }
-      void swap_base(Alloc& b) { swap(b, this->base()); }
+      void swap_base(Alloc& b) { using std::swap; swap(b, this->base()); }
 
     public:
       typedef typename check_consistent_alloc_value_type<Tp, Alloc>::value_type