public inbox for libstdc++@gcc.gnu.org
 help / color / mirror / Atom feed
From: "François Dumont" <frs.dumont@gmail.com>
To: Jonathan Wakely <jwakely.gcc@gmail.com>
Cc: Jonathan Wakely <jwakely@redhat.com>,
	libstdc++ <libstdc++@gcc.gnu.org>,
	gcc-patches <gcc-patches@gcc.gnu.org>
Subject: Re: [PATH][_GLIBCXX_DEBUG] Fix unordered container merge
Date: Mon, 8 Nov 2021 22:36:10 +0100	[thread overview]
Message-ID: <f20104db-f50e-7773-c382-28c10acf9bfc@gmail.com> (raw)
In-Reply-To: <68641ea0-2a14-e3a5-8315-a7b3a9c1fdb4@gmail.com>

[-- Attachment #1: Type: text/plain, Size: 16174 bytes --]

Yet another version this time with only 1 guard implementation. The 
predicate to invalidate the safe iterators has been externalized.

Ok to commit ?


On 06/11/21 2:51 pm, François Dumont wrote:
> You were right to delay your reply. Here is a new version with less 
> code duplication and a bug fix in the new _UContMergeGuard where we 
> were using it->second rather than it->first to get the key.
>
> Note also that the change to _M_merge_multi implementation is also 
> important because otherwise we might be trying to extract a key from a 
> destructed node.
>
>     libstdc++: [_GLIBCXX_DEBUG] Implement unordered container merge
>
>     The _GLIBCXX_DEBUG unordered containers need a dedicated merge 
> implementation
>     so that any existing iterator on the transfered nodes is properly 
> invalidated.
>
>     Add typedef/using declaration for everything used as-is from 
> normal implementation.
>
>     libstdc++-v3/ChangeLog:
>
>             * include/bits/hashtable_policy.h (__distance_fw): Replace 
> class keyword with
>             typename.
>             * include/bits/hashtable.h 
> (_Hashtable<>::_M_merge_unique): Remove noexcept
>             qualification. Use const_iterator for node 
> extraction/reinsert.
>             (_Hashtable<>::_M_merge_multi): Likewise. Compute new hash 
> code before extract.
>             * include/debug/safe_container.h (_Safe_container<>): Make 
> all methods
>             protected.
>             * include/debug/safe_unordered_container.h
> (_Safe_unordered_container<>::_UContMergeGuard<_ExtractKey, _Source>): 
> New.
> (_Safe_unordered_container<>::_S_uc_guard<_ExtractKey, _Source>): New.
> (_Safe_unordered_container<>::_UMContMergeGuard<_ExtractKey, 
> _Source>): New.
> (_Safe_unordered_container<>::_S_umc_guard<_ExtractKey, _Source>): New.
> (_Safe_unordered_container<>::_M_invalide_all): Make public.
>             (_Safe_unordered_container<>::_M_invalide_if): Likewise.
> (_Safe_unordered_container<>::_M_invalide_local_if): Likewise.
>             * include/debug/unordered_map
>             (unordered_map<>::mapped_type, pointer, const_pointer): 
> New typedef.
>             (unordered_map<>::reference, const_reference, 
> difference_type): New typedef.
>             (unordered_map<>::get_allocator, empty, size, max_size): 
> Add usings.
>             (unordered_map<>::bucket_count, max_bucket_count, bucket): 
> Add usings.
>             (unordered_map<>::hash_function, key_equal, count, 
> contains): Add usings.
>             (unordered_map<>::operator[], at, rehash, reserve): Add 
> usings.
>             (unordered_map<>::merge): New.
>             (unordered_multimap<>::mapped_type, pointer, 
> const_pointer): New typedef.
>             (unordered_multimap<>::reference, const_reference, 
> difference_type): New typedef.
>             (unordered_multimap<>::get_allocator, empty, size, 
> max_size): Add usings.
>             (unordered_multimap<>::bucket_count, max_bucket_count, 
> bucket): Add usings.
>             (unordered_multimap<>::hash_function, key_equal, count, 
> contains): Add usings.
>             (unordered_multimap<>::rehash, reserve): Add usings.
>             (unordered_multimap<>::merge): New.
>             * include/debug/unordered_set
>             (unordered_set<>::mapped_type, pointer, const_pointer): 
> New typedef.
>             (unordered_set<>::reference, const_reference, 
> difference_type): New typedef.
>             (unordered_set<>::get_allocator, empty, size, max_size): 
> Add usings.
>             (unordered_set<>::bucket_count, max_bucket_count, bucket): 
> Add usings.
>             (unordered_set<>::hash_function, key_equal, count, 
> contains): Add usings.
>             (unordered_set<>::rehash, reserve): Add usings.
>             (unordered_set<>::merge): New.
>             (unordered_multiset<>::mapped_type, pointer, 
> const_pointer): New typedef.
>             (unordered_multiset<>::reference, const_reference, 
> difference_type): New typedef.
>             (unordered_multiset<>::get_allocator, empty, size, 
> max_size): Add usings.
>             (unordered_multiset<>::bucket_count, max_bucket_count, 
> bucket): Add usings.
>             (unordered_multiset<>::hash_function, key_equal, count, 
> contains): Add usings.
>             (unordered_multiset<>::rehash, reserve): Add usings.
>             (unordered_multiset<>::merge): New.
>             * 
> testsuite/23_containers/unordered_map/debug/merge1_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_map/debug/merge2_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_map/debug/merge3_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_map/debug/merge4_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_set/debug/merge1_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_set/debug/merge2_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_set/debug/merge3_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_set/debug/merge4_neg.cc: New test.
>             * testsuite/util/testsuite_abi.h: [_GLIBCXX_DEBUG] Use 
> normal unordered container implementation.
>
> Tested under Linux x86_64.
>
> Ok to commit ?
>
> François
>
> On 25/10/21 8:08 pm, François Dumont wrote:
>> New patch with the proposed workaround below.
>>
>> I also slightly change the _M_merge_multi implementation so that if 
>> the new hash code computation raise an exception the node is simply 
>> not extracted rather than extracted and then released. This way, if 
>> it takes place on the 1st moved node the _GLIBCXX_DEBUG mode won't 
>> try to invalidate anything because the source size won't have changed.
>>
>> Ok to commit ?
>>
>> François
>>
>>
>> On 16/10/21 4:52 pm, Jonathan Wakely wrote:
>>>
>>>
>>> On Sat, 16 Oct 2021, 14:49 François Dumont via Libstdc++, 
>>> <libstdc++@gcc.gnu.org <mailto:libstdc%2B%2B@gcc.gnu.org>> wrote:
>>>
>>>     Hi
>>>
>>>          Here is the new proposal. My only concern is that we are
>>>     also using
>>>     hash or equal_to functors in the guard destructor.
>>>
>>>
>>>
>>> Can we catch any exception there, invalidate all iterators, and not 
>>> rethrow the exception?
>>>
>>>
>>>          I am going to enhance merge normal implementation to make
>>>     use of
>>>     the cached hash code when hash functors are the same between the
>>>     source
>>>     and destination of nodes. Maybe I'll be able to make use of it
>>>     in Debug
>>>     implementation too.
>>>
>>>     François
>>>
>>>
>>>     On 14/10/21 10:23 am, Jonathan Wakely wrote:
>>>     > On Wed, 13 Oct 2021 at 18:10, François Dumont via Libstdc++
>>>     > <libstdc++@gcc.gnu.org <mailto:libstdc%2B%2B@gcc.gnu.org>> wrote:
>>>     >> Hi
>>>     >>
>>>     >>       libstdc++: [_GLIBCXX_DEBUG] Implement unordered
>>>     container merge
>>>     >>
>>>     >>       The _GLIBCXX_DEBUG unordered containers need a
>>>     dedicated merge
>>>     >> implementation
>>>     >>       so that any existing iterator on the transfered nodes
>>>     is properly
>>>     >> invalidated.
>>>     >>
>>>     >>       Add typedef/using declaration for everything used as-is
>>>     from normal
>>>     >> implementation.
>>>     >>
>>>     >>       libstdc++-v3/ChangeLog:
>>>     >>
>>>     >>               * include/debug/safe_container.h
>>>     (_Safe_container<>): Make
>>>     >> all methods
>>>     >>               protected.
>>>     >>               * include/debug/safe_unordered_container.h
>>>     >>  (_Safe_unordered_container<>::_M_invalide_all): Make public.
>>>     >>  (_Safe_unordered_container<>::_M_invalide_if): Likewise.
>>>     >> (_Safe_unordered_container<>::_M_invalide_local_if): Likewise.
>>>     >>               * include/debug/unordered_map
>>>     >>  (unordered_map<>::mapped_type, pointer, const_pointer): New
>>>     >> typedef.
>>>     >>  (unordered_map<>::reference, const_reference,
>>>     >> difference_type): New typedef.
>>>     >>  (unordered_map<>::get_allocator, empty, size, max_size):
>>>     >> Add usings.
>>>     >>  (unordered_map<>::bucket_count, max_bucket_count, bucket):
>>>     >> Add usings.
>>>     >>  (unordered_map<>::hash_function, key_equal, count,
>>>     >> contains): Add usings.
>>>     >>  (unordered_map<>::operator[], at, rehash, reserve): Add usings.
>>>     >>               (unordered_map<>::merge): New.
>>>     >>  (unordered_multimap<>::mapped_type, pointer,
>>>     >> const_pointer): New typedef.
>>>     >>  (unordered_multimap<>::reference, const_reference,
>>>     >> difference_type): New typedef.
>>>     >>  (unordered_multimap<>::get_allocator, empty, size,
>>>     >> max_size): Add usings.
>>>     >>  (unordered_multimap<>::bucket_count, max_bucket_count,
>>>     >> bucket): Add usings.
>>>     >>  (unordered_multimap<>::hash_function, key_equal, count,
>>>     >> contains): Add usings.
>>>     >>  (unordered_multimap<>::rehash, reserve): Add usings.
>>>     >>  (unordered_multimap<>::merge): New.
>>>     >>               * include/debug/unordered_set
>>>     >>  (unordered_set<>::mapped_type, pointer, const_pointer): New
>>>     >> typedef.
>>>     >>  (unordered_set<>::reference, const_reference,
>>>     >> difference_type): New typedef.
>>>     >>  (unordered_set<>::get_allocator, empty, size, max_size):
>>>     >> Add usings.
>>>     >>  (unordered_set<>::bucket_count, max_bucket_count, bucket):
>>>     >> Add usings.
>>>     >>  (unordered_set<>::hash_function, key_equal, count,
>>>     >> contains): Add usings.
>>>     >>               (unordered_set<>::rehash, reserve): Add usings.
>>>     >>               (unordered_set<>::merge): New.
>>>     >>  (unordered_multiset<>::mapped_type, pointer,
>>>     >> const_pointer): New typedef.
>>>     >>  (unordered_multiset<>::reference, const_reference,
>>>     >> difference_type): New typedef.
>>>     >>  (unordered_multiset<>::get_allocator, empty, size,
>>>     >> max_size): Add usings.
>>>     >>  (unordered_multiset<>::bucket_count, max_bucket_count,
>>>     >> bucket): Add usings.
>>>     >>  (unordered_multiset<>::hash_function, key_equal, count,
>>>     >> contains): Add usings.
>>>     >>  (unordered_multiset<>::rehash, reserve): Add usings.
>>>     >>  (unordered_multiset<>::merge): New.
>>>     >>               *
>>>     >> testsuite/23_containers/unordered_map/debug/merge1_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >> testsuite/23_containers/unordered_map/debug/merge2_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >> testsuite/23_containers/unordered_map/debug/merge3_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >> testsuite/23_containers/unordered_map/debug/merge4_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >>
>>>     testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >>
>>>     testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >>
>>>     testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >>
>>>     testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >>
>>>     testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >>
>>>     testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >>
>>>     testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >>
>>>     testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >> testsuite/23_containers/unordered_set/debug/merge1_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >> testsuite/23_containers/unordered_set/debug/merge2_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >> testsuite/23_containers/unordered_set/debug/merge3_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >> testsuite/23_containers/unordered_set/debug/merge4_neg.cc:
>>>     New test.
>>>     >>               * testsuite/util/testsuite_abi.h:
>>>     [_GLIBCXX_DEBUG] Use
>>>     >> normal unordered container implementation.
>>>     >>
>>>     >> Tested under Linux x86_64.
>>>     >>
>>>     >> Ok to commit ?
>>>     > Yes, thanks. But ...
>>>     >
>>>     > This looks like an improvement over what we have now, but not 100%
>>>     > correct. The merge functions can exit via exception (if any hash
>>>     > function or equality predicate throws), and if that happens
>>>     the safe
>>>     > iterators will not get invalidated. I think we need to call
>>>     > _Base::merge in a try-block, and do the iterator invalidation
>>>     whether
>>>     > we return normally or via an exception.
>>>     >
>>>     > Something like:
>>>     >
>>>     >    template<typename _H2, typename _P2>
>>>     >      void
>>>     >      merge(unordered_set<_Value, _H2, _P2, _Alloc>& __source)
>>>     >      {
>>>     >        struct _Guard
>>>     >        {
>>>     >          _Guard(unordered_set& __source) noexcept
>>>     >          : _M_source(__source), _M_size(__source.size())
>>>     >          { }
>>>     >
>>>     >          ~_Guard()
>>>     >          {
>>>     >            const size_type __size = _M_source.size();
>>>     >            if (__size != _M_size)
>>>     >              {
>>>     >                if (__size == 0)
>>>     >                  _M_source._M_invalidate_all();
>>>     >                else
>>>     >                  {
>>>     >                    auto __pred = [&__source](auto __it)
>>>     >                                  { return
>>>     __source.count(*__it) == 0; };
>>>     > __source._M_invalidate_if(__pred);
>>>     > __source._M_invalidate_local_if(__pred);
>>>     >                  }
>>>     >              }
>>>     >          }
>>>     >
>>>     >          _Guard(const _Guard&) = delete;
>>>     >
>>>     >          unordered_set& _M_source;
>>>     >          const size_type _M_size;
>>>     >        };
>>>     >        _Guard __guard(__source);
>>>     >        _Base::merge(__source._M_base());
>>>     >      }
>>>     >
>>>
>>
>


[-- Attachment #2: merge.patch --]
[-- Type: text/x-patch, Size: 34367 bytes --]

diff --git a/libstdc++-v3/include/bits/hashtable.h b/libstdc++-v3/include/bits/hashtable.h
index 25c45d3ba85..0e949d73614 100644
--- a/libstdc++-v3/include/bits/hashtable.h
+++ b/libstdc++-v3/include/bits/hashtable.h
@@ -1065,14 +1065,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       /// Merge from a compatible container into one with unique keys.
       template<typename _Compatible_Hashtable>
 	void
-	_M_merge_unique(_Compatible_Hashtable& __src) noexcept
+	_M_merge_unique(_Compatible_Hashtable& __src)
 	{
 	  static_assert(is_same_v<typename _Compatible_Hashtable::node_type,
 	      node_type>, "Node types are compatible");
 	  __glibcxx_assert(get_allocator() == __src.get_allocator());
 
 	  auto __n_elt = __src.size();
-	  for (auto __i = __src.begin(), __end = __src.end(); __i != __end;)
+	  for (auto __i = __src.cbegin(), __end = __src.cend(); __i != __end;)
 	    {
 	      auto __pos = __i++;
 	      const key_type& __k = _ExtractKey{}(*__pos);
@@ -1093,15 +1093,22 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       /// Merge from a compatible container into one with equivalent keys.
       template<typename _Compatible_Hashtable>
 	void
-	_M_merge_multi(_Compatible_Hashtable& __src) noexcept
+	_M_merge_multi(_Compatible_Hashtable& __src)
 	{
 	  static_assert(is_same_v<typename _Compatible_Hashtable::node_type,
 	      node_type>, "Node types are compatible");
 	  __glibcxx_assert(get_allocator() == __src.get_allocator());
 
 	  this->reserve(size() + __src.size());
-	  for (auto __i = __src.begin(), __end = __src.end(); __i != __end;)
-	    _M_reinsert_node_multi(cend(), __src.extract(__i++));
+	  for (auto __i = __src.cbegin(), __end = __src.cend(); __i != __end;)
+	    {
+	      auto __pos = __i++;
+	      const key_type& __k = _ExtractKey{}(*__pos);
+	      __hash_code __code = this->_M_hash_code(__k);
+	      auto __nh = __src.extract(__pos);
+	      _M_insert_multi_node(nullptr, __code, __nh._M_ptr);
+	      __nh._M_ptr = nullptr;
+	    }
 	}
 #endif // C++17
 
diff --git a/libstdc++-v3/include/bits/hashtable_policy.h b/libstdc++-v3/include/bits/hashtable_policy.h
index 8c72043e368..c0295b75963 100644
--- a/libstdc++-v3/include/bits/hashtable_policy.h
+++ b/libstdc++-v3/include/bits/hashtable_policy.h
@@ -60,19 +60,19 @@ namespace __detail
 
   // Helper function: return distance(first, last) for forward
   // iterators, or 0/1 for input iterators.
-  template<class _Iterator>
+  template<typename _Iterator>
     inline typename std::iterator_traits<_Iterator>::difference_type
     __distance_fw(_Iterator __first, _Iterator __last,
 		  std::input_iterator_tag)
     { return __first != __last ? 1 : 0; }
 
-  template<class _Iterator>
+  template<typename _Iterator>
     inline typename std::iterator_traits<_Iterator>::difference_type
     __distance_fw(_Iterator __first, _Iterator __last,
 		  std::forward_iterator_tag)
     { return std::distance(__first, __last); }
 
-  template<class _Iterator>
+  template<typename _Iterator>
     inline typename std::iterator_traits<_Iterator>::difference_type
     __distance_fw(_Iterator __first, _Iterator __last)
     { return __distance_fw(__first, __last,
diff --git a/libstdc++-v3/include/debug/safe_container.h b/libstdc++-v3/include/debug/safe_container.h
index 97c47167fe8..5de55d69f34 100644
--- a/libstdc++-v3/include/debug/safe_container.h
+++ b/libstdc++-v3/include/debug/safe_container.h
@@ -78,7 +78,6 @@ namespace __gnu_debug
       { }
 #endif
 
-    public:
       // Copy assignment invalidate all iterators.
       _Safe_container&
       operator=(const _Safe_container&) _GLIBCXX_NOEXCEPT
diff --git a/libstdc++-v3/include/debug/safe_unordered_container.h b/libstdc++-v3/include/debug/safe_unordered_container.h
index aae1e2dab60..ce9d9ea4091 100644
--- a/libstdc++-v3/include/debug/safe_unordered_container.h
+++ b/libstdc++-v3/include/debug/safe_unordered_container.h
@@ -72,6 +72,96 @@ namespace __gnu_debug
 		{ return __it != __local_end; });
       }
 
+#if __cplusplus > 201402L
+      template<typename _ExtractKey, typename _Source>
+	struct _UContInvalidatePred
+	{
+	  template<typename _Iterator>
+	    bool
+	    operator()(_Iterator __it) const
+	    { return _M_source.count(_ExtractKey{}(*__it)) == 0; }
+
+	  const _Source& _M_source;
+	};
+
+      template<typename _ExtractKey, typename _Source>
+	struct _UMContInvalidatePred
+	{
+	  template<typename _Iterator>
+	    bool
+	    operator()(_Iterator __it) const
+	    {
+	      auto __rng =
+		_M_source._M_base().equal_range(_ExtractKey{}(*__it));
+	      for (auto __rit = __rng.first;
+		   __rit != __rng.second; ++__rit)
+		{
+		  if (__it == __rit)
+		    return false;
+		}
+
+	      return true;
+	    }
+
+	  const _Source& _M_source;
+	};
+
+      template<typename _Source, typename _InvalidatePred>
+	struct _UContMergeGuard
+	{
+	  _UContMergeGuard(_Source& __src) noexcept
+	  : _M_source(__src), _M_size(__src.size()), _M_pred { __src }
+	  { }
+
+	  _UContMergeGuard(const _UContMergeGuard&) = delete;
+
+	  ~_UContMergeGuard()
+	  {
+	    const std::size_t __size = _M_source.size();
+	    if (__size == _M_size)
+	      return;
+
+	    __try
+	      {
+		if (__size == 0)
+		  _M_source._M_invalidate_all();
+		else
+		  {
+		    _M_source._M_invalidate_if(_M_pred);
+		    _M_source._M_invalidate_local_if(_M_pred);
+		  }
+	      }
+	    __catch(...)
+	      {
+		_M_source._M_invalidate_all();
+	      }
+	  }
+
+	  _Source& _M_source;
+	  const std::size_t _M_size;
+	  _InvalidatePred _M_pred;
+	};
+
+      template<typename _ExtractKey, typename _Source>
+	static _UContMergeGuard<_Source,
+				_UContInvalidatePred<_ExtractKey, _Source>>
+	_S_uc_guard(_ExtractKey, _Source& __src)
+	{
+	  typedef _UContInvalidatePred<_ExtractKey, _Source> _InvalidatePred;
+	  return _UContMergeGuard<_Source, _InvalidatePred>(__src);
+	}
+
+      template<typename _ExtractKey, typename _Source>
+	static _UContMergeGuard<_Source,
+				_UMContInvalidatePred<_ExtractKey, _Source>>
+	_S_umc_guard(_ExtractKey, _Source& __src)
+	{
+	  typedef _UMContInvalidatePred<_ExtractKey, _Source> _InvalidatePred;
+	  return _UContMergeGuard<_Source, _InvalidatePred>(__src);
+	}
+#endif // C++17
+
+    public:
       void
       _M_invalidate_all()
       {
diff --git a/libstdc++-v3/include/debug/unordered_map b/libstdc++-v3/include/debug/unordered_map
index bb697d364ea..d6e184a2e99 100644
--- a/libstdc++-v3/include/debug/unordered_map
+++ b/libstdc++-v3/include/debug/unordered_map
@@ -97,7 +97,12 @@ namespace __debug
 
       typedef typename _Base::key_type			key_type;
       typedef typename _Base::value_type		value_type;
+      typedef typename _Base::mapped_type		mapped_type;
 
+      typedef typename _Base::pointer			pointer;
+      typedef typename _Base::const_pointer		const_pointer;
+      typedef typename _Base::reference			reference;
+      typedef typename _Base::const_reference		const_reference;
       typedef __gnu_debug::_Safe_iterator<
 	_Base_iterator, unordered_map>			iterator;
       typedef __gnu_debug::_Safe_iterator<
@@ -106,6 +111,7 @@ namespace __debug
 	_Base_local_iterator, unordered_map>		local_iterator;
       typedef __gnu_debug::_Safe_local_iterator<
 	_Base_const_local_iterator, unordered_map>	const_local_iterator;
+      typedef typename _Base::difference_type		difference_type;
 
       unordered_map() = default;
 
@@ -209,6 +215,11 @@ namespace __debug
 	return *this;
       }
 
+      using _Base::get_allocator;
+      using _Base::empty;
+      using _Base::size;
+      using _Base::max_size;
+
       void
       swap(unordered_map& __x)
 	noexcept( noexcept(declval<_Base&>().swap(__x)) )
@@ -291,6 +302,10 @@ namespace __debug
 	return { _Base::cend(__b), this };
       }
 
+      using _Base::bucket_count;
+      using _Base::max_bucket_count;
+      using _Base::bucket;
+
       size_type
       bucket_size(size_type __b) const
       {
@@ -298,6 +313,8 @@ namespace __debug
 	return _Base::bucket_size(__b);
       }
 
+      using _Base::load_factor;
+
       float
       max_load_factor() const noexcept
       { return _Base::max_load_factor(); }
@@ -538,9 +555,38 @@ namespace __debug
 	return { _Base::insert(__hint.base(), std::move(__nh)), this };
       }
 
-      using _Base::merge;
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  auto __guard
+	    = _Safe::_S_uc_guard(std::__detail::_Select1st{}, __source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  auto __guard
+	    = _Safe::_S_umc_guard(std::__detail::_Select1st{}, __source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
 #endif // C++17
 
+      using _Base::hash_function;
+      using _Base::key_eq;
+
       iterator
       find(const key_type& __key)
       { return { _Base::find(__key), this }; }
@@ -567,6 +613,11 @@ namespace __debug
 	{ return { _Base::find(__k), this }; }
 #endif
 
+      using _Base::count;
+#if __cplusplus > 201703L
+      using _Base::contains;
+#endif
+
       std::pair<iterator, iterator>
       equal_range(const key_type& __key)
       {
@@ -605,6 +656,9 @@ namespace __debug
 	}
 #endif
 
+      using _Base::operator[];
+      using _Base::at;
+
       size_type
       erase(const key_type& __key)
       {
@@ -651,6 +705,9 @@ namespace __debug
 	return { __next, this };
       }
 
+      using _Base::rehash;
+      using _Base::reserve;
+
       _Base&
       _M_base() noexcept	{ return *this; }
 
@@ -843,7 +900,12 @@ namespace __debug
 
       typedef typename _Base::key_type			key_type;
       typedef typename _Base::value_type		value_type;
+      typedef typename _Base::mapped_type		mapped_type;
 
+      typedef typename _Base::pointer			pointer;
+      typedef typename _Base::const_pointer		const_pointer;
+      typedef typename _Base::reference			reference;
+      typedef typename _Base::const_reference		const_reference;
       typedef __gnu_debug::_Safe_iterator<
 	_Base_iterator, unordered_multimap>		iterator;
       typedef __gnu_debug::_Safe_iterator<
@@ -852,6 +914,7 @@ namespace __debug
 	_Base_local_iterator, unordered_multimap>	local_iterator;
       typedef __gnu_debug::_Safe_local_iterator<
 	_Base_const_local_iterator, unordered_multimap>	const_local_iterator;
+      typedef typename _Base::difference_type		difference_type;
 
       unordered_multimap() = default;
 
@@ -952,6 +1015,11 @@ namespace __debug
 	return *this;
       }
 
+      using _Base::get_allocator;
+      using _Base::empty;
+      using _Base::size;
+      using _Base::max_size;
+
       void
       swap(unordered_multimap& __x)
 	noexcept( noexcept(declval<_Base&>().swap(__x)) )
@@ -1034,6 +1102,10 @@ namespace __debug
 	return { _Base::cend(__b), this };
       }
 
+      using _Base::bucket_count;
+      using _Base::max_bucket_count;
+      using _Base::bucket;
+
       size_type
       bucket_size(size_type __b) const
       {
@@ -1192,9 +1264,38 @@ namespace __debug
 	return { _Base::insert(__hint.base(), std::move(__nh)), this };
       }
 
-      using _Base::merge;
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  auto __guard
+	    = _Safe::_S_umc_guard(std::__detail::_Select1st{}, __source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  auto __guard
+	    = _Safe::_S_uc_guard(std::__detail::_Select1st{}, __source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
 #endif // C++17
 
+      using _Base::hash_function;
+      using _Base::key_eq;
+
       iterator
       find(const key_type& __key)
       { return { _Base::find(__key), this }; }
@@ -1221,6 +1322,11 @@ namespace __debug
 	{ return { _Base::find(__k), this }; }
 #endif
 
+      using _Base::count;
+#if __cplusplus > 201703L
+      using _Base::contains;
+#endif
+
       std::pair<iterator, iterator>
       equal_range(const key_type& __key)
       {
@@ -1309,6 +1415,9 @@ namespace __debug
 	return { __next, this };
       }
 
+      using _Base::rehash;
+      using _Base::reserve;
+
       _Base&
       _M_base() noexcept { return *this; }
 
diff --git a/libstdc++-v3/include/debug/unordered_set b/libstdc++-v3/include/debug/unordered_set
index c25910946b7..7dc91fa862d 100644
--- a/libstdc++-v3/include/debug/unordered_set
+++ b/libstdc++-v3/include/debug/unordered_set
@@ -88,6 +88,7 @@ namespace __debug
 
     public:
       typedef typename _Base::size_type			size_type;
+      typedef typename _Base::difference_type		difference_type;
       typedef typename _Base::hasher			hasher;
       typedef typename _Base::key_equal			key_equal;
       typedef typename _Base::allocator_type		allocator_type;
@@ -95,6 +96,10 @@ namespace __debug
       typedef typename _Base::key_type			key_type;
       typedef typename _Base::value_type		value_type;
 
+      typedef typename _Base::pointer			pointer;
+      typedef typename _Base::const_pointer		const_pointer;
+      typedef typename _Base::reference			reference;
+      typedef typename _Base::const_reference		const_reference;
       typedef __gnu_debug::_Safe_iterator<
 	_Base_iterator, unordered_set>			iterator;
       typedef __gnu_debug::_Safe_iterator<
@@ -203,6 +208,11 @@ namespace __debug
 	return *this;
       }
 
+      using _Base::get_allocator;
+      using _Base::empty;
+      using _Base::size;
+      using _Base::max_size;
+
       void
       swap(unordered_set& __x)
 	noexcept( noexcept(declval<_Base&>().swap(__x)) )
@@ -285,6 +295,9 @@ namespace __debug
 	return { _Base::cend(__b), this };
       }
 
+      using _Base::bucket_count;
+      using _Base::max_bucket_count;
+
       size_type
       bucket_size(size_type __b) const
       {
@@ -292,6 +305,9 @@ namespace __debug
 	return _Base::bucket_size(__b);
       }
 
+      using _Base::bucket;
+      using _Base::load_factor;
+
       float
       max_load_factor() const noexcept
       { return _Base::max_load_factor(); }
@@ -303,6 +319,9 @@ namespace __debug
 	_Base::max_load_factor(__f);
       }
 
+      using _Base::rehash;
+      using _Base::reserve;
+
       template<typename... _Args>
 	std::pair<iterator, bool>
 	emplace(_Args&&... __args)
@@ -423,9 +442,38 @@ namespace __debug
 	return { _Base::insert(__hint.base(), std::move(__nh)), this };
       }
 
