PR libstdc++/79254 fix exception-safety in std::string::operator=
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
This commit is contained in:
parent
f75bbf3fc6
commit
a7d47f3526
7 changed files with 155 additions and 16 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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>::
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue