[libcxx-commits] [libcxx] [libc++] Fix __hash_table::erase(iterator, iterator) to update the bucket list correctly when erasing the last bucket (PR #167865)

Nikolas Klauser via libcxx-commits libcxx-commits at lists.llvm.org
Thu Nov 13 03:21:55 PST 2025


https://github.com/philnik777 updated https://github.com/llvm/llvm-project/pull/167865

>From cd267145eae8efc1135bc600daae1c3252d0beea Mon Sep 17 00:00:00 2001
From: Nikolas Klauser <nikolasklauser at berlin.de>
Date: Thu, 13 Nov 2025 12:19:25 +0100
Subject: [PATCH] [libc++] Fix __hash_table::erase(iterator, iterator) to
 update the bucket list correctly when erasing the last bucket

---
 libcxx/include/__hash_table                   |  2 ++
 .../unord.map.modifiers/erase_range.pass.cpp  | 22 +++++++++++++++++++
 .../erase_range.pass.cpp                      | 22 +++++++++++++++++++
 .../unord/unord.multiset/erase_range.pass.cpp | 22 +++++++++++++++++++
 .../unord/unord.set/erase_range.pass.cpp      | 22 +++++++++++++++++++
 5 files changed, 90 insertions(+)

diff --git a/libcxx/include/__hash_table b/libcxx/include/__hash_table
index e1897949a47e6..ef487fb06dd5e 100644
--- a/libcxx/include/__hash_table
+++ b/libcxx/include/__hash_table
@@ -1910,6 +1910,8 @@ __hash_table<_Tp, _Hash, _Equal, _Alloc>::erase(const_iterator __first, const_it
         __bucket_list_[__next_chash] = __before_first;
         __chash                      = __next_chash;
       }
+    } else { // When __next is a nullptr we've fully erased the last bucket. Update the bucket list accordingly.
+      __bucket_list_[__chash] = nullptr;
     }
   }
 
diff --git a/libcxx/test/std/containers/unord/unord.map/unord.map.modifiers/erase_range.pass.cpp b/libcxx/test/std/containers/unord/unord.map/unord.map.modifiers/erase_range.pass.cpp
index 532413437f6be..21bd43051786c 100644
--- a/libcxx/test/std/containers/unord/unord.map/unord.map.modifiers/erase_range.pass.cpp
+++ b/libcxx/test/std/containers/unord/unord.map/unord.map.modifiers/erase_range.pass.cpp
@@ -57,6 +57,28 @@ int main(int, char**) {
     assert(c.size() == 0);
     assert(k == c.end());
   }
+  { // Make sure that we're properly updating the bucket list when we're erasing to the end
+    std::unordered_map<int, int> m;
+    m.emplace(1, 1);
+    m.emplace(2, 2);
+
+    {
+      auto [it, end] = m.equal_range(1);
+      assert(it != end);
+      m.erase(it, end);
+    }
+
+    {
+      auto [it, end] = m.equal_range(2);
+      assert(it != end);
+      m.erase(it, end);
+    }
+
+    m.emplace(3, 3);
+    assert(m.size() == 1);
+    assert(*m.begin() == std::make_pair(3, 3));
+    assert(++m.begin() == m.end());
+  }
 #if TEST_STD_VER >= 11
   {
     typedef std::unordered_map<int,
diff --git a/libcxx/test/std/containers/unord/unord.multimap/unord.multimap.modifiers/erase_range.pass.cpp b/libcxx/test/std/containers/unord/unord.multimap/unord.multimap.modifiers/erase_range.pass.cpp
index 38b75c0c1986b..95d1414e855fd 100644
--- a/libcxx/test/std/containers/unord/unord.multimap/unord.multimap.modifiers/erase_range.pass.cpp
+++ b/libcxx/test/std/containers/unord/unord.multimap/unord.multimap.modifiers/erase_range.pass.cpp
@@ -122,6 +122,28 @@ int main(int, char**) {
     for (const auto& v : map)
       assert(v.first == 1 || v.first == collision_val);
   }
+  { // Make sure that we're properly updating the bucket list when we're erasing to the end
+    std::unordered_multimap<int, int> m;
+    m.emplace(1, 1);
+    m.emplace(2, 2);
+
+    {
+      auto [it, end] = m.equal_range(1);
+      assert(it != end);
+      m.erase(it, end);
+    }
+
+    {
+      auto [it, end] = m.equal_range(2);
+      assert(it != end);
+      m.erase(it, end);
+    }
+
+    m.emplace(3, 3);
+    assert(m.size() == 1);
+    assert(*m.begin() == std::make_pair(3, 3));
+    assert(++m.begin() == m.end());
+  }
 #if TEST_STD_VER >= 11
   {
     typedef std::unordered_multimap<int,
diff --git a/libcxx/test/std/containers/unord/unord.multiset/erase_range.pass.cpp b/libcxx/test/std/containers/unord/unord.multiset/erase_range.pass.cpp
index 3bc686ec2d86e..c9f3abb882273 100644
--- a/libcxx/test/std/containers/unord/unord.multiset/erase_range.pass.cpp
+++ b/libcxx/test/std/containers/unord/unord.multiset/erase_range.pass.cpp
@@ -64,6 +64,28 @@ int main(int, char**) {
     for (const auto& v : map)
       assert(v == 1 || v == collision_val);
   }
+  { // Make sure that we're properly updating the bucket list when we're erasing to the end
+    std::unordered_multiset<int> m;
+    m.emplace(1);
+    m.emplace(2);
+
+    {
+      auto [it, end] = m.equal_range(1);
+      assert(it != end);
+      m.erase(it, end);
+    }
+
+    {
+      auto [it, end] = m.equal_range(2);
+      assert(it != end);
+      m.erase(it, end);
+    }
+
+    m.emplace(3);
+    assert(m.size() == 1);
+    assert(*m.begin() == 3);
+    assert(++m.begin() == m.end());
+  }
 #if TEST_STD_VER >= 11
   {
     typedef std::unordered_multiset<int, std::hash<int>, std::equal_to<int>, min_allocator<int>> C;
diff --git a/libcxx/test/std/containers/unord/unord.set/erase_range.pass.cpp b/libcxx/test/std/containers/unord/unord.set/erase_range.pass.cpp
index 5fa6e4199f756..256fd8ddcd955 100644
--- a/libcxx/test/std/containers/unord/unord.set/erase_range.pass.cpp
+++ b/libcxx/test/std/containers/unord/unord.set/erase_range.pass.cpp
@@ -47,6 +47,28 @@ int main(int, char**) {
     assert(c.size() == 0);
     assert(k == c.end());
   }
+  { // Make sure that we're properly updating the bucket list when we're erasing to the end
+    std::unordered_set<int> m;
+    m.emplace(1);
+    m.emplace(2);
+
+    {
+      auto [it, end] = m.equal_range(1);
+      assert(it != end);
+      m.erase(it, end);
+    }
+
+    {
+      auto [it, end] = m.equal_range(2);
+      assert(it != end);
+      m.erase(it, end);
+    }
+
+    m.emplace(3);
+    assert(m.size() == 1);
+    assert(*m.begin() == 3);
+    assert(++m.begin() == m.end());
+  }
 #if TEST_STD_VER >= 11
   {
     typedef std::unordered_set<int, std::hash<int>, std::equal_to<int>, min_allocator<int>> C;



More information about the libcxx-commits mailing list