1 //===- llvm/unittest/ADT/StringMapMap.cpp - StringMap unit tests ----------===// 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 #include "llvm/ADT/StringMap.h" 10 #include "llvm/ADT/Twine.h" 11 #include "llvm/Support/DataTypes.h" 12 #include "gtest/gtest.h" 13 #include <limits> 14 #include <tuple> 15 using namespace llvm; 16 17 namespace { 18 19 // Test fixture 20 class StringMapTest : public testing::Test { 21 protected: 22 StringMap<uint32_t> testMap; 23 24 static const char testKey[]; 25 static const uint32_t testValue; 26 static const char* testKeyFirst; 27 static size_t testKeyLength; 28 static const std::string testKeyStr; 29 30 void assertEmptyMap() { 31 // Size tests 32 EXPECT_EQ(0u, testMap.size()); 33 EXPECT_TRUE(testMap.empty()); 34 35 // Iterator tests 36 EXPECT_TRUE(testMap.begin() == testMap.end()); 37 38 // Lookup tests 39 EXPECT_EQ(0u, testMap.count(testKey)); 40 EXPECT_EQ(0u, testMap.count(StringRef(testKeyFirst, testKeyLength))); 41 EXPECT_EQ(0u, testMap.count(testKeyStr)); 42 EXPECT_TRUE(testMap.find(testKey) == testMap.end()); 43 EXPECT_TRUE(testMap.find(StringRef(testKeyFirst, testKeyLength)) == 44 testMap.end()); 45 EXPECT_TRUE(testMap.find(testKeyStr) == testMap.end()); 46 } 47 48 void assertSingleItemMap() { 49 // Size tests 50 EXPECT_EQ(1u, testMap.size()); 51 EXPECT_FALSE(testMap.begin() == testMap.end()); 52 EXPECT_FALSE(testMap.empty()); 53 54 // Iterator tests 55 StringMap<uint32_t>::iterator it = testMap.begin(); 56 EXPECT_STREQ(testKey, it->first().data()); 57 EXPECT_EQ(testValue, it->second); 58 ++it; 59 EXPECT_TRUE(it == testMap.end()); 60 61 // Lookup tests 62 EXPECT_EQ(1u, testMap.count(testKey)); 63 EXPECT_EQ(1u, testMap.count(StringRef(testKeyFirst, testKeyLength))); 64 EXPECT_EQ(1u, testMap.count(testKeyStr)); 65 EXPECT_TRUE(testMap.find(testKey) == testMap.begin()); 66 EXPECT_TRUE(testMap.find(StringRef(testKeyFirst, testKeyLength)) == 67 testMap.begin()); 68 EXPECT_TRUE(testMap.find(testKeyStr) == testMap.begin()); 69 } 70 }; 71 72 const char StringMapTest::testKey[] = "key"; 73 const uint32_t StringMapTest::testValue = 1u; 74 const char* StringMapTest::testKeyFirst = testKey; 75 size_t StringMapTest::testKeyLength = sizeof(testKey) - 1; 76 const std::string StringMapTest::testKeyStr(testKey); 77 78 struct CountCopyAndMove { 79 CountCopyAndMove() = default; 80 CountCopyAndMove(const CountCopyAndMove &) { copy = 1; } 81 CountCopyAndMove(CountCopyAndMove &&) { move = 1; } 82 void operator=(const CountCopyAndMove &) { ++copy; } 83 void operator=(CountCopyAndMove &&) { ++move; } 84 int copy = 0; 85 int move = 0; 86 }; 87 88 // Empty map tests. 89 TEST_F(StringMapTest, EmptyMapTest) { 90 assertEmptyMap(); 91 } 92 93 // Constant map tests. 94 TEST_F(StringMapTest, ConstEmptyMapTest) { 95 const StringMap<uint32_t>& constTestMap = testMap; 96 97 // Size tests 98 EXPECT_EQ(0u, constTestMap.size()); 99 EXPECT_TRUE(constTestMap.empty()); 100 101 // Iterator tests 102 EXPECT_TRUE(constTestMap.begin() == constTestMap.end()); 103 104 // Lookup tests 105 EXPECT_EQ(0u, constTestMap.count(testKey)); 106 EXPECT_EQ(0u, constTestMap.count(StringRef(testKeyFirst, testKeyLength))); 107 EXPECT_EQ(0u, constTestMap.count(testKeyStr)); 108 EXPECT_TRUE(constTestMap.find(testKey) == constTestMap.end()); 109 EXPECT_TRUE(constTestMap.find(StringRef(testKeyFirst, testKeyLength)) == 110 constTestMap.end()); 111 EXPECT_TRUE(constTestMap.find(testKeyStr) == constTestMap.end()); 112 } 113 114 // initializer_list ctor test; also implicitly tests initializer_list and 115 // iterator overloads of insert(). 116 TEST_F(StringMapTest, InitializerListCtor) { 117 testMap = StringMap<uint32_t>({{"key", 1}}); 118 assertSingleItemMap(); 119 } 120 121 // A map with a single entry. 122 TEST_F(StringMapTest, SingleEntryMapTest) { 123 testMap[testKey] = testValue; 124 assertSingleItemMap(); 125 } 126 127 // Test clear() method. 128 TEST_F(StringMapTest, ClearTest) { 129 testMap[testKey] = testValue; 130 testMap.clear(); 131 assertEmptyMap(); 132 } 133 134 // Test erase(iterator) method. 135 TEST_F(StringMapTest, EraseIteratorTest) { 136 testMap[testKey] = testValue; 137 testMap.erase(testMap.begin()); 138 assertEmptyMap(); 139 } 140 141 // Test erase(value) method. 142 TEST_F(StringMapTest, EraseValueTest) { 143 testMap[testKey] = testValue; 144 testMap.erase(testKey); 145 assertEmptyMap(); 146 } 147 148 // Test inserting two values and erasing one. 149 TEST_F(StringMapTest, InsertAndEraseTest) { 150 testMap[testKey] = testValue; 151 testMap["otherKey"] = 2; 152 testMap.erase("otherKey"); 153 assertSingleItemMap(); 154 } 155 156 TEST_F(StringMapTest, SmallFullMapTest) { 157 // StringMap has a tricky corner case when the map is small (<8 buckets) and 158 // it fills up through a balanced pattern of inserts and erases. This can 159 // lead to inf-loops in some cases (PR13148) so we test it explicitly here. 160 llvm::StringMap<int> Map(2); 161 162 Map["eins"] = 1; 163 Map["zwei"] = 2; 164 Map["drei"] = 3; 165 Map.erase("drei"); 166 Map.erase("eins"); 167 Map["veir"] = 4; 168 Map["funf"] = 5; 169 170 EXPECT_EQ(3u, Map.size()); 171 EXPECT_EQ(0, Map.lookup("eins")); 172 EXPECT_EQ(2, Map.lookup("zwei")); 173 EXPECT_EQ(0, Map.lookup("drei")); 174 EXPECT_EQ(4, Map.lookup("veir")); 175 EXPECT_EQ(5, Map.lookup("funf")); 176 } 177 178 TEST_F(StringMapTest, CopyCtorTest) { 179 llvm::StringMap<int> Map; 180 181 Map["eins"] = 1; 182 Map["zwei"] = 2; 183 Map["drei"] = 3; 184 Map.erase("drei"); 185 Map.erase("eins"); 186 Map["veir"] = 4; 187 Map["funf"] = 5; 188 189 EXPECT_EQ(3u, Map.size()); 190 EXPECT_EQ(0, Map.lookup("eins")); 191 EXPECT_EQ(2, Map.lookup("zwei")); 192 EXPECT_EQ(0, Map.lookup("drei")); 193 EXPECT_EQ(4, Map.lookup("veir")); 194 EXPECT_EQ(5, Map.lookup("funf")); 195 196 llvm::StringMap<int> Map2(Map); 197 EXPECT_EQ(3u, Map2.size()); 198 EXPECT_EQ(0, Map2.lookup("eins")); 199 EXPECT_EQ(2, Map2.lookup("zwei")); 200 EXPECT_EQ(0, Map2.lookup("drei")); 201 EXPECT_EQ(4, Map2.lookup("veir")); 202 EXPECT_EQ(5, Map2.lookup("funf")); 203 } 204 205 // A more complex iteration test. 206 TEST_F(StringMapTest, IterationTest) { 207 bool visited[100]; 208 209 // Insert 100 numbers into the map 210 for (int i = 0; i < 100; ++i) { 211 std::stringstream ss; 212 ss << "key_" << i; 213 testMap[ss.str()] = i; 214 visited[i] = false; 215 } 216 217 // Iterate over all numbers and mark each one found. 218 for (StringMap<uint32_t>::iterator it = testMap.begin(); 219 it != testMap.end(); ++it) { 220 std::stringstream ss; 221 ss << "key_" << it->second; 222 ASSERT_STREQ(ss.str().c_str(), it->first().data()); 223 visited[it->second] = true; 224 } 225 226 // Ensure every number was visited. 227 for (int i = 0; i < 100; ++i) { 228 ASSERT_TRUE(visited[i]) << "Entry #" << i << " was never visited"; 229 } 230 } 231 232 // Test StringMapEntry::Create() method. 233 TEST_F(StringMapTest, StringMapEntryTest) { 234 MallocAllocator Allocator; 235 StringMap<uint32_t>::value_type *entry = 236 StringMap<uint32_t>::value_type::Create( 237 StringRef(testKeyFirst, testKeyLength), Allocator, 1u); 238 EXPECT_STREQ(testKey, entry->first().data()); 239 EXPECT_EQ(1u, entry->second); 240 entry->Destroy(Allocator); 241 } 242 243 // Test insert() method. 244 TEST_F(StringMapTest, InsertTest) { 245 SCOPED_TRACE("InsertTest"); 246 testMap.insert( 247 StringMap<uint32_t>::value_type::Create( 248 StringRef(testKeyFirst, testKeyLength), 249 testMap.getAllocator(), 1u)); 250 assertSingleItemMap(); 251 } 252 253 // Test insert(pair<K, V>) method 254 TEST_F(StringMapTest, InsertPairTest) { 255 bool Inserted; 256 StringMap<uint32_t>::iterator NewIt; 257 std::tie(NewIt, Inserted) = 258 testMap.insert(std::make_pair(testKeyFirst, testValue)); 259 EXPECT_EQ(1u, testMap.size()); 260 EXPECT_EQ(testValue, testMap[testKeyFirst]); 261 EXPECT_EQ(testKeyFirst, NewIt->first()); 262 EXPECT_EQ(testValue, NewIt->second); 263 EXPECT_TRUE(Inserted); 264 265 StringMap<uint32_t>::iterator ExistingIt; 266 std::tie(ExistingIt, Inserted) = 267 testMap.insert(std::make_pair(testKeyFirst, testValue + 1)); 268 EXPECT_EQ(1u, testMap.size()); 269 EXPECT_EQ(testValue, testMap[testKeyFirst]); 270 EXPECT_FALSE(Inserted); 271 EXPECT_EQ(NewIt, ExistingIt); 272 } 273 274 // Test insert(pair<K, V>) method when rehashing occurs 275 TEST_F(StringMapTest, InsertRehashingPairTest) { 276 // Check that the correct iterator is returned when the inserted element is 277 // moved to a different bucket during internal rehashing. This depends on 278 // the particular key, and the implementation of StringMap and HashString. 279 // Changes to those might result in this test not actually checking that. 280 StringMap<uint32_t> t(0); 281 EXPECT_EQ(0u, t.getNumBuckets()); 282 283 StringMap<uint32_t>::iterator It = 284 t.insert(std::make_pair("abcdef", 42)).first; 285 EXPECT_EQ(16u, t.getNumBuckets()); 286 EXPECT_EQ("abcdef", It->first()); 287 EXPECT_EQ(42u, It->second); 288 } 289 290 TEST_F(StringMapTest, InsertOrAssignTest) { 291 struct A : CountCopyAndMove { 292 A(int v) : v(v) {} 293 int v; 294 }; 295 StringMap<A> t(0); 296 297 auto try1 = t.insert_or_assign("A", A(1)); 298 EXPECT_TRUE(try1.second); 299 EXPECT_EQ(1, try1.first->second.v); 300 EXPECT_EQ(1, try1.first->second.move); 301 302 auto try2 = t.insert_or_assign("A", A(2)); 303 EXPECT_FALSE(try2.second); 304 EXPECT_EQ(2, try2.first->second.v); 305 EXPECT_EQ(2, try1.first->second.move); 306 307 EXPECT_EQ(try1.first, try2.first); 308 EXPECT_EQ(0, try1.first->second.copy); 309 } 310 311 TEST_F(StringMapTest, IterMapKeys) { 312 StringMap<int> Map; 313 Map["A"] = 1; 314 Map["B"] = 2; 315 Map["C"] = 3; 316 Map["D"] = 3; 317 318 auto Keys = to_vector<4>(Map.keys()); 319 llvm::sort(Keys); 320 321 SmallVector<StringRef, 4> Expected = {"A", "B", "C", "D"}; 322 EXPECT_EQ(Expected, Keys); 323 } 324 325 // Create a non-default constructable value 326 struct StringMapTestStruct { 327 StringMapTestStruct(int i) : i(i) {} 328 StringMapTestStruct() = delete; 329 int i; 330 }; 331 332 TEST_F(StringMapTest, NonDefaultConstructable) { 333 StringMap<StringMapTestStruct> t; 334 t.insert(std::make_pair("Test", StringMapTestStruct(123))); 335 StringMap<StringMapTestStruct>::iterator iter = t.find("Test"); 336 ASSERT_NE(iter, t.end()); 337 ASSERT_EQ(iter->second.i, 123); 338 } 339 340 struct Immovable { 341 Immovable() {} 342 Immovable(Immovable&&) = delete; // will disable the other special members 343 }; 344 345 struct MoveOnly { 346 int i; 347 MoveOnly(int i) : i(i) {} 348 MoveOnly(const Immovable&) : i(0) {} 349 MoveOnly(MoveOnly &&RHS) : i(RHS.i) {} 350 MoveOnly &operator=(MoveOnly &&RHS) { 351 i = RHS.i; 352 return *this; 353 } 354 355 private: 356 MoveOnly(const MoveOnly &) = delete; 357 MoveOnly &operator=(const MoveOnly &) = delete; 358 }; 359 360 TEST_F(StringMapTest, MoveOnly) { 361 StringMap<MoveOnly> t; 362 t.insert(std::make_pair("Test", MoveOnly(42))); 363 StringRef Key = "Test"; 364 StringMapEntry<MoveOnly>::Create(Key, t.getAllocator(), MoveOnly(42)) 365 ->Destroy(t.getAllocator()); 366 } 367 368 TEST_F(StringMapTest, CtorArg) { 369 StringRef Key = "Test"; 370 MallocAllocator Allocator; 371 StringMapEntry<MoveOnly>::Create(Key, Allocator, Immovable()) 372 ->Destroy(Allocator); 373 } 374 375 TEST_F(StringMapTest, MoveConstruct) { 376 StringMap<int> A; 377 A["x"] = 42; 378 StringMap<int> B = std::move(A); 379 ASSERT_EQ(A.size(), 0u); 380 ASSERT_EQ(B.size(), 1u); 381 ASSERT_EQ(B["x"], 42); 382 ASSERT_EQ(B.count("y"), 0u); 383 } 384 385 TEST_F(StringMapTest, MoveAssignment) { 386 StringMap<int> A; 387 A["x"] = 42; 388 StringMap<int> B; 389 B["y"] = 117; 390 A = std::move(B); 391 ASSERT_EQ(A.size(), 1u); 392 ASSERT_EQ(B.size(), 0u); 393 ASSERT_EQ(A["y"], 117); 394 ASSERT_EQ(B.count("x"), 0u); 395 } 396 397 TEST_F(StringMapTest, EqualEmpty) { 398 StringMap<int> A; 399 StringMap<int> B; 400 ASSERT_TRUE(A == B); 401 ASSERT_FALSE(A != B); 402 ASSERT_TRUE(A == A); // self check 403 } 404 405 TEST_F(StringMapTest, EqualWithValues) { 406 StringMap<int> A; 407 A["A"] = 1; 408 A["B"] = 2; 409 A["C"] = 3; 410 A["D"] = 3; 411 412 StringMap<int> B; 413 B["A"] = 1; 414 B["B"] = 2; 415 B["C"] = 3; 416 B["D"] = 3; 417 418 ASSERT_TRUE(A == B); 419 ASSERT_TRUE(B == A); 420 ASSERT_FALSE(A != B); 421 ASSERT_FALSE(B != A); 422 ASSERT_TRUE(A == A); // self check 423 } 424 425 TEST_F(StringMapTest, NotEqualMissingKeys) { 426 StringMap<int> A; 427 A["A"] = 1; 428 A["B"] = 2; 429 430 StringMap<int> B; 431 B["A"] = 1; 432 B["B"] = 2; 433 B["C"] = 3; 434 B["D"] = 3; 435 436 ASSERT_FALSE(A == B); 437 ASSERT_FALSE(B == A); 438 ASSERT_TRUE(A != B); 439 ASSERT_TRUE(B != A); 440 } 441 442 TEST_F(StringMapTest, NotEqualWithDifferentValues) { 443 StringMap<int> A; 444 A["A"] = 1; 445 A["B"] = 2; 446 A["C"] = 100; 447 A["D"] = 3; 448 449 StringMap<int> B; 450 B["A"] = 1; 451 B["B"] = 2; 452 B["C"] = 3; 453 B["D"] = 3; 454 455 ASSERT_FALSE(A == B); 456 ASSERT_FALSE(B == A); 457 ASSERT_TRUE(A != B); 458 ASSERT_TRUE(B != A); 459 } 460 461 struct Countable { 462 int &InstanceCount; 463 int Number; 464 Countable(int Number, int &InstanceCount) 465 : InstanceCount(InstanceCount), Number(Number) { 466 ++InstanceCount; 467 } 468 Countable(Countable &&C) : InstanceCount(C.InstanceCount), Number(C.Number) { 469 ++InstanceCount; 470 C.Number = -1; 471 } 472 Countable(const Countable &C) 473 : InstanceCount(C.InstanceCount), Number(C.Number) { 474 ++InstanceCount; 475 } 476 Countable &operator=(Countable C) { 477 Number = C.Number; 478 return *this; 479 } 480 ~Countable() { --InstanceCount; } 481 }; 482 483 TEST_F(StringMapTest, MoveDtor) { 484 int InstanceCount = 0; 485 StringMap<Countable> A; 486 A.insert(std::make_pair("x", Countable(42, InstanceCount))); 487 ASSERT_EQ(InstanceCount, 1); 488 auto I = A.find("x"); 489 ASSERT_NE(I, A.end()); 490 ASSERT_EQ(I->second.Number, 42); 491 492 StringMap<Countable> B; 493 B = std::move(A); 494 ASSERT_EQ(InstanceCount, 1); 495 ASSERT_TRUE(A.empty()); 496 I = B.find("x"); 497 ASSERT_NE(I, B.end()); 498 ASSERT_EQ(I->second.Number, 42); 499 500 B = StringMap<Countable>(); 501 ASSERT_EQ(InstanceCount, 0); 502 ASSERT_TRUE(B.empty()); 503 } 504 505 namespace { 506 // Simple class that counts how many moves and copy happens when growing a map 507 struct CountCtorCopyAndMove { 508 static unsigned Ctor; 509 static unsigned Move; 510 static unsigned Copy; 511 int Data = 0; 512 CountCtorCopyAndMove(int Data) : Data(Data) { Ctor++; } 513 CountCtorCopyAndMove() { Ctor++; } 514 515 CountCtorCopyAndMove(const CountCtorCopyAndMove &) { Copy++; } 516 CountCtorCopyAndMove &operator=(const CountCtorCopyAndMove &) { 517 Copy++; 518 return *this; 519 } 520 CountCtorCopyAndMove(CountCtorCopyAndMove &&) { Move++; } 521 CountCtorCopyAndMove &operator=(const CountCtorCopyAndMove &&) { 522 Move++; 523 return *this; 524 } 525 }; 526 unsigned CountCtorCopyAndMove::Copy = 0; 527 unsigned CountCtorCopyAndMove::Move = 0; 528 unsigned CountCtorCopyAndMove::Ctor = 0; 529 530 } // anonymous namespace 531 532 // Make sure creating the map with an initial size of N actually gives us enough 533 // buckets to insert N items without increasing allocation size. 534 TEST(StringMapCustomTest, InitialSizeTest) { 535 // 1 is an "edge value", 32 is an arbitrary power of two, and 67 is an 536 // arbitrary prime, picked without any good reason. 537 for (auto Size : {1, 32, 67}) { 538 StringMap<CountCtorCopyAndMove> Map(Size); 539 auto NumBuckets = Map.getNumBuckets(); 540 CountCtorCopyAndMove::Move = 0; 541 CountCtorCopyAndMove::Copy = 0; 542 for (int i = 0; i < Size; ++i) 543 Map.insert(std::pair<std::string, CountCtorCopyAndMove>( 544 std::piecewise_construct, std::forward_as_tuple(Twine(i).str()), 545 std::forward_as_tuple(i))); 546 // After the initial move, the map will move the Elts in the Entry. 547 EXPECT_EQ((unsigned)Size * 2, CountCtorCopyAndMove::Move); 548 // We copy once the pair from the Elts vector 549 EXPECT_EQ(0u, CountCtorCopyAndMove::Copy); 550 // Check that the map didn't grow 551 EXPECT_EQ(Map.getNumBuckets(), NumBuckets); 552 } 553 } 554 555 TEST(StringMapCustomTest, BracketOperatorCtor) { 556 StringMap<CountCtorCopyAndMove> Map; 557 CountCtorCopyAndMove::Ctor = 0; 558 Map["abcd"]; 559 EXPECT_EQ(1u, CountCtorCopyAndMove::Ctor); 560 // Test that operator[] does not create a value when it is already in the map 561 CountCtorCopyAndMove::Ctor = 0; 562 Map["abcd"]; 563 EXPECT_EQ(0u, CountCtorCopyAndMove::Ctor); 564 } 565 566 namespace { 567 struct NonMoveableNonCopyableType { 568 int Data = 0; 569 NonMoveableNonCopyableType() = default; 570 NonMoveableNonCopyableType(int Data) : Data(Data) {} 571 NonMoveableNonCopyableType(const NonMoveableNonCopyableType &) = delete; 572 NonMoveableNonCopyableType(NonMoveableNonCopyableType &&) = delete; 573 }; 574 } 575 576 // Test that we can "emplace" an element in the map without involving map/move 577 TEST(StringMapCustomTest, EmplaceTest) { 578 StringMap<NonMoveableNonCopyableType> Map; 579 Map.try_emplace("abcd", 42); 580 EXPECT_EQ(1u, Map.count("abcd")); 581 EXPECT_EQ(42, Map["abcd"].Data); 582 } 583 584 // Test that StringMapEntryBase can handle size_t wide sizes. 585 TEST(StringMapCustomTest, StringMapEntryBaseSize) { 586 size_t LargeValue; 587 588 // Test that the entry can represent max-unsigned. 589 if (sizeof(size_t) <= sizeof(unsigned)) 590 LargeValue = std::numeric_limits<unsigned>::max(); 591 else 592 LargeValue = std::numeric_limits<unsigned>::max() + 1ULL; 593 StringMapEntryBase LargeBase(LargeValue); 594 EXPECT_EQ(LargeValue, LargeBase.getKeyLength()); 595 596 // Test that the entry can hold at least max size_t. 597 LargeValue = std::numeric_limits<size_t>::max(); 598 StringMapEntryBase LargerBase(LargeValue); 599 LargeValue = std::numeric_limits<size_t>::max(); 600 EXPECT_EQ(LargeValue, LargerBase.getKeyLength()); 601 } 602 603 // Test that StringMapEntry can handle size_t wide sizes. 604 TEST(StringMapCustomTest, StringMapEntrySize) { 605 size_t LargeValue; 606 607 // Test that the entry can represent max-unsigned. 608 if (sizeof(size_t) <= sizeof(unsigned)) 609 LargeValue = std::numeric_limits<unsigned>::max(); 610 else 611 LargeValue = std::numeric_limits<unsigned>::max() + 1ULL; 612 StringMapEntry<int> LargeEntry(LargeValue); 613 StringRef Key = LargeEntry.getKey(); 614 EXPECT_EQ(LargeValue, Key.size()); 615 616 // Test that the entry can hold at least max size_t. 617 LargeValue = std::numeric_limits<size_t>::max(); 618 StringMapEntry<int> LargerEntry(LargeValue); 619 Key = LargerEntry.getKey(); 620 EXPECT_EQ(LargeValue, Key.size()); 621 } 622 623 } // end anonymous namespace 624