From: Jonathan Wakely Date: Wed, 1 Feb 2017 12:18:43 +0000 (+0000) Subject: PR libstdc++/79254 fix exception-safety of std::string copy assignment X-Git-Tag: releases/gcc-5.5.0~547 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=26722ba9572c6d5124ae44ad9f0a304554668344;p=thirdparty%2Fgcc.git PR libstdc++/79254 fix exception-safety of std::string copy assignment PR libstdc++/79254 * include/bits/basic_string.h [_GLIBCXX_USE_CXX11_ABI] (basic_string::operator=(const basic_string&)): If source object is small just deallocate, otherwise perform new allocation before making any changes. * testsuite/21_strings/basic_string/allocator/wchar_t/copy_assign.cc: Test exception-safety of copy assignment when allocator propagates. * testsuite/21_strings/basic_string/allocator/char/copy_assign.cc: Likewise. * testsuite/util/testsuite_allocator.h (uneq_allocator::swap): Make std::swap visible. From-SVN: r245088 --- diff --git a/libstdc++-v3/ChangeLog b/libstdc++-v3/ChangeLog index c7f446554d26..35c852d8ed2c 100644 --- a/libstdc++-v3/ChangeLog +++ b/libstdc++-v3/ChangeLog @@ -1,3 +1,17 @@ +2017-02-01 Jonathan Wakely + + PR libstdc++/79254 + * include/bits/basic_string.h [_GLIBCXX_USE_CXX11_ABI] + (basic_string::operator=(const basic_string&)): If source object is + small just deallocate, otherwise perform new allocation before + making any changes. + * testsuite/21_strings/basic_string/allocator/wchar_t/copy_assign.cc: + Test exception-safety of copy assignment when allocator propagates. + * testsuite/21_strings/basic_string/allocator/char/copy_assign.cc: + Likewise. + * testsuite/util/testsuite_allocator.h (uneq_allocator::swap): Make + std::swap visible. + 2017-01-22 Gerald Pfeifer Merge from mainline diff --git a/libstdc++-v3/include/bits/basic_string.h b/libstdc++-v3/include/bits/basic_string.h index 9e3aec0940a5..de56d86fd83b 100644 --- a/libstdc++-v3/include/bits/basic_string.h +++ b/libstdc++-v3/include/bits/basic_string.h @@ -571,10 +571,25 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 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); + // Propagating allocator cannot free existing storage so must + // deallocate it before replacing current allocator. + if (__str.size() <= _S_local_capacity) + { + _M_destroy(_M_allocated_capacity); + _M_data(_M_local_data()); + _M_set_length(0); + } + else + { + const auto __len = __str.size(); + auto __alloc = __str._M_get_allocator(); + // If this allocation throws there are no effects: + auto __ptr = _Alloc_traits::allocate(__alloc, __len + 1); + _M_destroy(_M_allocated_capacity); + _M_data(__ptr); + _M_capacity(__len); + _M_set_length(__len); + } } std::__alloc_on_copy(_M_get_allocator(), __str._M_get_allocator()); } diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/allocator/char/copy_assign.cc b/libstdc++-v3/testsuite/21_strings/basic_string/allocator/char/copy_assign.cc index 94e079611686..ccb474e5fa6a 100644 --- a/libstdc++-v3/testsuite/21_strings/basic_string/allocator/char/copy_assign.cc +++ b/libstdc++-v3/testsuite/21_strings/basic_string/allocator/char/copy_assign.cc @@ -20,6 +20,7 @@ #include #include #include +#include #if _GLIBCXX_USE_CXX11_ABI using C = char; @@ -100,10 +101,44 @@ void test02() VERIFY(1 == v5.get_allocator().get_personality()); } +void test03() +{ + // PR libstdc++/79254 + using throw_alloc = __gnu_cxx::throw_allocator_limit; + typedef propagating_allocator alloc_type; + typedef std::basic_string 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 ); + + throw_alloc::set_limit(1); // Allow one more allocation (and no more). + test_type v3(s1, a1); + // No allocation when allocators are equal and capacity is sufficient: + VERIFY( v1.capacity() >= v3.size() ); + v1 = v3; + // No allocation when the contents fit in the small-string buffer: + v2 = "sso"; + v1 = v2; + VERIFY( v1.get_allocator() == a2 ); +} + int main() { test01(); test02(); + test03(); return 0; } #else diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/allocator/wchar_t/copy_assign.cc b/libstdc++-v3/testsuite/21_strings/basic_string/allocator/wchar_t/copy_assign.cc index ed8feb7b33e4..68a452463f05 100644 --- a/libstdc++-v3/testsuite/21_strings/basic_string/allocator/wchar_t/copy_assign.cc +++ b/libstdc++-v3/testsuite/21_strings/basic_string/allocator/wchar_t/copy_assign.cc @@ -20,6 +20,7 @@ #include #include #include +#include #if _GLIBCXX_USE_CXX11_ABI using C = wchar_t; @@ -100,10 +101,44 @@ void test02() VERIFY(1 == v5.get_allocator().get_personality()); } +void test03() +{ + // PR libstdc++/79254 + using throw_alloc = __gnu_cxx::throw_allocator_limit; + typedef propagating_allocator alloc_type; + typedef std::basic_string 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 ); + + throw_alloc::set_limit(1); // Allow one more allocation (and no more). + test_type v3(s1, a1); + // No allocation when allocators are equal and capacity is sufficient: + VERIFY( v1.capacity() >= v3.size() ); + v1 = v3; + // No allocation when the contents fit in the small-string buffer: + v2 = L"sso"; + v1 = v2; + VERIFY( v1.get_allocator() == a2 ); +} + int main() { test01(); test02(); + test03(); return 0; } #else diff --git a/libstdc++-v3/testsuite/util/testsuite_allocator.h b/libstdc++-v3/testsuite/util/testsuite_allocator.h index f94dd7088b53..876d16ddb083 100644 --- a/libstdc++-v3/testsuite/util/testsuite_allocator.h +++ b/libstdc++-v3/testsuite/util/testsuite_allocator.h @@ -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::value_type