xref: /aosp_15_r20/external/abseil-cpp/absl/container/flat_hash_map_test.cc (revision 9356374a3709195abf420251b3e825997ff56c0f)
1 // Copyright 2018 The Abseil Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "absl/container/flat_hash_map.h"
16 
17 #include <cstddef>
18 #include <memory>
19 #include <string>
20 #include <type_traits>
21 #include <utility>
22 #include <vector>
23 
24 #include "gmock/gmock.h"
25 #include "gtest/gtest.h"
26 #include "absl/base/config.h"
27 #include "absl/container/internal/hash_generator_testing.h"
28 #include "absl/container/internal/hash_policy_testing.h"
29 #include "absl/container/internal/test_allocator.h"
30 #include "absl/container/internal/unordered_map_constructor_test.h"
31 #include "absl/container/internal/unordered_map_lookup_test.h"
32 #include "absl/container/internal/unordered_map_members_test.h"
33 #include "absl/container/internal/unordered_map_modifiers_test.h"
34 #include "absl/log/check.h"
35 #include "absl/meta/type_traits.h"
36 #include "absl/types/any.h"
37 
38 namespace absl {
39 ABSL_NAMESPACE_BEGIN
40 namespace container_internal {
41 namespace {
42 using ::absl::container_internal::hash_internal::Enum;
43 using ::absl::container_internal::hash_internal::EnumClass;
44 using ::testing::_;
45 using ::testing::IsEmpty;
46 using ::testing::Pair;
47 using ::testing::UnorderedElementsAre;
48 using ::testing::UnorderedElementsAreArray;
49 
50 // Check that absl::flat_hash_map works in a global constructor.
51 struct BeforeMain {
BeforeMainabsl::container_internal::__anone1ee0a730111::BeforeMain52   BeforeMain() {
53     absl::flat_hash_map<int, int> x;
54     x.insert({1, 1});
55     CHECK(x.find(0) == x.end()) << "x should not contain 0";
56     auto it = x.find(1);
57     CHECK(it != x.end()) << "x should contain 1";
58     CHECK(it->second) << "1 should map to 1";
59   }
60 };
61 const BeforeMain before_main;
62 
63 template <class K, class V>
64 using Map = flat_hash_map<K, V, StatefulTestingHash, StatefulTestingEqual,
65                           Alloc<std::pair<const K, V>>>;
66 
67 static_assert(!std::is_standard_layout<NonStandardLayout>(), "");
68 
69 using MapTypes =
70     ::testing::Types<Map<int, int>, Map<std::string, int>,
71                      Map<Enum, std::string>, Map<EnumClass, int>,
72                      Map<int, NonStandardLayout>, Map<NonStandardLayout, int>>;
73 
74 INSTANTIATE_TYPED_TEST_SUITE_P(FlatHashMap, ConstructorTest, MapTypes);
75 INSTANTIATE_TYPED_TEST_SUITE_P(FlatHashMap, LookupTest, MapTypes);
76 INSTANTIATE_TYPED_TEST_SUITE_P(FlatHashMap, MembersTest, MapTypes);
77 INSTANTIATE_TYPED_TEST_SUITE_P(FlatHashMap, ModifiersTest, MapTypes);
78 
79 using UniquePtrMapTypes = ::testing::Types<Map<int, std::unique_ptr<int>>>;
80 
81 INSTANTIATE_TYPED_TEST_SUITE_P(FlatHashMap, UniquePtrModifiersTest,
82                                UniquePtrMapTypes);
83 
TEST(FlatHashMap,StandardLayout)84 TEST(FlatHashMap, StandardLayout) {
85   struct Int {
86     explicit Int(size_t value) : value(value) {}
87     Int() : value(0) { ADD_FAILURE(); }
88     Int(const Int& other) : value(other.value) { ADD_FAILURE(); }
89     Int(Int&&) = default;
90     bool operator==(const Int& other) const { return value == other.value; }
91     size_t value;
92   };
93   static_assert(std::is_standard_layout<Int>(), "");
94 
95   struct Hash {
96     size_t operator()(const Int& obj) const { return obj.value; }
97   };
98 
99   // Verify that neither the key nor the value get default-constructed or
100   // copy-constructed.
101   {
102     flat_hash_map<Int, Int, Hash> m;
103     m.try_emplace(Int(1), Int(2));
104     m.try_emplace(Int(3), Int(4));
105     m.erase(Int(1));
106     m.rehash(2 * m.bucket_count());
107   }
108   {
109     flat_hash_map<Int, Int, Hash> m;
110     m.try_emplace(Int(1), Int(2));
111     m.try_emplace(Int(3), Int(4));
112     m.erase(Int(1));
113     m.clear();
114   }
115 }
116 
TEST(FlatHashMap,Relocatability)117 TEST(FlatHashMap, Relocatability) {
118   static_assert(absl::is_trivially_relocatable<int>::value, "");
119   static_assert(
120       absl::is_trivially_relocatable<std::pair<const int, int>>::value, "");
121   static_assert(
122       std::is_same<decltype(absl::container_internal::FlatHashMapPolicy<
123                             int, int>::transfer<std::allocator<char>>(nullptr,
124                                                                       nullptr,
125                                                                       nullptr)),
126                    std::true_type>::value,
127       "");
128 
129     struct NonRelocatable {
130       NonRelocatable() = default;
131       NonRelocatable(NonRelocatable&&) {}
132       NonRelocatable& operator=(NonRelocatable&&) { return *this; }
133       void* self = nullptr;
134     };
135 
136   EXPECT_FALSE(absl::is_trivially_relocatable<NonRelocatable>::value);
137   EXPECT_TRUE(
138       (std::is_same<decltype(absl::container_internal::FlatHashMapPolicy<
139                             int, NonRelocatable>::
140                                 transfer<std::allocator<char>>(nullptr, nullptr,
141                                                                nullptr)),
142                    std::false_type>::value));
143 }
144 
145 // gcc becomes unhappy if this is inside the method, so pull it out here.
146 struct balast {};
147 
TEST(FlatHashMap,IteratesMsan)148 TEST(FlatHashMap, IteratesMsan) {
149   // Because SwissTable randomizes on pointer addresses, we keep old tables
150   // around to ensure we don't reuse old memory.
151   std::vector<absl::flat_hash_map<int, balast>> garbage;
152   for (int i = 0; i < 100; ++i) {
153     absl::flat_hash_map<int, balast> t;
154     for (int j = 0; j < 100; ++j) {
155       t[j];
156       for (const auto& p : t) EXPECT_THAT(p, Pair(_, _));
157     }
158     garbage.push_back(std::move(t));
159   }
160 }
161 
162 // Demonstration of the "Lazy Key" pattern.  This uses heterogeneous insert to
163 // avoid creating expensive key elements when the item is already present in the
164 // map.
165 struct LazyInt {
LazyIntabsl::container_internal::__anone1ee0a730111::LazyInt166   explicit LazyInt(size_t value, int* tracker)
167       : value(value), tracker(tracker) {}
168 
operator size_tabsl::container_internal::__anone1ee0a730111::LazyInt169   explicit operator size_t() const {
170     ++*tracker;
171     return value;
172   }
173 
174   size_t value;
175   int* tracker;
176 };
177 
178 struct Hash {
179   using is_transparent = void;
180   int* tracker;
operator ()absl::container_internal::__anone1ee0a730111::Hash181   size_t operator()(size_t obj) const {
182     ++*tracker;
183     return obj;
184   }
operator ()absl::container_internal::__anone1ee0a730111::Hash185   size_t operator()(const LazyInt& obj) const {
186     ++*tracker;
187     return obj.value;
188   }
189 };
190 
191 struct Eq {
192   using is_transparent = void;
operator ()absl::container_internal::__anone1ee0a730111::Eq193   bool operator()(size_t lhs, size_t rhs) const { return lhs == rhs; }
operator ()absl::container_internal::__anone1ee0a730111::Eq194   bool operator()(size_t lhs, const LazyInt& rhs) const {
195     return lhs == rhs.value;
196   }
197 };
198 
TEST(FlatHashMap,LazyKeyPattern)199 TEST(FlatHashMap, LazyKeyPattern) {
200   // hashes are only guaranteed in opt mode, we use assertions to track internal
201   // state that can cause extra calls to hash.
202   int conversions = 0;
203   int hashes = 0;
204   flat_hash_map<size_t, size_t, Hash, Eq> m(0, Hash{&hashes});
205   m.reserve(3);
206 
207   m[LazyInt(1, &conversions)] = 1;
208   EXPECT_THAT(m, UnorderedElementsAre(Pair(1, 1)));
209   EXPECT_EQ(conversions, 1);
210 #ifdef NDEBUG
211   EXPECT_EQ(hashes, 1);
212 #endif
213 
214   m[LazyInt(1, &conversions)] = 2;
215   EXPECT_THAT(m, UnorderedElementsAre(Pair(1, 2)));
216   EXPECT_EQ(conversions, 1);
217 #ifdef NDEBUG
218   EXPECT_EQ(hashes, 2);
219 #endif
220 
221   m.try_emplace(LazyInt(2, &conversions), 3);
222   EXPECT_THAT(m, UnorderedElementsAre(Pair(1, 2), Pair(2, 3)));
223   EXPECT_EQ(conversions, 2);
224 #ifdef NDEBUG
225   EXPECT_EQ(hashes, 3);
226 #endif
227 
228   m.try_emplace(LazyInt(2, &conversions), 4);
229   EXPECT_THAT(m, UnorderedElementsAre(Pair(1, 2), Pair(2, 3)));
230   EXPECT_EQ(conversions, 2);
231 #ifdef NDEBUG
232   EXPECT_EQ(hashes, 4);
233 #endif
234 }
235 
TEST(FlatHashMap,BitfieldArgument)236 TEST(FlatHashMap, BitfieldArgument) {
237   union {
238     int n : 1;
239   };
240   n = 0;
241   flat_hash_map<int, int> m;
242   m.erase(n);
243   m.count(n);
244   m.prefetch(n);
245   m.find(n);
246   m.contains(n);
247   m.equal_range(n);
248   m.insert_or_assign(n, n);
249   m.insert_or_assign(m.end(), n, n);
250   m.try_emplace(n);
251   m.try_emplace(m.end(), n);
252   m.at(n);
253   m[n];
254 }
255 
TEST(FlatHashMap,MergeExtractInsert)256 TEST(FlatHashMap, MergeExtractInsert) {
257   // We can't test mutable keys, or non-copyable keys with flat_hash_map.
258   // Test that the nodes have the proper API.
259   absl::flat_hash_map<int, int> m = {{1, 7}, {2, 9}};
260   auto node = m.extract(1);
261   EXPECT_TRUE(node);
262   EXPECT_EQ(node.key(), 1);
263   EXPECT_EQ(node.mapped(), 7);
264   EXPECT_THAT(m, UnorderedElementsAre(Pair(2, 9)));
265 
266   node.mapped() = 17;
267   m.insert(std::move(node));
268   EXPECT_THAT(m, UnorderedElementsAre(Pair(1, 17), Pair(2, 9)));
269 }
270 
FirstIsEven(std::pair<const int,int> p)271 bool FirstIsEven(std::pair<const int, int> p) { return p.first % 2 == 0; }
272 
TEST(FlatHashMap,EraseIf)273 TEST(FlatHashMap, EraseIf) {
274   // Erase all elements.
275   {
276     flat_hash_map<int, int> s = {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}};
277     EXPECT_EQ(erase_if(s, [](std::pair<const int, int>) { return true; }), 5);
278     EXPECT_THAT(s, IsEmpty());
279   }
280   // Erase no elements.
281   {
282     flat_hash_map<int, int> s = {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}};
283     EXPECT_EQ(erase_if(s, [](std::pair<const int, int>) { return false; }), 0);
284     EXPECT_THAT(s, UnorderedElementsAre(Pair(1, 1), Pair(2, 2), Pair(3, 3),
285                                         Pair(4, 4), Pair(5, 5)));
286   }
287   // Erase specific elements.
288   {
289     flat_hash_map<int, int> s = {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}};
290     EXPECT_EQ(erase_if(s,
291                        [](std::pair<const int, int> kvp) {
292                          return kvp.first % 2 == 1;
293                        }),
294               3);
295     EXPECT_THAT(s, UnorderedElementsAre(Pair(2, 2), Pair(4, 4)));
296   }
297   // Predicate is function reference.
298   {
299     flat_hash_map<int, int> s = {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}};
300     EXPECT_EQ(erase_if(s, FirstIsEven), 2);
301     EXPECT_THAT(s, UnorderedElementsAre(Pair(1, 1), Pair(3, 3), Pair(5, 5)));
302   }
303   // Predicate is function pointer.
304   {
305     flat_hash_map<int, int> s = {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}};
306     EXPECT_EQ(erase_if(s, &FirstIsEven), 2);
307     EXPECT_THAT(s, UnorderedElementsAre(Pair(1, 1), Pair(3, 3), Pair(5, 5)));
308   }
309 }
310 
TEST(FlatHashMap,CForEach)311 TEST(FlatHashMap, CForEach) {
312   flat_hash_map<int, int> m;
313   std::vector<std::pair<int, int>> expected;
314   for (int i = 0; i < 100; ++i) {
315     {
316       SCOPED_TRACE("mutable object iteration");
317       std::vector<std::pair<int, int>> v;
318       absl::container_internal::c_for_each_fast(
319           m, [&v](std::pair<const int, int>& p) { v.push_back(p); });
320       EXPECT_THAT(v, UnorderedElementsAreArray(expected));
321     }
322     {
323       SCOPED_TRACE("const object iteration");
324       std::vector<std::pair<int, int>> v;
325       const flat_hash_map<int, int>& cm = m;
326       absl::container_internal::c_for_each_fast(
327           cm, [&v](const std::pair<const int, int>& p) { v.push_back(p); });
328       EXPECT_THAT(v, UnorderedElementsAreArray(expected));
329     }
330     {
331       SCOPED_TRACE("const object iteration");
332       std::vector<std::pair<int, int>> v;
333       absl::container_internal::c_for_each_fast(
334           flat_hash_map<int, int>(m),
335           [&v](std::pair<const int, int>& p) { v.push_back(p); });
336       EXPECT_THAT(v, UnorderedElementsAreArray(expected));
337     }
338     m[i] = i;
339     expected.emplace_back(i, i);
340   }
341 }
342 
TEST(FlatHashMap,CForEachMutate)343 TEST(FlatHashMap, CForEachMutate) {
344   flat_hash_map<int, int> s;
345   std::vector<std::pair<int, int>> expected;
346   for (int i = 0; i < 100; ++i) {
347     std::vector<std::pair<int, int>> v;
348     absl::container_internal::c_for_each_fast(
349         s, [&v](std::pair<const int, int>& p) {
350           v.push_back(p);
351           p.second++;
352         });
353     EXPECT_THAT(v, UnorderedElementsAreArray(expected));
354     for (auto& p : expected) {
355       p.second++;
356     }
357     EXPECT_THAT(s, UnorderedElementsAreArray(expected));
358     s[i] = i;
359     expected.emplace_back(i, i);
360   }
361 }
362 
363 // This test requires std::launder for mutable key access in node handles.
364 #if defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606
TEST(FlatHashMap,NodeHandleMutableKeyAccess)365 TEST(FlatHashMap, NodeHandleMutableKeyAccess) {
366   flat_hash_map<std::string, std::string> map;
367 
368   map["key1"] = "mapped";
369 
370   auto nh = map.extract(map.begin());
371   nh.key().resize(3);
372   map.insert(std::move(nh));
373 
374   EXPECT_THAT(map, testing::ElementsAre(Pair("key", "mapped")));
375 }
376 #endif
377 
TEST(FlatHashMap,Reserve)378 TEST(FlatHashMap, Reserve) {
379   // Verify that if we reserve(size() + n) then we can perform n insertions
380   // without a rehash, i.e., without invalidating any references.
381   for (size_t trial = 0; trial < 20; ++trial) {
382     for (size_t initial = 3; initial < 100; ++initial) {
383       // Fill in `initial` entries, then erase 2 of them, then reserve space for
384       // two inserts and check for reference stability while doing the inserts.
385       flat_hash_map<size_t, size_t> map;
386       for (size_t i = 0; i < initial; ++i) {
387         map[i] = i;
388       }
389       map.erase(0);
390       map.erase(1);
391       map.reserve(map.size() + 2);
392       size_t& a2 = map[2];
393       // In the event of a failure, asan will complain in one of these two
394       // assignments.
395       map[initial] = a2;
396       map[initial + 1] = a2;
397       // Fail even when not under asan:
398       size_t& a2new = map[2];
399       EXPECT_EQ(&a2, &a2new);
400     }
401   }
402 }
403 
TEST(FlatHashMap,RecursiveTypeCompiles)404 TEST(FlatHashMap, RecursiveTypeCompiles) {
405   struct RecursiveType {
406     flat_hash_map<int, RecursiveType> m;
407   };
408   RecursiveType t;
409   t.m[0] = RecursiveType{};
410 }
411 
TEST(FlatHashMap,FlatHashMapPolicyDestroyReturnsTrue)412 TEST(FlatHashMap, FlatHashMapPolicyDestroyReturnsTrue) {
413   EXPECT_TRUE(
414       (decltype(FlatHashMapPolicy<int, char>::destroy<std::allocator<char>>(
415           nullptr, nullptr))()));
416   EXPECT_FALSE(
417       (decltype(FlatHashMapPolicy<int, char>::destroy<CountingAllocator<char>>(
418           nullptr, nullptr))()));
419   EXPECT_FALSE((decltype(FlatHashMapPolicy<int, std::unique_ptr<int>>::destroy<
420                          std::allocator<char>>(nullptr, nullptr))()));
421 }
422 
423 struct InconsistentHashEqType {
InconsistentHashEqTypeabsl::container_internal::__anone1ee0a730111::InconsistentHashEqType424   InconsistentHashEqType(int v1, int v2) : v1(v1), v2(v2) {}
425   template <typename H>
AbslHashValue(H h,InconsistentHashEqType t)426   friend H AbslHashValue(H h, InconsistentHashEqType t) {
427     return H::combine(std::move(h), t.v1);
428   }
operator ==absl::container_internal::__anone1ee0a730111::InconsistentHashEqType429   bool operator==(InconsistentHashEqType t) const { return v2 == t.v2; }
430   int v1, v2;
431 };
432 
TEST(Iterator,InconsistentHashEqFunctorsValidation)433 TEST(Iterator, InconsistentHashEqFunctorsValidation) {
434   if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled.";
435 
436   absl::flat_hash_map<InconsistentHashEqType, int> m;
437   for (int i = 0; i < 10; ++i) m[{i, i}] = 1;
438   // We need to insert multiple times to guarantee that we get the assertion
439   // because it's possible for the hash to collide with the inserted element
440   // that has v2==0. In those cases, the new element won't be inserted.
441   auto insert_conflicting_elems = [&] {
442     for (int i = 100; i < 20000; ++i) {
443       EXPECT_EQ((m[{i, 0}]), 1);
444     }
445   };
446 
447   const char* crash_message = "hash/eq functors are inconsistent.";
448 #if defined(__arm__) || defined(__aarch64__)
449   // On ARM, the crash message is garbled so don't expect a specific message.
450   crash_message = "";
451 #endif
452   EXPECT_DEATH_IF_SUPPORTED(insert_conflicting_elems(), crash_message);
453 }
454 
455 }  // namespace
456 }  // namespace container_internal
457 ABSL_NAMESPACE_END
458 }  // namespace absl
459