[clang] [LifetimeSafety] Generalize invalidating member function detection (PR #194907)

via cfe-commits cfe-commits at lists.llvm.org
Wed Apr 29 09:47:56 PDT 2026


https://github.com/NeKon69 created https://github.com/llvm/llvm-project/pull/194907

This PR adds support for invalidating references after reassigning a `unique_ptr` or calling its `reset` member function.

Previously, invalidation handling was limited to container-like types. This PR generalizes the helper for detecting invalidating member calls and adds `unique_ptr`'s `reset` member function as an invalidating one. Since `unique_ptr` is now handled by this helper, reassignment through `operator=` is also treated as invalidating.

Fixes #184630

>From 3b8eac663f65fb9bcfcc23a2c42b8ae15f71a202 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Wed, 29 Apr 2026 17:29:57 +0300
Subject: [PATCH 1/5] apply fix

---
 .../LifetimeSafety/LifetimeAnnotations.h        |  2 ++
 .../LifetimeSafety/LifetimeAnnotations.cpp      | 17 +++++++++++++++--
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
index 098c15f4a7fb4..1a4354edbc281 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
@@ -71,6 +71,8 @@ bool isGslOwnerType(QualType QT);
 // when ownership is manually transferred.
 bool isUniquePtrRelease(const CXXMethodDecl &MD);
 
+bool isUniquePtrReset(const CXXMethodDecl &MD);
+
 // Returns true if the given method invalidates references to container
 // elements (e.g. vector::push_back). Methods that only invalidate iterators
 // but not references (e.g. unordered_map::emplace) are not considered
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
index 27d95821dd0b4..7b617fd6e3215 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -272,9 +272,18 @@ static bool isStdUniquePtr(const CXXRecordDecl &RD) {
   return RD.isInStdNamespace() && getName(RD) == "unique_ptr";
 }
 
+static bool isStdUniquePtrFunc(const CXXMethodDecl &MD, unsigned int Num,
+                               StringRef Name) {
+  return MD.getIdentifier() && MD.getName() == Name &&
+         MD.getNumParams() == Num && isStdUniquePtr(*MD.getParent());
+}
+
 bool isUniquePtrRelease(const CXXMethodDecl &MD) {
-  return MD.getIdentifier() && MD.getName() == "release" &&
-         MD.getNumParams() == 0 && isStdUniquePtr(*MD.getParent());
+  return isStdUniquePtrFunc(MD, 0, "release");
+}
+
+bool isUniquePtrReset(const CXXMethodDecl &MD) {
+  return isStdUniquePtrFunc(MD, 0, "reset");
 }
 
 bool isContainerInvalidationMethod(const CXXMethodDecl &MD) {
@@ -342,6 +351,9 @@ bool isContainerInvalidationMethod(const CXXMethodDecl &MD) {
                                          // Assignment
                                          "replace"};
 
+  static const llvm::StringSet<> SmartPtr = {// Reallocation
+                                             "reset"};
+
   const StringRef ContainerName = getName(*RD);
   // TODO: Consider caching this lookup by CXXMethodDecl pointer if this
   // StringSwitch becomes a performance bottleneck.
@@ -356,6 +368,7 @@ bool isContainerInvalidationMethod(const CXXMethodDecl &MD) {
                  &NodeBased)
           .Cases({"flat_map", "flat_set", "flat_multimap", "flat_multiset"},
                  &Flat)
+          .Cases({"unique_ptr"}, &SmartPtr)
           .Default(nullptr);
 
   if (!InvalidatingMethods)

>From 6ea2cd96726859b7cc8b7563f96bd79f454146f8 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Wed, 29 Apr 2026 18:39:06 +0300
Subject: [PATCH 2/5] refactor

---
 .../LifetimeSafety/LifetimeAnnotations.h      |  4 +--
 .../LifetimeSafety/FactsGenerator.cpp         |  2 +-
 .../LifetimeSafety/LifetimeAnnotations.cpp    | 27 +++++++------------
 3 files changed, 11 insertions(+), 22 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
index 1a4354edbc281..43cfdd08d4559 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
@@ -71,8 +71,6 @@ bool isGslOwnerType(QualType QT);
 // when ownership is manually transferred.
 bool isUniquePtrRelease(const CXXMethodDecl &MD);
 
-bool isUniquePtrReset(const CXXMethodDecl &MD);
-
 // Returns true if the given method invalidates references to container
 // elements (e.g. vector::push_back). Methods that only invalidate iterators
 // but not references (e.g. unordered_map::emplace) are not considered
@@ -80,7 +78,7 @@ bool isUniquePtrReset(const CXXMethodDecl &MD);
 //
 // Invalidation rules are based on:
 // https://en.cppreference.com/w/cpp/container#Iterator_invalidation
-bool isContainerInvalidationMethod(const CXXMethodDecl &MD);
+bool isInvalidationMethod(const CXXMethodDecl &MD);
 
 /// Returns true for standard library callable wrappers (e.g., std::function)
 /// that can propagate the stored lambda's origins.
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index efdb1a1691ae3..2c7cc6b38b3e1 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -771,7 +771,7 @@ void FactsGenerator::handleInvalidatingCall(const Expr *Call,
   if (!MD || !MD->isInstance())
     return;
 
-  if (!isContainerInvalidationMethod(*MD))
+  if (!isInvalidationMethod(*MD))
     return;
   // Heuristics to turn-down false positives.
   auto *DRE = dyn_cast<DeclRefExpr>(Args[0]);
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
index 7b617fd6e3215..e1cc6a098694c 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -272,21 +272,12 @@ static bool isStdUniquePtr(const CXXRecordDecl &RD) {
   return RD.isInStdNamespace() && getName(RD) == "unique_ptr";
 }
 
-static bool isStdUniquePtrFunc(const CXXMethodDecl &MD, unsigned int Num,
-                               StringRef Name) {
-  return MD.getIdentifier() && MD.getName() == Name &&
-         MD.getNumParams() == Num && isStdUniquePtr(*MD.getParent());
-}
-
 bool isUniquePtrRelease(const CXXMethodDecl &MD) {
-  return isStdUniquePtrFunc(MD, 0, "release");
-}
-
-bool isUniquePtrReset(const CXXMethodDecl &MD) {
-  return isStdUniquePtrFunc(MD, 0, "reset");
+  return MD.getIdentifier() && MD.getName() == "release" &&
+         MD.getNumParams() == 0 && isStdUniquePtr(*MD.getParent());
 }
 
-bool isContainerInvalidationMethod(const CXXMethodDecl &MD) {
+bool isInvalidationMethod(const CXXMethodDecl &MD) {
   const CXXRecordDecl *RD = MD.getParent();
   if (!isInStlNamespace(RD))
     return false;
@@ -351,14 +342,14 @@ bool isContainerInvalidationMethod(const CXXMethodDecl &MD) {
                                          // Assignment
                                          "replace"};
 
-  static const llvm::StringSet<> SmartPtr = {// Reallocation
-                                             "reset"};
+  static const llvm::StringSet<> UniquePtr = {// Reallocation
+                                              "reset"};
 
-  const StringRef ContainerName = getName(*RD);
+  const StringRef RecordName = getName(*RD);
   // TODO: Consider caching this lookup by CXXMethodDecl pointer if this
   // StringSwitch becomes a performance bottleneck.
   const llvm::StringSet<> *InvalidatingMethods =
-      llvm::StringSwitch<const llvm::StringSet<> *>(ContainerName)
+      llvm::StringSwitch<const llvm::StringSet<> *>(RecordName)
           .Case("vector", &Vector)
           .Case("basic_string", &String)
           .Case("deque", &Deque)
@@ -368,7 +359,7 @@ bool isContainerInvalidationMethod(const CXXMethodDecl &MD) {
                  &NodeBased)
           .Cases({"flat_map", "flat_set", "flat_multimap", "flat_multiset"},
                  &Flat)
-          .Cases({"unique_ptr"}, &SmartPtr)
+          .Case("unique_ptr", &UniquePtr)
           .Default(nullptr);
 
   if (!InvalidatingMethods)
@@ -384,7 +375,7 @@ bool isContainerInvalidationMethod(const CXXMethodDecl &MD) {
     case OO_Subscript: // operator[] : Invalidation only for
                        // `flat_map` (Insert-or-access).
                        // `map` and `unordered_map` are excluded.
-      return ContainerName == "flat_map";
+      return RecordName == "flat_map";
     default:
       return false;
     }

>From f18a36314c0f16b9b834859c6fb8218b313415f7 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Wed, 29 Apr 2026 19:29:53 +0300
Subject: [PATCH 3/5] add tests

---
 .../warn-lifetime-safety-invalidations.cpp    | 34 +++++++++++++++++++
 1 file changed, 34 insertions(+)

diff --git a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
index 973e095fb68b4..19e2c9299a650 100644
--- a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
@@ -539,3 +539,37 @@ void function_captured_ref_invalidated() {
 }
 
 } // namespace callable_wrappers
+
+namespace unique_ptr_invalidation {
+
+void invalid_after_reset() {
+  std::unique_ptr<int> up(new int);
+  int *p = up.get(); // expected-warning {{object whose reference is captured is later invalidated}}
+  up.reset();        // expected-note {{invalidated here}}
+  (void)*p;          // expected-note {{later used here}}
+}
+
+void invalid_after_move_assign() {
+  std::unique_ptr<int> up(new int);
+  std::unique_ptr<int> other(new int);
+  int *p = up.get();     // expected-warning {{object whose reference is captured is later invalidated}}
+  up = std::move(other); // expected-note {{invalidated here}}
+  (void)*p;              // expected-note {{later used here}}
+}
+
+void invalid_after_null_assign() {
+  std::unique_ptr<int> up(new int);
+  int *p = up.get(); // expected-warning {{object whose reference is captured is later invalidated}}
+  up = nullptr;      // expected-note {{invalidated here}}
+  (void)*p;          // expected-note {{later used here}}
+}
+
+void invalid_after_ternary_reset(bool flag) {
+  std::unique_ptr<int> up(new int);
+  std::unique_ptr<int> other(new int);
+  int *p = flag ? up.get() : other.get(); // expected-warning {{object whose reference is captured is later invalidated}}
+  up.reset();                             // expected-note {{invalidated here}}
+  (void)*p;                               // expected-note {{later used here}}
+}
+
+} // namespace unique_ptr_invalidation

>From 0d2ba3a38934be290388d687581cd625a4b761ff Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Wed, 29 Apr 2026 19:30:07 +0300
Subject: [PATCH 4/5] change inputs file

---
 clang/test/Sema/Inputs/lifetime-analysis.h | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h b/clang/test/Sema/Inputs/lifetime-analysis.h
index 14226c7ff1f13..c7473d2928adf 100644
--- a/clang/test/Sema/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/Inputs/lifetime-analysis.h
@@ -40,6 +40,7 @@ template<typename T, int N>
 T *begin(T (&array)[N]);
 
 using size_t = decltype(sizeof(0));
+using nullptr_t = decltype(nullptr);
 
 template<typename T>
 struct initializer_list {
@@ -199,6 +200,8 @@ struct unique_ptr {
   explicit unique_ptr(T*);
   unique_ptr(unique_ptr<T>&&);
   unique_ptr& operator=(unique_ptr<T>&&);
+  unique_ptr& operator=(std::nullptr_t);
+  void reset();
   ~unique_ptr();
   T* release();
   T &operator*();

>From 497500fc5f62df9849e44f85a68bc9bb20e5b470 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Wed, 29 Apr 2026 19:38:22 +0300
Subject: [PATCH 5/5] update doc comment

---
 .../Analyses/LifetimeSafety/LifetimeAnnotations.h      | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
index 43cfdd08d4559..433239fb385a3 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
@@ -71,12 +71,12 @@ bool isGslOwnerType(QualType QT);
 // when ownership is manually transferred.
 bool isUniquePtrRelease(const CXXMethodDecl &MD);
 
-// Returns true if the given method invalidates references to container
-// elements (e.g. vector::push_back). Methods that only invalidate iterators
-// but not references (e.g. unordered_map::emplace) are not considered
-// invalidating here.
+// 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
+// here.
 //
-// Invalidation rules are based on:
+// Container invalidation rules are based on:
 // https://en.cppreference.com/w/cpp/container#Iterator_invalidation
 bool isInvalidationMethod(const CXXMethodDecl &MD);
 



More information about the cfe-commits mailing list