-      using _Base::merge;
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_set<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  auto __guard
+	    = _Safe::_S_uc_guard(std::__detail::_Identity{}, __source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_set<_Value, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  auto __guard
+	    = _Safe::_S_umc_guard(std::__detail::_Identity{}, __source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
 #endif // C++17
 
+      using _Base::hash_function;
+      using _Base::key_eq;
+
       iterator
       find(const key_type& __key)
       { return { _Base::find(__key), this }; }
@@ -452,6 +500,12 @@ namespace __debug
 	{ return { _Base::find(__k), this }; }
 #endif
 
+      using _Base::count;
+
+#if __cplusplus > 201703L
+      using _Base::contains;
+#endif
+
       std::pair<iterator, iterator>
       equal_range(const key_type& __key)
       {
@@ -707,6 +761,7 @@ namespace __debug
 
     public:
       typedef typename _Base::size_type			size_type;
+      typedef typename _Base::difference_type		difference_type;
       typedef typename _Base::hasher			hasher;
       typedef typename _Base::key_equal			key_equal;
       typedef typename _Base::allocator_type		allocator_type;
@@ -714,6 +769,10 @@ namespace __debug
       typedef typename _Base::key_type			key_type;
       typedef typename _Base::value_type		value_type;
 
+      typedef typename _Base::pointer			pointer;
+      typedef typename _Base::const_pointer		const_pointer;
+      typedef typename _Base::reference			reference;
+      typedef typename _Base::const_reference		const_reference;
       typedef __gnu_debug::_Safe_iterator<
 	_Base_iterator, unordered_multiset>		iterator;
       typedef __gnu_debug::_Safe_iterator<
@@ -822,6 +881,11 @@ namespace __debug
 	return *this;
       }
 
+      using _Base::get_allocator;
+      using _Base::empty;
+      using _Base::size;
+      using _Base::max_size;
+
       void
       swap(unordered_multiset& __x)
 	noexcept( noexcept(declval<_Base&>().swap(__x)) )
@@ -904,6 +968,9 @@ namespace __debug
 	return { _Base::cend(__b), this };
       }
 
+      using _Base::bucket_count;
+      using _Base::max_bucket_count;
+
       size_type
       bucket_size(size_type __b) const
       {
@@ -911,6 +978,9 @@ namespace __debug
 	return _Base::bucket_size(__b);
       }
 
+      using _Base::bucket;
+      using _Base::load_factor;
+
       float
       max_load_factor() const noexcept
       { return _Base::max_load_factor(); }
@@ -922,6 +992,9 @@ namespace __debug
 	_Base::max_load_factor(__f);
       }
 
