Skip to content

Commit e60b49a

Browse files
committed
[libc++] Enable randomized element order during rehash in unordered_{set,map,multiset,multimap} under _LIBCPP_DEBUG_RANDOMIZE_UNSPECIFIED_STABILITY
- Adds randomization of element order during rehash in unordered containers when the _LIBCPP_DEBUG_RANDOMIZE_UNSPECIFIED_STABILITY flag is set, similar to existing behavior in sort, nth_element, and partial_sort.
1 parent 99b85ca commit e60b49a

File tree

3 files changed

+138
-0
lines changed

3 files changed

+138
-0
lines changed

libcxx/docs/DesignDocs/UnspecifiedBehaviorRandomization.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,5 +82,7 @@ Currently supported randomization
8282
on the order of the remaining part
8383
* ``std::nth_element``, there is no guarantee on the order from both sides of the
8484
partition
85+
* ``std::unordered_{set,map}``, there is no guarantee on the order of the elements
86+
* ``std::unordered_{multiset,multimap}``, there is no guarantee on the order of the elements nor the order of equal elements
8587

8688
Patches welcome.

libcxx/include/__hash_table

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@
4545
#include <limits>
4646
#include <new> // __launder
4747

48+
#ifdef _LIBCPP_DEBUG_RANDOMIZE_UNSPECIFIED_STABILITY
49+
# include <__debug_utils/randomize_range.h>
50+
# include <__numeric/iota.h>
51+
#endif
52+
4853
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
4954
# pragma GCC system_header
5055
#endif
@@ -980,6 +985,9 @@ private:
980985
template <bool _UniqueKeys>
981986
_LIBCPP_HIDE_FROM_ABI void __do_rehash(size_type __n);
982987

988+
template <bool _UniqueKeys>
989+
_LIBCPP_HIDE_FROM_ABI void __debug_randomize_order();
990+
983991
template <class... _Args>
984992
_LIBCPP_HIDE_FROM_ABI __node_holder __construct_node(_Args&&... __args);
985993

@@ -1702,6 +1710,7 @@ void __hash_table<_Tp, _Hash, _Equal, _Alloc>::__rehash(size_type __n) _LIBCPP_D
17021710
template <class _Tp, class _Hash, class _Equal, class _Alloc>
17031711
template <bool _UniqueKeys>
17041712
void __hash_table<_Tp, _Hash, _Equal, _Alloc>::__do_rehash(size_type __nbc) {
1713+
__debug_randomize_order<_UniqueKeys>();
17051714
__pointer_allocator& __npa = __bucket_list_.get_deleter().__alloc();
17061715
__bucket_list_.reset(__nbc > 0 ? __pointer_alloc_traits::allocate(__npa, __nbc) : nullptr);
17071716
__bucket_list_.get_deleter().size() = __nbc;
@@ -1741,6 +1750,54 @@ void __hash_table<_Tp, _Hash, _Equal, _Alloc>::__do_rehash(size_type __nbc) {
17411750
}
17421751
}
17431752

