[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