+      using _Base::rehash;
+      using _Base::reserve;
+
       template<typename... _Args>
 	iterator
 	emplace(_Args&&... __args)
@@ -1037,9 +1110,38 @@ namespace __debug
 	return { _Base::insert(__hint.base(), std::move(__nh)), this };
       }
 
-      using _Base::merge;
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  auto __guard
+	    = _Safe::_S_umc_guard(std::__detail::_Identity{}, __source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_set<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  auto __guard
+	    = _Safe::_S_uc_guard(std::__detail::_Identity{}, __source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_set<_Value, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
 #endif // C++17
 
+      using _Base::hash_function;
+      using _Base::key_eq;
+
       iterator
       find(const key_type& __key)
       { return { _Base::find(__key), this }; }
@@ -1066,6 +1168,12 @@ namespace __debug
 	{ return { _Base::find(__k), this }; }
 #endif
 
+      using _Base::count;
+
+#if __cplusplus > 201703L
+      using _Base::contains;
+#endif
+
       std::pair<iterator, iterator>
       equal_range(const key_type& __key)
       {
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge1_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge1_neg.cc
new file mode 100644
index 00000000000..6d007944918
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge1_neg.cc
@@ -0,0 +1,31 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_map<int, double>;
+
+void
+test01()
+{
+  test_type c0{ { 1, 3.5 }, { 2, 5.5 }, { 3, 7.5 }, { 5, 11.5 }, { 6, 13.5 } };
+  test_type c1{ { 1, 3.5 }, { 2, 5.5 }, { 3, 7.5 }, { 4, 9.5 } };
+
+  auto it2 = c1.find(2);
+  auto it4 = c1.find(4);
+  VERIFY( it2->second == 5.5 );
+  VERIFY( it4->second == 9.5 );
+
+  c0.merge(c1);
+
+  VERIFY( it2->second == 5.5 );
+  VERIFY( it4 != it2 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge2_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge2_neg.cc
new file mode 100644
index 00000000000..543cd960a5e
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge2_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_map<int, int>;
+
+void
+test01()
+{
+  test_type c0{ { 1, 1 }, { 2, 2 }, { 3, 3 }, { 5, 5 }, { 6, 6 } };
+  test_type c1{ { 1, 1 }, { 2, 2 }, { 3, 3 }, { 4, 4 } };
+
+  auto it2 = c1.find(2);
+  auto it4 = c1.find(4);
+  VERIFY( it2->second == 2 );
+  VERIFY( it4->second == 4 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it2->second == 2 );
+  VERIFY( it2 != it4 ); // Invalid iterator.
+}
+
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge3_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge3_neg.cc
new file mode 100644
index 00000000000..8e234799cbf
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge3_neg.cc
@@ -0,0 +1,42 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_map<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 2, 2 }, { 3, 3 },
+     { 5, 5 }, { 6, 6 }, { 7, 7 }
+    };
+  std::unordered_multimap<int, int> c1
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 }, { 2, 2 },
+     { 3, 3 }, { 3, 3 }, { 4, 4 }, { 4, 4 },
+     { 5, 5 }
+    };
+
+  auto it1 = c1.find(1);
+  auto it41 = c1.find(4);
+  auto it42 = it41;
+  ++it42;
+  VERIFY( it42->second == 4 );
+
+  c0.merge(c1);
+
+  VERIFY( it1->second == 1 );
+  VERIFY( c1.count(4) == 1 );
+  VERIFY( it41 != it42 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge4_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge4_neg.cc
new file mode 100644
index 00000000000..3c9c8268f8c
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge4_neg.cc
@@ -0,0 +1,42 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_map<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 2, 2 }, { 3, 3 },
+     { 5, 5 }, { 6, 6 }, { 7, 7 }
+    };
+  std::unordered_multimap<int, int> c1
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 }, { 2, 2 },
+     { 3, 3 }, { 3, 3 }, { 4, 4 }, { 4, 4 },
+     { 5, 5 }
+    };
+
+  auto it1 = c1.find(1);
+  auto it41 = c1.find(4);
+  auto it42 = it41;
+  ++it42;
+  VERIFY( it42->second == 4 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it1->second == 1 );
+  VERIFY( c1.count(4) == 1 );
+  VERIFY( it41 != it42 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc
new file mode 100644
index 00000000000..25b3b9e0c75
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multimap<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 },
+     { 2, 2 }, { 3, 3 }, { 3, 3 }
+    };
+  test_type c1 = c0;
+
+  auto it = c1.find(2);
+  VERIFY( it->second == 2 );
+
+  c0.merge(c1);
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc
new file mode 100644
index 00000000000..8d28d83b972
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multimap<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 },
+     { 2, 2 }, { 3, 3 }, { 3, 3 }
+    };
+  test_type c1 = c0;
+
+  auto it = c1.find(2);
+  VERIFY( it->second == 2 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc
new file mode 100644
index 00000000000..5db91a27ca0
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multimap<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 },
+     { 2, 2 }, { 3, 3 }, { 3, 3 }
+    };
+  std::unordered_map<int, int> c1{ { 1, 1 }, { 2, 2 }, { 3, 3 } };
+
+  auto it = c1.find(2);
+  VERIFY( it->second == 2 );
+
+  c0.merge(c1);
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc
new file mode 100644
index 00000000000..a1636703569
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multimap<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 },
+     { 2, 2 }, { 3, 3 }, { 3, 3 }
+    };
+  std::unordered_map<int, int> c1{ { 1, 1 }, { 2, 2 }, { 3, 3 } };
+
+  auto it = c1.find(2);
+  VERIFY( it->second == 2 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc
new file mode 100644
index 00000000000..bce8da7f6cf
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multiset<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 1, 2, 2, 3, 3 };
+  test_type c1 = c0;
+
+  auto it = c1.find(2);
+  VERIFY( *it == 2 );
+
+  c0.merge(c1);
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc
new file mode 100644
index 00000000000..72317a32e89
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multiset<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 1, 2, 2, 3, 3 };
+  test_type c1 = c0;
+
+  auto it = c1.find(2);
+  VERIFY( *it == 2 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc
new file mode 100644
index 00000000000..1b1f4870dd1
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multiset<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 1, 2, 2, 3, 3 };
+  std::unordered_set<int> c1{ 1, 2, 3 };
+
+  auto it = c1.find(2);
+  VERIFY( *it == 2 );
+
+  c0.merge(c1);
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc
new file mode 100644
index 00000000000..5005cf8468a
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multiset<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 1, 2, 2, 3, 3 };
+  std::unordered_set<int> c1{ 1, 2, 3 };
+
+  auto it = c1.find(2);
+  VERIFY( *it == 2 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge1_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge1_neg.cc
new file mode 100644
index 00000000000..8a2bc6e468f
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge1_neg.cc
@@ -0,0 +1,31 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_set<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 2, 3, 5, 6 };
+  test_type c1{ 1, 2, 3, 4 };
+
+  auto it2 = c1.find(2);
+  auto it4 = c1.find(4);
+  VERIFY( *it2 == 2 );
+  VERIFY( *it4 == 4 );
+
+  c0.merge(c1);
+
+  VERIFY( *it2 == 2 );
+  VERIFY( it2 != it4 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge2_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge2_neg.cc
new file mode 100644
index 00000000000..3ac96540770
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge2_neg.cc
@@ -0,0 +1,31 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_set<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 2, 3, 5, 6 };
+  test_type c1{ 1, 2, 3, 4 };
+
+  auto it2 = c1.find(2);
+  auto it4 = c1.find(4);
+  VERIFY( *it2 == 2 );
+  VERIFY( *it4 == 4 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( *it2 == 2 );
+  VERIFY( it2 != it4 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge3_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge3_neg.cc
new file mode 100644
index 00000000000..7e93b55d507
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge3_neg.cc
@@ -0,0 +1,33 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_set<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 2, 3, 5, 6, 7 };
+  std::unordered_multiset<int> c1{ 1, 1, 2, 2, 3, 3, 4, 4, 5 };
+
+  auto it1 = c1.find(1);
+  auto it41 = c1.find(4);
+  auto it42 = it41;
+  ++it42;
+  VERIFY( *it42 == 4 );
+
+  c0.merge(c1);
+
+  VERIFY( *it1 == 1 );
+  VERIFY( c1.count(4) == 1 );
+  VERIFY( it41 != it42 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge4_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge4_neg.cc
new file mode 100644
index 00000000000..14c8ff63b05
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge4_neg.cc
@@ -0,0 +1,33 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_set<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 2, 3, 5, 6, 7 };
+  std::unordered_multiset<int> c1{ 1, 1, 2, 2, 3, 3, 4, 4, 5 };
+
+  auto it1 = c1.find(1);
+  auto it41 = c1.find(4);
+  auto it42 = it41;
+  ++it42;
+  VERIFY( *it42 == 4 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( *it1 == 1 );
+  VERIFY( c1.count(4) == 1 );
+  VERIFY( it41 != it42 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/util/testsuite_abi.h b/libstdc++-v3/testsuite/util/testsuite_abi.h
index 667c46c33d3..4a0cf64f6fb 100644
--- a/libstdc++-v3/testsuite/util/testsuite_abi.h
+++ b/libstdc++-v3/testsuite/util/testsuite_abi.h
@@ -24,7 +24,11 @@
 #include <locale>
 #if __cplusplus >= 201103L
 # include <unordered_map>
+# ifdef _GLIBCXX_DEBUG
+namespace unord = std::_GLIBCXX_STD_C;
+# else
 namespace unord = std;
+# endif
 #else
 # include <tr1/unordered_map>
 namespace unord = std::tr1;

  reply	other threads:[~2021-11-08 21:36 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-10-13 17:10 François Dumont
2021-10-14  8:23 ` Jonathan Wakely
2021-10-16 13:47   ` François Dumont
2021-10-16 14:52     ` Jonathan Wakely
2021-10-21 16:51       ` François Dumont
2021-10-21 16:55         ` Jonathan Wakely
2021-10-22  5:22           ` François Dumont
2021-10-25 18:08       ` François Dumont
2021-11-06 13:51         ` François Dumont
2021-11-08 21:36           ` François Dumont [this message]
2021-11-09 16:25             ` Jonathan Wakely
2021-11-10  5:47               ` François Dumont
2021-11-10  9:38                 ` Jonathan Wakely
2021-11-15 18:16                   ` François Dumont
2021-11-10 11:55               ` Jonathan Wakely
2021-11-11 20:41                 ` Jonathan Wakely
2021-11-11 21:33                   ` François Dumont
2021-11-11 22:01                     ` Jonathan Wakely
2021-11-10  0:05             ` H.J. Lu
2021-11-10  5:44               ` François Dumont
2021-11-10  7:26                 ` Jonathan Wakely

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=f20104db-f50e-7773-c382-28c10acf9bfc@gmail.com \
    --to=frs.dumont@gmail.com \
    --cc=gcc-patches@gcc.gnu.org \
    --cc=jwakely.gcc@gmail.com \
    --cc=jwakely@redhat.com \
    --cc=libstdc++@gcc.gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).