Construct new elements before moving existing ones, so that if a default constructor throws, the existing elements are not left in a moved-from state.
2018-06-14 Daniel Trebbien <dtrebb...@gmail.com> Jonathan Wakely <jwak...@redhat.com> PR libstdc++/83982 * include/bits/vector.tcc (vector::_M_default_append(size_type)): Default-construct new elements before moving existing ones. * testsuite/23_containers/vector/capacity/resize/strong_guarantee.cc: New. Tested powerpc64le-linux. Daniel (CC'd) originally proposed a fix based on the code used in vector::_M_realloc_insert. This is a slightly simpler fix, possible because resize only inserts at the end not in the middle. I also wrote a new testcase which is also valid in C++98, where the lack of move constructors means the strong exception-safety guarantee is always met. (Which means the bug is a regression since the default mode changed to -std=gnu++14 in GCC 6.1). I plan to commit this to trunk tomorrow, and backport soon too.
commit ef19eec2848fecf65ec51199549fdabfafed9927 Author: Jonathan Wakely <jwak...@redhat.com> Date: Thu Jun 14 00:09:11 2018 +0100 PR libstdc++/83982 fix exception-safety guarantee of std::vector::resize Construct new elements before moving existing ones, so that if a default constructor throws, the existing elements are not left in a moved-from state. 2018-06-14 Daniel Trebbien <dtrebb...@gmail.com> Jonathan Wakely <jwak...@redhat.com> PR libstdc++/83982 * include/bits/vector.tcc (vector::_M_default_append(size_type)): Default-construct new elements before moving existing ones. * testsuite/23_containers/vector/capacity/resize/strong_guarantee.cc: New. diff --git a/libstdc++-v3/include/bits/vector.tcc b/libstdc++-v3/include/bits/vector.tcc index 1d1ef427b26..86a711713b2 100644 --- a/libstdc++-v3/include/bits/vector.tcc +++ b/libstdc++-v3/include/bits/vector.tcc @@ -582,7 +582,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER { if (__n != 0) { - size_type __size = size(); + const size_type __size = size(); size_type __navail = size_type(this->_M_impl._M_end_of_storage - this->_M_impl._M_finish); @@ -601,23 +601,22 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER { const size_type __len = _M_check_len(__n, "vector::_M_default_append"); - const size_type __old_size = __size; pointer __new_start(this->_M_allocate(__len)); - pointer __new_finish(__new_start); + pointer __destroy_from = pointer(); __try { - __new_finish - = std::__uninitialized_move_if_noexcept_a - (this->_M_impl._M_start, this->_M_impl._M_finish, - __new_start, _M_get_Tp_allocator()); - __new_finish = - std::__uninitialized_default_n_a(__new_finish, __n, - _M_get_Tp_allocator()); + std::__uninitialized_default_n_a(__new_start + __size, + __n, _M_get_Tp_allocator()); + __destroy_from = __new_start + __size; + std::__uninitialized_move_if_noexcept_a( + this->_M_impl._M_start, this->_M_impl._M_finish, + __new_start, _M_get_Tp_allocator()); } __catch(...) { - std::_Destroy(__new_start, __new_finish, - _M_get_Tp_allocator()); + if (__destroy_from) + std::_Destroy(__destroy_from, __destroy_from + __n, + _M_get_Tp_allocator()); _M_deallocate(__new_start, __len); __throw_exception_again; } @@ -628,7 +627,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER this->_M_impl._M_end_of_storage - this->_M_impl._M_start); this->_M_impl._M_start = __new_start; - this->_M_impl._M_finish = __new_finish; + this->_M_impl._M_finish = __new_start + __size + __n; this->_M_impl._M_end_of_storage = __new_start + __len; } } diff --git a/libstdc++-v3/testsuite/23_containers/vector/capacity/resize/strong_guarantee.cc b/libstdc++-v3/testsuite/23_containers/vector/capacity/resize/strong_guarantee.cc new file mode 100644 index 00000000000..b209d76867a --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/vector/capacity/resize/strong_guarantee.cc @@ -0,0 +1,60 @@ +// Copyright (C) 2018 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// <http://www.gnu.org/licenses/>. + +#include <vector> +#include <testsuite_hooks.h> + +struct X +{ + X() : data(1) + { + if (fail) + throw 1; + } + + static bool fail; + + std::vector<int> data; +}; + +bool X::fail = false; + +void +test01() +{ + std::vector<X> v(2); + X* const addr = &v[0]; + bool caught = false; + try { + X::fail = true; + v.resize(v.capacity() + 1); // force reallocation + } catch (int) { + caught = true; + } + VERIFY( caught ); + VERIFY( v.size() == 2 ); + VERIFY( &v[0] == addr ); + // PR libstdc++/83982 + VERIFY( ! v[0].data.empty() ); + VERIFY( ! v[1].data.empty() ); +} + +int +main() +{ + test01(); +}