[clang] [LifetimeSafety] Add support for iterator arithmetic (PR #195442)
via cfe-commits
cfe-commits at lists.llvm.org
Mon May 4 05:44:35 PDT 2026
https://github.com/NeKon69 updated https://github.com/llvm/llvm-project/pull/195442
>From 4dde5a695d765d7a168ad6844eb7084fcd65cc30 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sat, 2 May 2026 13:23:37 +0300
Subject: [PATCH 1/7] add support for iterator pointer arithmetic
---
.../LifetimeSafety/LifetimeAnnotations.h | 3 +++
.../Analysis/LifetimeSafety/FactsGenerator.cpp | 17 +++++++++++++++++
.../LifetimeSafety/LifetimeAnnotations.cpp | 18 ++++++++++++++++++
3 files changed, 38 insertions(+)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
index 54d52fee6bea7..fdf6ccd211929 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 isPropogatingIteratorOP(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..1818d227d4d29 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 && isPropogatingIteratorOP(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..4b1fb83a5c06b 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -283,6 +283,24 @@ bool isUniquePtrRelease(const CXXMethodDecl &MD) {
MD.getNumParams() == 0 && isStdUniquePtr(*MD.getParent());
}
+bool isIteratorType(const CXXRecordDecl *RD) {
+ // FIXME: Add more iterator names in the future
+ static const llvm::StringSet<> Iterators = {
+ // Usually not an alias
+ "reverse_iterator",
+ // Alias for continuos iterators in gcc
+ "__normal_iterator",
+ // Alias for continuos iterators in clang
+ "__wrap_iter"};
+ return RD && isInStlNamespace(RD) && Iterators.contains(getName(*RD));
+}
+
+bool isPropogatingIteratorOP(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))
>From 7f0e7a0a4711b7fcb762dc35f4e9a7f7d0497ada Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sat, 2 May 2026 13:56:34 +0300
Subject: [PATCH 2/7] add tests
---
clang/test/Sema/Inputs/lifetime-analysis.h | 55 ++++++++++++++++++-
.../warn-lifetime-safety-invalidations.cpp | 30 ++++++++++
2 files changed, 84 insertions(+), 1 deletion(-)
diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h b/clang/test/Sema/Inputs/lifetime-analysis.h
index eaedba372ee5e..5f3f1a236a282 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,43 @@ 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 T, typename Alloc = allocator<T>>
struct vector {
- typedef __gnu_cxx::basic_iterator<T> iterator;
+ using iterator = __wrap_iter<T>;
+ using reverse_iterator = reverse_iterator<iterator>;
iterator begin();
iterator end();
+ reverse_iterator rbegin();
+ reverse_iterator rend();
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..9485a2cf273a3 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 = std::begin(v); // expected-warning {{object whose reference is captured is later invalidated}}
+ auto next = ++it;
+ v.push_back(4); // expected-note {{invalidated here}}
+ *next; // expected-note {{later used here}}
+}
+
+void IteratorUsedAfterPreDecrement(std::vector<int> v) {
+ auto it = std::end(v); // expected-warning {{object whose reference is captured is later invalidated}}
+ auto prev = --it;
+ v.resize(8); // expected-note {{invalidated here}}
+ *prev; // expected-note {{later used here}}
+}
+
+void IteratorUsedAfterAddition() {
+ std::vector<int> v;
+ auto it = std::begin(v); // expected-warning {{object whose reference is captured is later invalidated}}
+ auto next = it + 5;
+ v.insert(it, 0); // expected-note {{invalidated here}}
+ *next; // expected-note {{later used here}}
+}
+
+void IteratorUsedAfterReverseSubtraction(std::vector<int> v) {
+ auto it = std::end(v); // expected-warning {{object whose reference is captured is later invalidated}}
+ auto prev = 5 - it;
+ v.clear(); // expected-note {{invalidated here}}
+ *prev; // expected-note {{later used here}}
+}
} // namespace SimpleInvalidIterators
namespace ElementReferences {
>From 2c847bcd87a2b58eb0061e600519a1c63542a8ee Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sat, 2 May 2026 14:07:34 +0300
Subject: [PATCH 3/7] cleanup
---
.../Analyses/LifetimeSafety/LifetimeAnnotations.h | 2 +-
clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 2 +-
.../Analysis/LifetimeSafety/LifetimeAnnotations.cpp | 11 ++++++-----
3 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
index fdf6ccd211929..2f814216a4285 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
@@ -73,7 +73,7 @@ bool isUniquePtrRelease(const CXXMethodDecl &MD);
bool isIteratorType(const CXXRecordDecl *RD);
-bool isPropogatingIteratorOP(OverloadedOperatorKind OP);
+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 1818d227d4d29..f32e6b57c10e5 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -542,7 +542,7 @@ void FactsGenerator::VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE) {
}
}
- if (OCE->getNumArgs() < 3 && isPropogatingIteratorOP(OCE->getOperator())) {
+ if (OCE->getNumArgs() < 3 && isPropagatingIteratorOp(OCE->getOperator())) {
const Expr *IteratorArg = nullptr;
for (const Expr *Arg : OCE->arguments()) {
if (isIteratorType(Arg->getType()->getAsCXXRecordDecl())) {
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
index 4b1fb83a5c06b..15ee38ebd2b09 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -284,18 +284,19 @@ bool isUniquePtrRelease(const CXXMethodDecl &MD) {
}
bool isIteratorType(const CXXRecordDecl *RD) {
- // FIXME: Add more iterator names in the future
+ // FIXME: Extend this to cover more iterator wrapper types used by standard
+ // library implementations.
static const llvm::StringSet<> Iterators = {
- // Usually not an alias
+ // Standard reverse iterator wrapper.
"reverse_iterator",
- // Alias for continuos iterators in gcc
+ // libstdc++ contiguous iterator wrapper.
"__normal_iterator",
- // Alias for continuos iterators in clang
+ // libc++ contiguous iterator wrapper.
"__wrap_iter"};
return RD && isInStlNamespace(RD) && Iterators.contains(getName(*RD));
}
-bool isPropogatingIteratorOP(OverloadedOperatorKind OP) {
+bool isPropagatingIteratorOp(OverloadedOperatorKind OP) {
llvm::SmallDenseSet<OverloadedOperatorKind> PropagatingOperators = {
OO_Plus, OO_Minus, OO_PlusPlus, OO_MinusMinus};
return PropagatingOperators.contains(OP);
>From 4eda2377f023951566b1a5f4a1a6ced55e6bce39 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sat, 2 May 2026 14:13:04 +0300
Subject: [PATCH 4/7] cleanup tests
---
clang/test/Sema/Inputs/lifetime-analysis.h | 13 ++++++++++++-
.../warn-lifetime-safety-invalidations.cpp | 18 +++++++++---------
2 files changed, 21 insertions(+), 10 deletions(-)
diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h b/clang/test/Sema/Inputs/lifetime-analysis.h
index 5f3f1a236a282..4c6f833de9e3b 100644
--- a/clang/test/Sema/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/Inputs/lifetime-analysis.h
@@ -97,14 +97,25 @@ struct reverse_iterator {
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 {
using iterator = __wrap_iter<T>;
- using reverse_iterator = reverse_iterator<iterator>;
+ 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 9485a2cf273a3..5c812ff318cee 100644
--- a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
@@ -255,32 +255,32 @@ void IteratorUsedAfterPushBack(std::vector<int> v) {
void IteratorUsedAfterPreIncrement() {
std::vector<int> v;
- auto it = std::begin(v); // expected-warning {{object whose reference is captured is later invalidated}}
+ 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}}
- *next; // expected-note {{later used here}}
+ (void)*next; // expected-note {{later used here}}
}
void IteratorUsedAfterPreDecrement(std::vector<int> v) {
- auto it = std::end(v); // expected-warning {{object whose reference is captured is later invalidated}}
+ auto it = v.rbegin(); // expected-warning {{object whose reference is captured is later invalidated}}
auto prev = --it;
v.resize(8); // expected-note {{invalidated here}}
- *prev; // expected-note {{later used here}}
+ (void)*prev; // expected-note {{later used here}}
}
void IteratorUsedAfterAddition() {
std::vector<int> v;
- auto it = std::begin(v); // expected-warning {{object whose reference is captured is later invalidated}}
+ auto it = v.cbegin(); // expected-warning {{object whose reference is captured is later invalidated}}
auto next = it + 5;
- v.insert(it, 0); // expected-note {{invalidated here}}
- *next; // expected-note {{later used here}}
+ v.insert(v.begin(), 0); // expected-note {{invalidated here}}
+ (void)*next; // expected-note {{later used here}}
}
void IteratorUsedAfterReverseSubtraction(std::vector<int> v) {
- auto it = std::end(v); // expected-warning {{object whose reference is captured is later invalidated}}
+ auto it = v.crbegin(); // expected-warning {{object whose reference is captured is later invalidated}}
auto prev = 5 - it;
v.clear(); // expected-note {{invalidated here}}
- *prev; // expected-note {{later used here}}
+ (void)*prev; // expected-note {{later used here}}
}
} // namespace SimpleInvalidIterators
>From 5a0eff921fce9e6cfce02d4a64e38a18c1c9997a Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sun, 3 May 2026 14:36:44 +0300
Subject: [PATCH 5/7] update tests
---
clang/test/Sema/Inputs/lifetime-analysis.h | 11 +++++++++++
.../Sema/warn-lifetime-safety-invalidations.cpp | 13 +++++++------
2 files changed, 18 insertions(+), 6 deletions(-)
diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h b/clang/test/Sema/Inputs/lifetime-analysis.h
index 4c6f833de9e3b..2e7c7feab0b55 100644
--- a/clang/test/Sema/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/Inputs/lifetime-analysis.h
@@ -17,7 +17,9 @@ bool operator!=(basic_iterator<T>, basic_iterator<T>);
template <typename T>
struct __normal_iterator {
__normal_iterator operator++();
+ __normal_iterator operator++(int);
__normal_iterator operator--();
+ __normal_iterator operator--(int);
__normal_iterator operator+(int) const;
__normal_iterator operator-(int) const;
T& operator*() const;
@@ -72,7 +74,9 @@ template<typename T> class allocator {};
template <typename T>
struct __wrap_iter {
__wrap_iter operator++();
+ __wrap_iter operator++(int);
__wrap_iter operator--();
+ __wrap_iter operator--(int);
__wrap_iter operator+(int) const;
__wrap_iter operator-(int) const;
T& operator*() const;
@@ -91,7 +95,9 @@ __wrap_iter<T> operator-(int, __wrap_iter<T>);
template <typename Iterator>
struct reverse_iterator {
reverse_iterator operator++();
+ reverse_iterator operator++(int);
reverse_iterator operator--();
+ reverse_iterator operator--(int);
reverse_iterator operator+(int) const;
reverse_iterator operator-(int) const;
decltype(*Iterator()) operator*() const;
@@ -104,8 +110,13 @@ reverse_iterator<Iterator> operator-(int, reverse_iterator<Iterator>);
template <typename T, typename Alloc = allocator<T>>
struct vector {
+#ifdef USE_LIBSTDCPP_ITERATORS
+ using iterator = __gnu_cxx::__normal_iterator<T>;
+ using const_iterator = __gnu_cxx::__normal_iterator<const T>;
+#else
using iterator = __wrap_iter<T>;
using const_iterator = __wrap_iter<const T>;
+#endif
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
iterator begin();
diff --git a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
index 5c812ff318cee..9b613317374c0 100644
--- a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
@@ -1,4 +1,5 @@
// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -verify %s
+// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -verify -DUSE_LIBSTDCPP_ITERATORS %s
#include "Inputs/lifetime-analysis.h"
@@ -257,14 +258,14 @@ 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}}
+ v.push_back(1); // expected-note {{invalidated here}}
(void)*next; // expected-note {{later used here}}
}
-void IteratorUsedAfterPreDecrement(std::vector<int> v) {
+void IteratorUsedAfterPostDecrement(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}}
+ auto prev = it--;
+ v.push_back(1); // expected-note {{invalidated here}}
(void)*prev; // expected-note {{later used here}}
}
@@ -272,14 +273,14 @@ 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}}
+ v.push_back(1); // 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}}
+ v.push_back(1); // expected-note {{invalidated here}}
(void)*prev; // expected-note {{later used here}}
}
} // namespace SimpleInvalidIterators
>From 4c718b9a925345acbb1ce4da72cd3e11c2e7d154 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sun, 3 May 2026 23:34:30 +0300
Subject: [PATCH 6/7] switch to tracking all gsl::pointer types
---
.../LifetimeSafety/LifetimeAnnotations.h | 5 +-
.../LifetimeSafety/FactsGenerator.cpp | 19 +-----
.../LifetimeSafety/LifetimeAnnotations.cpp | 39 ++++++-------
clang/test/Sema/Inputs/lifetime-analysis.h | 58 +++----------------
4 files changed, 31 insertions(+), 90 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
index 2f814216a4285..2e52c3a7f74a3 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
@@ -61,6 +61,8 @@ bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee,
// std::any_cast.
bool shouldTrackFirstArgument(const FunctionDecl *FD);
+bool shouldTrackSecondArgument(const FunctionDecl *FD);
+
// Tells whether the type is annotated with [[gsl::Pointer]].
bool isGslPointerType(QualType QT);
// Tells whether the type is annotated with [[gsl::Owner]].
@@ -71,9 +73,6 @@ 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 f32e6b57c10e5..0a06548d881d1 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -542,23 +542,6 @@ 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.
@@ -914,6 +897,8 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
PVD = Method->getParamDecl(I - 1);
} else if (I == 0 && shouldTrackFirstArgument(FD)) {
return true;
+ } else if (I == 1 && shouldTrackSecondArgument(FD)) {
+ return true;
} else if (I < FD->getNumParams()) {
// For free functions or static methods.
PVD = FD->getParamDecl(I);
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
index 15ee38ebd2b09..b37ba403cd806 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -13,6 +13,7 @@
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/Type.h"
#include "clang/AST/TypeLoc.h"
+#include "clang/Basic/OperatorKinds.h"
#include "llvm/ADT/StringSet.h"
namespace clang::lifetimes {
@@ -139,8 +140,10 @@ bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee,
if (RunningUnderLifetimeSafety &&
isGslPointerType(Callee->getFunctionObjectParameterType()) &&
isReferenceOrPointerLikeType(Callee->getReturnType())) {
- if (Callee->getOverloadedOperator() == OverloadedOperatorKind::OO_Star ||
- Callee->getOverloadedOperator() == OverloadedOperatorKind::OO_Arrow)
+ auto Op = Callee->getOverloadedOperator();
+ llvm::SmallDenseSet<OverloadedOperatorKind> PropagatingOps = {
+ OO_Star, OO_Arrow, OO_PlusPlus, OO_MinusMinus, OO_Plus, OO_Minus};
+ if (PropagatingOps.contains(Op))
return true;
if (Callee->getIdentifier() &&
(IteratorMembers.contains(Callee->getName()) ||
@@ -230,6 +233,19 @@ bool shouldTrackFirstArgument(const FunctionDecl *FD) {
return false;
}
+bool shouldTrackSecondArgument(const FunctionDecl *FD) {
+ if (FD->getNumParams() < 2)
+ return false;
+ if (!isInStlNamespace(FD))
+ return false;
+ const auto *RD = FD->getParamDecl(1)->getType()->getAsCXXRecordDecl();
+ if (!RD || !isInStlNamespace(RD))
+ return false;
+ return RD->hasAttr<PointerAttr>() &&
+ (FD->getOverloadedOperator() == OO_Plus ||
+ FD->getOverloadedOperator() == OO_Minus);
+}
+
template <typename T> static bool isRecordWithAttr(QualType Type) {
auto *RD = Type->getAsCXXRecordDecl();
if (!RD)
@@ -283,25 +299,6 @@ 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 2e7c7feab0b55..79b009170ffd0 100644
--- a/clang/test/Sema/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/Inputs/lifetime-analysis.h
@@ -3,6 +3,11 @@ namespace __gnu_cxx {
template <typename T>
struct basic_iterator {
basic_iterator operator++();
+ basic_iterator operator++(int);
+ basic_iterator operator--();
+ basic_iterator operator--(int);
+ basic_iterator operator+(int);
+ basic_iterator operator-(int);
T& operator*() const;
T* operator->() const;
};
@@ -11,29 +16,10 @@ 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++(int);
- __normal_iterator operator--();
- __normal_iterator operator--(int);
- __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>);
+basic_iterator<T> operator+(int, basic_iterator<T>);
template<typename T>
-__normal_iterator<T> operator+(int, __normal_iterator<T>);
-template<typename T>
-__normal_iterator<T> operator-(int, __normal_iterator<T>);
+basic_iterator<T> operator-(int, basic_iterator<T>);
}
namespace std {
@@ -71,27 +57,6 @@ struct initializer_list {
};
template<typename T> class allocator {};
-template <typename T>
-struct __wrap_iter {
- __wrap_iter operator++();
- __wrap_iter operator++(int);
- __wrap_iter operator--();
- __wrap_iter operator--(int);
- __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++();
@@ -110,13 +75,8 @@ reverse_iterator<Iterator> operator-(int, reverse_iterator<Iterator>);
template <typename T, typename Alloc = allocator<T>>
struct vector {
-#ifdef USE_LIBSTDCPP_ITERATORS
- using iterator = __gnu_cxx::__normal_iterator<T>;
- using const_iterator = __gnu_cxx::__normal_iterator<const T>;
-#else
- using iterator = __wrap_iter<T>;
- using const_iterator = __wrap_iter<const T>;
-#endif
+ using iterator = __gnu_cxx::basic_iterator<T>;
+ using const_iterator = __gnu_cxx::basic_iterator<const T>;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
iterator begin();
>From 1bacd9a67272b114ed655c1f27fbbeafe3cff71c Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Mon, 4 May 2026 15:44:15 +0300
Subject: [PATCH 7/7] address review comments
---
.../LifetimeSafety/LifetimeAnnotations.h | 8 +++-
clang/include/clang/Basic/AttrDocs.td | 2 +-
.../LifetimeSafety/LifetimeAnnotations.cpp | 22 +++++++----
.../warn-lifetime-safety-invalidations.cpp | 38 ++++++++++++++++++-
4 files changed, 58 insertions(+), 12 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
index 2e52c3a7f74a3..fb65e37d9d4b7 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
@@ -49,8 +49,9 @@ bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD);
// Returns true if the implicit object argument (this) of a method call should
// be tracked for GSL lifetime analysis. This applies to STL methods that return
// pointers or references that depend on the lifetime of the object, such as
-// container iterators (begin, end), data accessors (c_str, data, get), or
-// element accessors (operator[], operator*, front, back, at).
+// container iterators (begin, end), data accessors (c_str, data, get),
+// element accessors (operator[], operator*, front, back, at), or propogating
+// operations (operator+, operator-, operator++, operator--).
bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee,
bool RunningUnderLifetimeSafety);
@@ -61,6 +62,9 @@ bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee,
// std::any_cast.
bool shouldTrackFirstArgument(const FunctionDecl *FD);
+// Returns true if the second argument of a free function should be tracked for
+// GSL lifetime analysis. This applies to free operator functions that take GSL
+// Pointer as it's second argument.
bool shouldTrackSecondArgument(const FunctionDecl *FD);
// Tells whether the type is annotated with [[gsl::Pointer]].
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 67aef81dd3a81..4f7ee5353ddf3 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -7841,7 +7841,7 @@ that provide transparent access to the pointed-to object. These include:
* Dereference operators: ``operator*``, ``operator->``
* Data access methods: ``data()``, ``c_str()``, ``get()``
-* Iterator methods: ``begin()``, ``end()``, ``rbegin()``, ``rend()``, ``cbegin()``, ``cend()``, ``crbegin()``, ``crend()``
+* Iterator methods: ``begin()``, ``end()``, ``rbegin()``, ``rend()``, ``cbegin()``, ``cend()``, ``crbegin()``, ``crend()``, ``operator+``, ``operator-``, ``operator++``, ``operator--``
When these methods return pointers, view types, or references, the analysis treats them as
transparently borrowing from the same object that the pointer itself borrows from,
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
index b37ba403cd806..d7bd06ceadfd2 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -140,11 +140,17 @@ bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee,
if (RunningUnderLifetimeSafety &&
isGslPointerType(Callee->getFunctionObjectParameterType()) &&
isReferenceOrPointerLikeType(Callee->getReturnType())) {
- auto Op = Callee->getOverloadedOperator();
- llvm::SmallDenseSet<OverloadedOperatorKind> PropagatingOps = {
- OO_Star, OO_Arrow, OO_PlusPlus, OO_MinusMinus, OO_Plus, OO_Minus};
- if (PropagatingOps.contains(Op))
+ switch (Callee->getOverloadedOperator()) {
+ case OO_Arrow:
+ case OO_Star:
+ case OO_Plus:
+ case OO_Minus:
+ case OO_PlusPlus:
+ case OO_MinusMinus:
return true;
+ default:
+ break;
+ }
if (Callee->getIdentifier() &&
(IteratorMembers.contains(Callee->getName()) ||
InnerPointerGetters.contains(Callee->getName())))
@@ -236,14 +242,14 @@ bool shouldTrackFirstArgument(const FunctionDecl *FD) {
bool shouldTrackSecondArgument(const FunctionDecl *FD) {
if (FD->getNumParams() < 2)
return false;
- if (!isInStlNamespace(FD))
- return false;
const auto *RD = FD->getParamDecl(1)->getType()->getAsCXXRecordDecl();
- if (!RD || !isInStlNamespace(RD))
+ if (!RD)
return false;
return RD->hasAttr<PointerAttr>() &&
(FD->getOverloadedOperator() == OO_Plus ||
- FD->getOverloadedOperator() == OO_Minus);
+ FD->getOverloadedOperator() == OO_Minus) &&
+ ASTContext::hasSameUnqualifiedType(FD->getParamDecl(1)->getType(),
+ FD->getReturnType());
}
template <typename T> static bool isRecordWithAttr(QualType Type) {
diff --git a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
index 9b613317374c0..82df24e82c387 100644
--- a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
@@ -1,5 +1,4 @@
// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -verify %s
-// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -verify -DUSE_LIBSTDCPP_ITERATORS %s
#include "Inputs/lifetime-analysis.h"
@@ -283,6 +282,43 @@ void IteratorUsedAfterReverseSubtraction(std::vector<int> v) {
v.push_back(1); // expected-note {{invalidated here}}
(void)*prev; // expected-note {{later used here}}
}
+
+void IteratorUSedAfterAddAdd(std::vector<int> v) {
+ auto it = v.cbegin(); // expected-warning {{object whose reference is captured is later invalidated}}
+ auto next = (it + 5) + 5;
+ v.push_back(1); // expected-note {{invalidated here}}
+ (void)*next; // expected-note {{later used here}}
+}
+
+void IteratorUsedAfterMixedAddition() {
+ std::vector<int> v;
+ auto it = v.cbegin(); // expected-warning {{object whose reference is captured is later invalidated}}
+ auto next = 1 + it + 2 + 3;
+ v.push_back(1); // expected-note {{invalidated here}}
+ (void)*next; // expected-note {{later used here}}
+}
+
+void IteratorUsedAfterPreIncrementAddAssign(std::vector<int> v) {
+ auto it = v.begin(); // expected-warning {{object whose reference is captured is later invalidated}}
+ it = ++it + 1 + 2;
+ v.push_back(1); // expected-note {{invalidated here}}
+ (void)*it; // expected-note {{later used here}}
+}
+
+void IteratorUsedAfterBeginAddAssign() {
+ std::vector<int> v;
+ auto it = v.begin() + 1; // expected-warning {{object whose reference is captured is later invalidated}}
+ v.push_back(1); // expected-note {{invalidated here}}
+ (void)*it; // expected-note {{later used here}}
+}
+
+void IteratorUsedAfterStdBeginAddAssign() {
+ std::vector<int> v;
+ std::vector<int>::iterator it;
+ it = std::begin(v) + 1; // expected-warning {{object whose reference is captured is later invalidated}}
+ v.push_back(1); // expected-note {{invalidated here}}
+ (void)*it; // expected-note {{later used here}}
+}
} // namespace SimpleInvalidIterators
namespace ElementReferences {
More information about the cfe-commits
mailing list