flat_hash_map_test.cc (14854B)
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 { 52 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 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 117 TEST(FlatHashMap, Relocatability) { 118 static_assert(absl::is_trivially_relocatable<int>::value); 119 #if ABSL_INTERNAL_CPLUSPLUS_LANG <= 202002L 120 // std::pair is not trivially copyable in C++23 in some standard 121 // library versions. 122 // See https://github.com/llvm/llvm-project/pull/95444 for instance. 123 // container_memory.h contains a workaround so what really matters 124 // is the transfer test below. 125 static_assert( 126 absl::is_trivially_relocatable<std::pair<const int, int>>::value); 127 #endif 128 static_assert( 129 std::is_same<decltype(absl::container_internal::FlatHashMapPolicy< 130 int, int>::transfer<std::allocator<char>>(nullptr, 131 nullptr, 132 nullptr)), 133 std::true_type>::value); 134 135 struct NonRelocatable { 136 NonRelocatable() = default; 137 NonRelocatable(NonRelocatable&&) {} 138 NonRelocatable& operator=(NonRelocatable&&) { return *this; } 139 void* self = nullptr; 140 }; 141 142 EXPECT_FALSE(absl::is_trivially_relocatable<NonRelocatable>::value); 143 EXPECT_TRUE( 144 (std::is_same<decltype(absl::container_internal::FlatHashMapPolicy< 145 int, NonRelocatable>:: 146 transfer<std::allocator<char>>(nullptr, nullptr, 147 nullptr)), 148 std::false_type>::value)); 149 } 150 151 // gcc becomes unhappy if this is inside the method, so pull it out here. 152 struct balast {}; 153 154 TEST(FlatHashMap, IteratesMsan) { 155 // Because SwissTable randomizes on pointer addresses, we keep old tables 156 // around to ensure we don't reuse old memory. 157 std::vector<absl::flat_hash_map<int, balast>> garbage; 158 for (int i = 0; i < 100; ++i) { 159 absl::flat_hash_map<int, balast> t; 160 for (int j = 0; j < 100; ++j) { 161 t[j]; 162 for (const auto& p : t) EXPECT_THAT(p, Pair(_, _)); 163 } 164 garbage.push_back(std::move(t)); 165 } 166 } 167 168 // Demonstration of the "Lazy Key" pattern. This uses heterogeneous insert to 169 // avoid creating expensive key elements when the item is already present in the 170 // map. 171 struct LazyInt { 172 explicit LazyInt(size_t value, int* tracker) 173 : value(value), tracker(tracker) {} 174 175 explicit operator size_t() const { 176 ++*tracker; 177 return value; 178 } 179 180 size_t value; 181 int* tracker; 182 }; 183 184 struct Hash { 185 using is_transparent = void; 186 int* tracker; 187 size_t operator()(size_t obj) const { 188 ++*tracker; 189 return obj; 190 } 191 size_t operator()(const LazyInt& obj) const { 192 ++*tracker; 193 return obj.value; 194 } 195 }; 196 197 struct Eq { 198 using is_transparent = void; 199 bool operator()(size_t lhs, size_t rhs) const { return lhs == rhs; } 200 bool operator()(size_t lhs, const LazyInt& rhs) const { 201 return lhs == rhs.value; 202 } 203 }; 204 205 TEST(FlatHashMap, LazyKeyPattern) { 206 // hashes are only guaranteed in opt mode, we use assertions to track internal 207 // state that can cause extra calls to hash. 208 int conversions = 0; 209 int hashes = 0; 210 flat_hash_map<size_t, size_t, Hash, Eq> m(0, Hash{&hashes}); 211 m.reserve(3); 212 213 m[LazyInt(1, &conversions)] = 1; 214 EXPECT_THAT(m, UnorderedElementsAre(Pair(1, 1))); 215 EXPECT_EQ(conversions, 1); 216 #ifdef NDEBUG 217 EXPECT_EQ(hashes, 1); 218 #endif 219 220 m[LazyInt(1, &conversions)] = 2; 221 EXPECT_THAT(m, UnorderedElementsAre(Pair(1, 2))); 222 EXPECT_EQ(conversions, 1); 223 #ifdef NDEBUG 224 EXPECT_EQ(hashes, 2); 225 #endif 226 227 m.try_emplace(LazyInt(2, &conversions), 3); 228 EXPECT_THAT(m, UnorderedElementsAre(Pair(1, 2), Pair(2, 3))); 229 EXPECT_EQ(conversions, 2); 230 #ifdef NDEBUG 231 EXPECT_EQ(hashes, 3); 232 #endif 233 234 m.try_emplace(LazyInt(2, &conversions), 4); 235 EXPECT_THAT(m, UnorderedElementsAre(Pair(1, 2), Pair(2, 3))); 236 EXPECT_EQ(conversions, 2); 237 #ifdef NDEBUG 238 EXPECT_EQ(hashes, 4); 239 #endif 240 } 241 242 TEST(FlatHashMap, BitfieldArgument) { 243 union { 244 int n : 1; 245 }; 246 n = 0; 247 flat_hash_map<int, int> m; 248 m.erase(n); 249 m.count(n); 250 m.prefetch(n); 251 m.find(n); 252 m.contains(n); 253 m.equal_range(n); 254 m.insert_or_assign(n, n); 255 m.insert_or_assign(m.end(), n, n); 256 m.try_emplace(n); 257 m.try_emplace(m.end(), n); 258 m.at(n); 259 m[n]; 260 } 261 262 TEST(FlatHashMap, MergeExtractInsert) { 263 // We can't test mutable keys, or non-copyable keys with flat_hash_map. 264 // Test that the nodes have the proper API. 265 absl::flat_hash_map<int, int> m = {{1, 7}, {2, 9}}; 266 auto node = m.extract(1); 267 EXPECT_TRUE(node); 268 EXPECT_EQ(node.key(), 1); 269 EXPECT_EQ(node.mapped(), 7); 270 EXPECT_THAT(m, UnorderedElementsAre(Pair(2, 9))); 271 272 node.mapped() = 17; 273 m.insert(std::move(node)); 274 EXPECT_THAT(m, UnorderedElementsAre(Pair(1, 17), Pair(2, 9))); 275 } 276 277 bool FirstIsEven(std::pair<const int, int> p) { return p.first % 2 == 0; } 278 279 TEST(FlatHashMap, EraseIf) { 280 // Erase all 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 true; }), 5); 284 EXPECT_THAT(s, IsEmpty()); 285 } 286 // Erase no elements. 287 { 288 flat_hash_map<int, int> s = {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}}; 289 EXPECT_EQ(erase_if(s, [](std::pair<const int, int>) { return false; }), 0); 290 EXPECT_THAT(s, UnorderedElementsAre(Pair(1, 1), Pair(2, 2), Pair(3, 3), 291 Pair(4, 4), Pair(5, 5))); 292 } 293 // Erase specific elements. 294 { 295 flat_hash_map<int, int> s = {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}}; 296 EXPECT_EQ(erase_if(s, 297 [](std::pair<const int, int> kvp) { 298 return kvp.first % 2 == 1; 299 }), 300 3); 301 EXPECT_THAT(s, UnorderedElementsAre(Pair(2, 2), Pair(4, 4))); 302 } 303 // Predicate is function reference. 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 // Predicate is function pointer. 310 { 311 flat_hash_map<int, int> s = {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}}; 312 EXPECT_EQ(erase_if(s, &FirstIsEven), 2); 313 EXPECT_THAT(s, UnorderedElementsAre(Pair(1, 1), Pair(3, 3), Pair(5, 5))); 314 } 315 } 316 317 TEST(FlatHashMap, CForEach) { 318 flat_hash_map<int, int> m; 319 std::vector<std::pair<int, int>> expected; 320 for (int i = 0; i < 100; ++i) { 321 { 322 SCOPED_TRACE("mutable object iteration"); 323 std::vector<std::pair<int, int>> v; 324 absl::container_internal::c_for_each_fast( 325 m, [&v](std::pair<const int, int>& p) { v.push_back(p); }); 326 EXPECT_THAT(v, UnorderedElementsAreArray(expected)); 327 } 328 { 329 SCOPED_TRACE("const object iteration"); 330 std::vector<std::pair<int, int>> v; 331 const flat_hash_map<int, int>& cm = m; 332 absl::container_internal::c_for_each_fast( 333 cm, [&v](const std::pair<const int, int>& p) { v.push_back(p); }); 334 EXPECT_THAT(v, UnorderedElementsAreArray(expected)); 335 } 336 { 337 SCOPED_TRACE("const object iteration"); 338 std::vector<std::pair<int, int>> v; 339 absl::container_internal::c_for_each_fast( 340 flat_hash_map<int, int>(m), 341 [&v](std::pair<const int, int>& p) { v.push_back(p); }); 342 EXPECT_THAT(v, UnorderedElementsAreArray(expected)); 343 } 344 m[i] = i; 345 expected.emplace_back(i, i); 346 } 347 } 348 349 TEST(FlatHashMap, CForEachMutate) { 350 flat_hash_map<int, int> s; 351 std::vector<std::pair<int, int>> expected; 352 for (int i = 0; i < 100; ++i) { 353 std::vector<std::pair<int, int>> v; 354 absl::container_internal::c_for_each_fast( 355 s, [&v](std::pair<const int, int>& p) { 356 v.push_back(p); 357 p.second++; 358 }); 359 EXPECT_THAT(v, UnorderedElementsAreArray(expected)); 360 for (auto& p : expected) { 361 p.second++; 362 } 363 EXPECT_THAT(s, UnorderedElementsAreArray(expected)); 364 s[i] = i; 365 expected.emplace_back(i, i); 366 } 367 } 368 369 TEST(FlatHashMap, NodeHandleMutableKeyAccess) { 370 flat_hash_map<std::string, std::string> map; 371 372 map["key1"] = "mapped"; 373 374 auto nh = map.extract(map.begin()); 375 nh.key().resize(3); 376 map.insert(std::move(nh)); 377 378 EXPECT_THAT(map, testing::ElementsAre(Pair("key", "mapped"))); 379 } 380 381 TEST(FlatHashMap, Reserve) { 382 // Verify that if we reserve(size() + n) then we can perform n insertions 383 // without a rehash, i.e., without invalidating any references. 384 for (size_t trial = 0; trial < 20; ++trial) { 385 for (size_t initial = 3; initial < 100; ++initial) { 386 // Fill in `initial` entries, then erase 2 of them, then reserve space for 387 // two inserts and check for reference stability while doing the inserts. 388 flat_hash_map<size_t, size_t> map; 389 for (size_t i = 0; i < initial; ++i) { 390 map[i] = i; 391 } 392 map.erase(0); 393 map.erase(1); 394 map.reserve(map.size() + 2); 395 size_t& a2 = map[2]; 396 // In the event of a failure, asan will complain in one of these two 397 // assignments. 398 map[initial] = a2; 399 map[initial + 1] = a2; 400 // Fail even when not under asan: 401 size_t& a2new = map[2]; 402 EXPECT_EQ(&a2, &a2new); 403 } 404 } 405 } 406 407 TEST(FlatHashMap, RecursiveTypeCompiles) { 408 struct RecursiveType { 409 flat_hash_map<int, RecursiveType> m; 410 }; 411 RecursiveType t; 412 t.m[0] = RecursiveType{}; 413 } 414 415 TEST(FlatHashMap, FlatHashMapPolicyDestroyReturnsTrue) { 416 EXPECT_TRUE( 417 (decltype(FlatHashMapPolicy<int, char>::destroy<std::allocator<char>>( 418 nullptr, nullptr))())); 419 EXPECT_FALSE( 420 (decltype(FlatHashMapPolicy<int, char>::destroy<CountingAllocator<char>>( 421 nullptr, nullptr))())); 422 EXPECT_FALSE((decltype(FlatHashMapPolicy<int, std::unique_ptr<int>>::destroy< 423 std::allocator<char>>(nullptr, nullptr))())); 424 } 425 426 struct InconsistentHashEqType { 427 InconsistentHashEqType(int v1, int v2) : v1(v1), v2(v2) {} 428 template <typename H> 429 friend H AbslHashValue(H h, InconsistentHashEqType t) { 430 return H::combine(std::move(h), t.v1); 431 } 432 bool operator==(InconsistentHashEqType t) const { return v2 == t.v2; } 433 int v1, v2; 434 }; 435 436 TEST(Iterator, InconsistentHashEqFunctorsValidation) { 437 if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled."; 438 439 absl::flat_hash_map<InconsistentHashEqType, int> m; 440 for (int i = 0; i < 10; ++i) m[{i, i}] = 1; 441 // We need to insert multiple times to guarantee that we get the assertion 442 // because it's possible for the hash to collide with the inserted element 443 // that has v2==0. In those cases, the new element won't be inserted. 444 auto insert_conflicting_elems = [&] { 445 for (int i = 100; i < 20000; ++i) { 446 EXPECT_EQ((m[{i, 0}]), 1); 447 } 448 }; 449 450 const char* crash_message = "hash/eq functors are inconsistent."; 451 #if defined(__arm__) || defined(__aarch64__) 452 // On ARM, the crash message is garbled so don't expect a specific message. 453 crash_message = ""; 454 #endif 455 EXPECT_DEATH_IF_SUPPORTED(insert_conflicting_elems(), crash_message); 456 } 457 458 } // namespace 459 } // namespace container_internal 460 ABSL_NAMESPACE_END 461 } // namespace absl