[llvm] [FoldingSet] Invalidate iterators on mutation (PR #218179)

Fangrui Song via llvm-commits llvm-commits at lists.llvm.org
Sat Aug 22 20:07:51 PDT 2026


https://github.com/MaskRay created https://github.com/llvm/llvm-project/pull/218179

Tighten FoldingSet's iterator contract so that, like DenseMap (#199369)
and StringMap (#202237), mutating the set invalidates iterators obtained
before the call. insert / remove (successful) / clear / move bump the
epoch, so a stale iterator fails under LLVM_ENABLE_ABI_BREAKING_CHECKS.

LLM-aided


>From 48ca332cd7f0cd0954af9baeb6617bdb450a4a4d Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sat, 22 Aug 2026 20:03:32 -0700
Subject: [PATCH] [FoldingSet] Invalidate iterators on mutation

Tighten FoldingSet's iterator contract so that, like DenseMap (#199369)
and StringMap (#202237), mutating the set invalidates iterators obtained
before the call. insert / remove (successful) / clear / move bump the
epoch, so a stale iterator fails under LLVM_ENABLE_ABI_BREAKING_CHECKS.

LLM-aided
---
 llvm/include/llvm/ADT/FoldingSet.h | 31 ++++++++++++-------
 llvm/lib/Support/FoldingSet.cpp    | 11 ++++++-
 llvm/unittests/ADT/FoldingSet.cpp  | 48 ++++++++++++++++++++++++++++++
 3 files changed, 78 insertions(+), 12 deletions(-)

diff --git a/llvm/include/llvm/ADT/FoldingSet.h b/llvm/include/llvm/ADT/FoldingSet.h
index ab4fa2712d4a5..114a689a1073a 100644
--- a/llvm/include/llvm/ADT/FoldingSet.h
+++ b/llvm/include/llvm/ADT/FoldingSet.h
@@ -16,6 +16,7 @@
 #ifndef LLVM_ADT_FOLDINGSET_H
 #define LLVM_ADT_FOLDINGSET_H
 
+#include "llvm/ADT/EpochTracker.h"
 #include "llvm/ADT/Hashing.h"
 #include "llvm/ADT/STLForwardCompat.h"
 #include "llvm/ADT/SmallVector.h"
@@ -295,7 +296,7 @@ class FoldingSetNodeID {
 /// linked list.  The last node in the list points back to the bucket to
 /// facilitate node removal.
 ///
-class FoldingSetBase {
+class FoldingSetBase : public DebugEpochBase {
 protected:
   /// Array of bucket chains.
   void **Buckets;
@@ -496,13 +497,13 @@ class FoldingSetImpl : public FoldingSetBase, public Trait::ContextStorage {
 public:
   using iterator = FoldingSetIterator<T>;
 
-  iterator begin() { return iterator(Buckets); }
-  iterator end() { return iterator(Buckets + NumBuckets); }
+  iterator begin() { return iterator(this, Buckets); }
+  iterator end() { return iterator(this, Buckets + NumBuckets); }
 
   using const_iterator = FoldingSetIterator<const T>;
 
-  const_iterator begin() const { return const_iterator(Buckets); }
-  const_iterator end() const { return const_iterator(Buckets + NumBuckets); }
+  const_iterator begin() const { return const_iterator(this, Buckets); }
+  const_iterator end() const { return const_iterator(this, Buckets + NumBuckets); }
 
   /// Grow the number of buckets so that we can hold at least \p EltCount
   /// nodes before rebucketing. May allocate more space than requested.
@@ -636,30 +637,38 @@ template <class T, class VectorT = SmallVector<T *, 8>> class FoldingSetVector {
 //===----------------------------------------------------------------------===//
 /// This is the common iterator support shared by all folding sets, which knows
 /// how to walk the folding set hash table.
-class FoldingSetIteratorImpl {
+class FoldingSetIteratorImpl : protected DebugEpochBase::HandleBase {
 protected:
   FoldingSetNode *NodePtr;
 
-  LLVM_ABI FoldingSetIteratorImpl(void **Bucket);
+  LLVM_ABI FoldingSetIteratorImpl(const DebugEpochBase *Epoch, void **Bucket);
 
   LLVM_ABI void advance();
 
 public:
   bool operator==(const FoldingSetIteratorImpl &RHS) const {
+    assert(isHandleInSync() && RHS.isHandleInSync() && "handle not in sync!");
     return NodePtr == RHS.NodePtr;
   }
   bool operator!=(const FoldingSetIteratorImpl &RHS) const {
-    return NodePtr != RHS.NodePtr;
+    return !(*this == RHS);
   }
 };
 
 template <class T> class FoldingSetIterator : public FoldingSetIteratorImpl {
 public:
-  explicit FoldingSetIterator(void **Bucket) : FoldingSetIteratorImpl(Bucket) {}
+  explicit FoldingSetIterator(const DebugEpochBase *Epoch, void **Bucket)
+      : FoldingSetIteratorImpl(Epoch, Bucket) {}
 
-  T &operator*() const { return *static_cast<T *>(NodePtr); }
+  T &operator*() const {
+    assert(isHandleInSync() && "invalid iterator access!");
+    return *static_cast<T *>(NodePtr);
+  }
 
-  T *operator->() const { return static_cast<T *>(NodePtr); }
+  T *operator->() const {
+    assert(isHandleInSync() && "invalid iterator access!");
+    return static_cast<T *>(NodePtr);
+  }
 
   inline FoldingSetIterator &operator++() { // Preincrement
     advance();
diff --git a/llvm/lib/Support/FoldingSet.cpp b/llvm/lib/Support/FoldingSet.cpp
index d9ae1aca5fc4a..d4bd863fa9a3f 100644
--- a/llvm/lib/Support/FoldingSet.cpp
+++ b/llvm/lib/Support/FoldingSet.cpp
@@ -185,12 +185,15 @@ FoldingSetBase::FoldingSetBase(unsigned Log2InitSize) {
 
 FoldingSetBase::FoldingSetBase(FoldingSetBase &&Arg)
     : Buckets(Arg.Buckets), NumBuckets(Arg.NumBuckets), NumNodes(Arg.NumNodes) {
+  Arg.incrementEpoch();
   Arg.Buckets = nullptr;
   Arg.NumBuckets = 0;
   Arg.NumNodes = 0;
 }
 
 FoldingSetBase &FoldingSetBase::operator=(FoldingSetBase &&RHS) {
+  incrementEpoch();
+  RHS.incrementEpoch();
   free(Buckets); // This may be null if the set is in a moved-from state.
   Buckets = RHS.Buckets;
   NumBuckets = RHS.NumBuckets;
@@ -204,6 +207,7 @@ FoldingSetBase &FoldingSetBase::operator=(FoldingSetBase &&RHS) {
 FoldingSetBase::~FoldingSetBase() { free(Buckets); }
 
 void FoldingSetBase::clear() {
+  incrementEpoch();
   // Set all but the last bucket to null pointers.
   memset(Buckets, 0, NumBuckets * sizeof(void *));
 
@@ -278,6 +282,7 @@ FoldingSetBase::Node *FoldingSetBase::FindNodeOrInsertPos(
 void FoldingSetBase::InsertNode(Node *N, void *InsertPos,
                                 const FoldingSetInfo &Info) {
   assert(!N->getNextInBucket());
+  incrementEpoch();
   // Do we need to grow the hashtable?
   if (NumNodes + 1 > capacity()) {
     GrowBucketCount(NumBuckets * 2, Info);
@@ -311,6 +316,7 @@ bool FoldingSetBase::RemoveNode(Node *N) {
   if (!Ptr)
     return false; // Not in folding set.
 
+  incrementEpoch();
   --NumNodes;
   N->SetNextInBucket(nullptr);
 
@@ -357,7 +363,9 @@ FoldingSetBase::GetOrInsertNode(Node *N, const FoldingSetInfo &Info) {
 //===----------------------------------------------------------------------===//
 // FoldingSetIteratorImpl Implementation
 
-FoldingSetIteratorImpl::FoldingSetIteratorImpl(void **Bucket) {
+FoldingSetIteratorImpl::FoldingSetIteratorImpl(const DebugEpochBase *Epoch,
+                                               void **Bucket)
+    : DebugEpochBase::HandleBase(Epoch) {
   // Skip to the first non-null non-self-cycle bucket.
   while (*Bucket != reinterpret_cast<void *>(-1) &&
          (!*Bucket || !GetNextPtr(*Bucket)))
@@ -367,6 +375,7 @@ FoldingSetIteratorImpl::FoldingSetIteratorImpl(void **Bucket) {
 }
 
 void FoldingSetIteratorImpl::advance() {
+  assert(isHandleInSync() && "invalid iterator access!");
   // If there is another link within this bucket, go to it.
   void *Probe = NodePtr->getNextInBucket();
 
diff --git a/llvm/unittests/ADT/FoldingSet.cpp b/llvm/unittests/ADT/FoldingSet.cpp
index f02fbdb0d459e..ff0f253cf355a 100644
--- a/llvm/unittests/ADT/FoldingSet.cpp
+++ b/llvm/unittests/ADT/FoldingSet.cpp
@@ -358,4 +358,52 @@ TEST(FoldingSetTest, ContextualFoldingSetBasic) {
   EXPECT_THAT(Set, SizeIs(0));
 }
 
+#if LLVM_ENABLE_ABI_BREAKING_CHECKS
+TEST(FoldingSetTest, InsertInvalidatesIterators) {
+  FoldingSet<TrivialPair> Set;
+  TrivialPair T1(1, 1), T2(2, 2);
+  Set.InsertNode(&T1);
+  auto It = Set.begin();
+  Set.InsertNode(&T2);
+  EXPECT_DEATH((void)It->Value, "invalid iterator access");
+}
+
+TEST(FoldingSetTest, RemoveInvalidatesIterators) {
+  FoldingSet<TrivialPair> Set;
+  TrivialPair T1(1, 1), T2(2, 2);
+  Set.InsertNode(&T1);
+  Set.InsertNode(&T2);
+  auto It = Set.begin();
+  Set.RemoveNode(&T2);
+  EXPECT_DEATH((void)It->Value, "invalid iterator access");
+}
+
+TEST(FoldingSetTest, RemoveOfAbsentNodeKeepsIterators) {
+  FoldingSet<TrivialPair> Set;
+  TrivialPair T1(1, 1), Absent(2, 2);
+  Set.InsertNode(&T1);
+  auto It = Set.begin();
+  EXPECT_FALSE(Set.RemoveNode(&Absent));
+  EXPECT_EQ(&T1, &*It);
+}
+
+TEST(FoldingSetTest, ClearInvalidatesIterators) {
+  FoldingSet<TrivialPair> Set;
+  TrivialPair T1(1, 1);
+  Set.InsertNode(&T1);
+  auto It = Set.begin();
+  Set.clear();
+  EXPECT_DEATH((void)It->Value, "invalid iterator access");
+}
+
+TEST(FoldingSetTest, MoveInvalidatesIterators) {
+  FoldingSet<TrivialPair> Set;
+  TrivialPair T1(1, 1);
+  Set.InsertNode(&T1);
+  auto It = Set.begin();
+  FoldingSet<TrivialPair> Other(std::move(Set));
+  EXPECT_DEATH((void)It->Value, "invalid iterator access");
+}
+#endif
+
 } // namespace



More information about the llvm-commits mailing list