We introduced a regression in r214986 when changing the _Hashtable
range constructor to delegate to another constructor. That change
means that the object has completed construction after the target
constructor completes, and so if an exception is thrown in the
delegating constructor then the destructor will run. This results in
calling _M_deallocate_buckets twice, causing a double-free.

The fix is to simply omit the try-catch in the delegating constructor,
so that the destructor takes care of the clean up.

        PR libstdc++/81891
        * include/bits/hashtable.h (_Hashtable(_InputIterator, _InputIterator,
        size_type, const _H1&, const _H2&, const _Hash&, const _Equal&,
        const _ExtractKey&, const allocator_type&)): Let destructor do clean
        up if an exception is thrown.
        * testsuite/23_containers/unordered_map/cons/81891.cc: New.

Tested powerpc64le-linux, committed to trunk.

As a regression since 4.9 this needs to be backported to all active
branches.


commit 30ce5842ed9b91813b97b756960afe6369f1a568
Author: Jonathan Wakely <jwak...@redhat.com>
Date:   Fri Aug 18 18:03:51 2017 +0100

    PR libstdc++/81891 fix double-free in hashtable constructor
    
            PR libstdc++/81891
            * include/bits/hashtable.h (_Hashtable(_InputIterator, 
_InputIterator,
            size_type, const _H1&, const _H2&, const _Hash&, const _Equal&,
            const _ExtractKey&, const allocator_type&)): Let destructor do clean
            up if an exception is thrown.
            * testsuite/23_containers/unordered_map/cons/81891.cc: New.

diff --git a/libstdc++-v3/include/bits/hashtable.h 
b/libstdc++-v3/include/bits/hashtable.h
index bc7448bcf13..e0806dc93a1 100644
--- a/libstdc++-v3/include/bits/hashtable.h
+++ b/libstdc++-v3/include/bits/hashtable.h
@@ -973,17 +973,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
            _M_bucket_count = __bkt_count;
          }
 
-       __try
-         {
-           for (; __f != __l; ++__f)
-             this->insert(*__f);
-         }
-       __catch(...)
-         {
-           clear();
-           _M_deallocate_buckets();
-           __throw_exception_again;
-         }
+       for (; __f != __l; ++__f)
+         this->insert(*__f);
       }
 
   template<typename _Key, typename _Value,
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/cons/81891.cc 
b/libstdc++-v3/testsuite/23_containers/unordered_map/cons/81891.cc
new file mode 100644
index 00000000000..61807c0efd2
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/cons/81891.cc
@@ -0,0 +1,68 @@
+// Copyright (C) 2017 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/>.
+
+// { dg-do run { target c++11 } }
+
+#include <unordered_map>
+#include <testsuite_hooks.h>
+#include <testsuite_allocator.h>
+
+struct fails_on_copy {
+  fails_on_copy() = default;
+  fails_on_copy(const fails_on_copy&) { throw 0; };
+};
+
+using value_type = std::pair<int, fails_on_copy>;
+
+void
+test01()
+{
+  value_type p;
+  try
+  {
+    std::unordered_map<int, fails_on_copy> umap(&p, &p + 1);
+  }
+  catch(...)
+  { }
+}
+
+void
+test02()
+{
+  using Alloc = __gnu_test::tracker_allocator<value_type>;
+  using std::hash;
+  using std::equal_to;
+
+  value_type p;
+  try
+  {
+    std::unordered_map<int, fails_on_copy, hash<int>, equal_to<int>, Alloc>
+       umap(&p, &p + 1);
+  }
+  catch(...)
+  { }
+
+  using counter = __gnu_test::tracker_allocator_counter;
+  VERIFY(counter::get_allocation_count() == counter::get_deallocation_count());
+}
+
+int
+main()
+{
+  test01();
+  test02();
+}

Reply via email to