PR libstdc++/83982 fix exception-safety guarantee of std::vector::resize

Message ID 20180613234328.GA27409@redhat.com
State New
Headers show
Series
  • PR libstdc++/83982 fix exception-safety guarantee of std::vector::resize
Related show

Commit Message

Jonathan Wakely June 13, 2018, 11:43 p.m.
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 <dtrebbien@gmail.com>
	    Jonathan Wakely  <jwakely@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 <jwakely@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 <dtrebbien@gmail.com>
                Jonathan Wakely  <jwakely@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.

Comments

Daniel Trebbien June 14, 2018, 5:01 a.m. | #1
On Wed, Jun 13, 2018 at 11:43 PM, Jonathan Wakely <jwakely@redhat.com>
wrote:

> 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 <dtrebbien@gmail.com>
>             Jonathan Wakely  <jwakely@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_guaran
> tee.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.
>
>

Thank you, Jonathan.  Your simpler patch looks good to me.  I have also
verified that the strong_guarantee.cc test case fails with g++-8 (Homebrew
GCC 8.1.0) macOS AMD64 architecture, and passes when the changes to
vector.tcc are applied to /usr/local/include/c++/8.1.0/bits/vector.tcc.

Daniel

Patch

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();
+}