1753+
template <class _Tp, class _Hash, class _Equal, class _Alloc>
1754+
template <bool _UniqueKeys>
1755+
void __hash_table<_Tp, _Hash, _Equal, _Alloc>::__debug_randomize_order() {
1756+
#ifdef _LIBCPP_DEBUG_RANDOMIZE_UNSPECIFIED_STABILITY
1757+
size_type __total_nodes = size();
1758+
size_type __initialized_nodes = 0;
1759+
1760+
// Storage to handle non-assignable, non-default constructible __node_holder.
1761+
union __nh_storage {
1762+
__nh_storage() {}
1763+
~__nh_storage() {}
1764+
__node_holder __nh;
1765+
};
1766+
1767+
auto __nh_storage_deleter = [&__initialized_nodes](__nh_storage* __p) {
1768+
for (size_type __i = 0; __i < __initialized_nodes; ++__i)
1769+
__p[__i].__nh.~__node_holder();
1770+
delete[] __p;
1771+
};
1772+
1773+
// Allocate storage for nodes and indices.
1774+
unique_ptr<__nh_storage[], decltype(__nh_storage_deleter)> __nodes(
1775+
new __nh_storage[__total_nodes], __nh_storage_deleter);
1776+
unique_ptr<size_type[]> __randomized_indices(new size_type[__total_nodes]);
1777+
1778+
// Move nodes into temporary storage.
1779+
for (; __initialized_nodes < __total_nodes; ++__initialized_nodes)
1780+
new (std::addressof(__nodes[__initialized_nodes].__nh)) __node_holder(remove(begin()));
1781+
1782+
// Randomize the order of indices.
1783+
std::iota(__randomized_indices.get(), __randomized_indices.get() + __total_nodes, size_type{0});
1784+
__debug_randomize_range<_ClassicAlgPolicy>(__randomized_indices.get(), __randomized_indices.get() + __total_nodes);
1785+
1786+
// Reinsert nodes into the hash table in randomized order.
1787+
for (size_type __i = 0; __i < __total_nodes; ++__i) {
1788+
__node_holder& __nh = __nodes[__randomized_indices[__i]].__nh;
1789+
__node_pointer __np = __nh->__upcast();
1790+
if _LIBCPP_CONSTEXPR_SINCE_CXX17 (_UniqueKeys) {
1791+
__node_insert_unique_perform(__np);
1792+
} else {
1793+
__next_pointer __pn = __node_insert_multi_prepare(__np->__hash(), __np->__get_value());
1794+
__node_insert_multi_perform(__np, __pn);
1795+
}
1796+
__nh.release();
1797+
}
1798+
#endif
1799+
}
1800+
17441801
template <class _Tp, class _Hash, class _Equal, class _Alloc>
17451802
template <class _Key>
17461803
typename __hash_table<_Tp, _Hash, _Equal, _Alloc>::iterator
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// Test std::unordered_{set,map,multiset,multimap} randomization
10+
11+
// UNSUPPORTED: c++03
12+
// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_DEBUG_RANDOMIZE_UNSPECIFIED_STABILITY
13+
14+
#include <unordered_set>
15+
#include <unordered_map>
16+
#include <cassert>
17+
#include <vector>
18+
#include <algorithm>
19+
20+
const int kSize = 128;
21+
22+
template <typename T, typename F>
23+
T get_random(F get_value) {
24+
T v;
25+
v.reserve(kSize);
26+
for (int i = 0; i < kSize; ++i) {
27+
v.insert(get_value());
28+
}
29+
v.rehash(v.bucket_count() + 1);
30+
return v;
31+
}
32+
33+
template <typename T, typename F>
34+
T get_deterministic(F get_value) {
35+
T v;
36+
v.reserve(kSize);
37+
for (int i = 0; i < kSize; ++i) {
38+
v.insert(get_value());
39+
}
40+
return v;
41+
}
42+
43+
template <typename T>
44+
struct RemoveConst {
45+
using type = T;
46+
};
47+
48+
template <typename T, typename U>
49+
struct RemoveConst<std::pair<const T, U>> {
50+
using type = std::pair<T, U>;
51+
};
52+
53+
template <typename T, typename F>
54+
void test_randomization(F get_value) {
55+
T t1 = get_deterministic<T>(get_value), t2 = get_random<T>(get_value);
56+
57+
// Convert pair<const K, V> to pair<K, V> so it can be sorted
58+
using U = typename RemoveConst<typename T::value_type>::type;
59+
60+
std::vector<U> t1v(t1.begin(), t1.end()), t2v(t2.begin(), t2.end());
61+
62+
assert(t1v != t2v);
63+
64+
std::sort(t1v.begin(), t1v.end());
65+
std::sort(t2v.begin(), t2v.end());
66+
67+
assert(t1v == t2v);
68+
}
69+
70+
int main(int, char**) {
71+
int i = 0, j = 0;
72+
test_randomization<std::unordered_set<int>>([i]() mutable { return i++; });
73+
test_randomization<std::unordered_map<int, int>>([i, j]() mutable { return std::make_pair(i++, j++); });
74+
test_randomization<std::unordered_multiset<int>>([i]() mutable { return i++ % 32; });
75+
test_randomization<std::unordered_multimap<int, int>>([i, j]() mutable {
76+
return std::make_pair(i++ % 32, j++);
77+
});
78+
return 0;
79+
}

0 commit comments

Comments
 (0)