[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