[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