[clang] [LifetimeSafety] Add support for iterator arithmetic (PR #195442)
via cfe-commits
cfe-commits at lists.llvm.org
Sat May 2 04:20:22 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-temporal-safety
Author: NeKon69
<details>
<summary>Changes</summary>
This PR adds origin propagation through iterator arithmetic.
It also adds a few tests to check the new behavior.
Closes #<!-- -->190140
---
Full diff: https://github.com/llvm/llvm-project/pull/195442.diff
5 Files Affected:
- (modified) clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h (+3)
- (modified) clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp (+17)
- (modified) clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp (+19)
- (modified) clang/test/Sema/Inputs/lifetime-analysis.h (+65-1)
- (modified) clang/test/Sema/warn-lifetime-safety-invalidations.cpp (+30)
``````````diff
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
index 54d52fee6bea7..2f814216a4285 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
@@ -71,6 +71,9 @@ bool isGslOwnerType(QualType QT);
// when ownership is manually transferred.
bool isUniquePtrRelease(const CXXMethodDecl &MD);
+bool isIteratorType(const CXXRecordDecl *RD);
+
+bool isPropagatingIteratorOp(OverloadedOperatorKind OP);
// Returns true if the given method invalidates references tracked by lifetime
// analysis (e.g. vector::push_back). Methods that only invalidate iterators but
// not references (e.g. unordered_map::emplace) are not considered invalidating
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index dc80d2783fc03..f32e6b57c10e5 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -542,6 +542,23 @@ void FactsGenerator::VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE) {
}
}
+ if (OCE->getNumArgs() < 3 && isPropagatingIteratorOp(OCE->getOperator())) {
+ const Expr *IteratorArg = nullptr;
+ for (const Expr *Arg : OCE->arguments()) {
+ if (isIteratorType(Arg->getType()->getAsCXXRecordDecl())) {
+ IteratorArg = Arg;
+ break;
+ }
+ }
+
+ if (IteratorArg) {
+ flow(getOriginsList(*OCE),
+ getRValueOrigins(IteratorArg, getOriginsList(*IteratorArg)),
+ /*Kill=*/true);
+ return;
+ }
+ }
+
ArrayRef Args = {OCE->getArgs(), OCE->getNumArgs()};
// For `static operator()`, the first argument is the object argument,
// remove it from the argument list to avoid off-by-one errors.
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
index b33fb8edc100b..15ee38ebd2b09 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -283,6 +283,25 @@ bool isUniquePtrRelease(const CXXMethodDecl &MD) {
MD.getNumParams() == 0 && isStdUniquePtr(*MD.getParent());
}
+bool isIteratorType(const CXXRecordDecl *RD) {
+ // FIXME: Extend this to cover more iterator wrapper types used by standard
+ // library implementations.
+ static const llvm::StringSet<> Iterators = {
+ // Standard reverse iterator wrapper.
+ "reverse_iterator",
+ // libstdc++ contiguous iterator wrapper.
+ "__normal_iterator",
+ // libc++ contiguous iterator wrapper.
+ "__wrap_iter"};
+ return RD && isInStlNamespace(RD) && Iterators.contains(getName(*RD));
+}
+
+bool isPropagatingIteratorOp(OverloadedOperatorKind OP) {
+ llvm::SmallDenseSet<OverloadedOperatorKind> PropagatingOperators = {
+ OO_Plus, OO_Minus, OO_PlusPlus, OO_MinusMinus};
+ return PropagatingOperators.contains(OP);
+}
+
bool isInvalidationMethod(const CXXMethodDecl &MD) {
const CXXRecordDecl *RD = MD.getParent();
if (!isInStlNamespace(RD))
diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h b/clang/test/Sema/Inputs/lifetime-analysis.h
index eaedba372ee5e..4c6f833de9e3b 100644
--- a/clang/test/Sema/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/Inputs/lifetime-analysis.h
@@ -11,6 +11,27 @@ template<typename T>
bool operator==(basic_iterator<T>, basic_iterator<T>);
template<typename T>
bool operator!=(basic_iterator<T>, basic_iterator<T>);
+
+// These iterator spellings match libstdc++ names documented at:
+// https://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-4.2/namespace____gnu__cxx.html
+template <typename T>
+struct __normal_iterator {
+ __normal_iterator operator++();
+ __normal_iterator operator--();
+ __normal_iterator operator+(int) const;
+ __normal_iterator operator-(int) const;
+ T& operator*() const;
+ T* operator->() const;
+};
+
+template<typename T>
+bool operator==(__normal_iterator<T>, __normal_iterator<T>);
+template<typename T>
+bool operator!=(__normal_iterator<T>, __normal_iterator<T>);
+template<typename T>
+__normal_iterator<T> operator+(int, __normal_iterator<T>);
+template<typename T>
+__normal_iterator<T> operator-(int, __normal_iterator<T>);
}
namespace std {
@@ -47,11 +68,54 @@ struct initializer_list {
const T* ptr; size_t sz;
};
template<typename T> class allocator {};
+
+template <typename T>
+struct __wrap_iter {
+ __wrap_iter operator++();
+ __wrap_iter operator--();
+ __wrap_iter operator+(int) const;
+ __wrap_iter operator-(int) const;
+ T& operator*() const;
+ T* operator->() const;
+};
+
+template<typename T>
+bool operator==(__wrap_iter<T>, __wrap_iter<T>);
+template<typename T>
+bool operator!=(__wrap_iter<T>, __wrap_iter<T>);
+template<typename T>
+__wrap_iter<T> operator+(int, __wrap_iter<T>);
+template<typename T>
+__wrap_iter<T> operator-(int, __wrap_iter<T>);
+
+template <typename Iterator>
+struct reverse_iterator {
+ reverse_iterator operator++();
+ reverse_iterator operator--();
+ reverse_iterator operator+(int) const;
+ reverse_iterator operator-(int) const;
+ decltype(*Iterator()) operator*() const;
+};
+
+template <typename Iterator>
+reverse_iterator<Iterator> operator+(int, reverse_iterator<Iterator>);
+template <typename Iterator>
+reverse_iterator<Iterator> operator-(int, reverse_iterator<Iterator>);
+
template <typename T, typename Alloc = allocator<T>>
struct vector {
- typedef __gnu_cxx::basic_iterator<T> iterator;
+ using iterator = __wrap_iter<T>;
+ using const_iterator = __wrap_iter<const T>;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
iterator begin();
iterator end();
+ const_iterator cbegin() const;
+ const_iterator cend() const;
+ reverse_iterator rbegin();
+ reverse_iterator rend();
+ const_reverse_iterator crbegin() const;
+ const_reverse_iterator crend() const;
const T *data() const;
vector();
~vector();
diff --git a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
index f1044e2ad1cdd..5c812ff318cee 100644
--- a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
@@ -252,6 +252,36 @@ void IteratorUsedAfterPushBack(std::vector<int> v) {
}
++it; // expected-note {{later used here}}
}
+
+void IteratorUsedAfterPreIncrement() {
+ std::vector<int> v;
+ auto it = v.begin(); // expected-warning {{object whose reference is captured is later invalidated}}
+ auto next = ++it;
+ v.push_back(4); // expected-note {{invalidated here}}
+ (void)*next; // expected-note {{later used here}}
+}
+
+void IteratorUsedAfterPreDecrement(std::vector<int> v) {
+ auto it = v.rbegin(); // expected-warning {{object whose reference is captured is later invalidated}}
+ auto prev = --it;
+ v.resize(8); // expected-note {{invalidated here}}
+ (void)*prev; // expected-note {{later used here}}
+}
+
+void IteratorUsedAfterAddition() {
+ std::vector<int> v;
+ auto it = v.cbegin(); // expected-warning {{object whose reference is captured is later invalidated}}
+ auto next = it + 5;
+ v.insert(v.begin(), 0); // expected-note {{invalidated here}}
+ (void)*next; // expected-note {{later used here}}
+}
+
+void IteratorUsedAfterReverseSubtraction(std::vector<int> v) {
+ auto it = v.crbegin(); // expected-warning {{object whose reference is captured is later invalidated}}
+ auto prev = 5 - it;
+ v.clear(); // expected-note {{invalidated here}}
+ (void)*prev; // expected-note {{later used here}}
+}
} // namespace SimpleInvalidIterators
namespace ElementReferences {
``````````
</details>
https://github.com/llvm/llvm-project/pull/195442
More information about the cfe-commits
mailing list