[clang] [LifetimeSafety] Refactor function handlng invalidating calls (PR #195064)

via cfe-commits cfe-commits at lists.llvm.org
Thu Apr 30 04:47:21 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang-analysis

Author: NeKon69

<details>
<summary>Changes</summary>

Previously `handleInvalidatingCall` only handled member function calls. This patch extends it to also handle free functions, and adds `std::destroy_at` as one such invalidating call.

Comes as a part of completion of #<!-- -->164963.

Assisted-by: GPT-5.4 for writing some of the tests.

---
Full diff: https://github.com/llvm/llvm-project/pull/195064.diff


5 Files Affected:

- (modified) clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h (+2) 
- (modified) clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp (+10-7) 
- (modified) clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp (+9) 
- (modified) clang/test/Sema/Inputs/lifetime-analysis.h (+3) 
- (modified) clang/test/Sema/warn-lifetime-safety-invalidations.cpp (+67) 


``````````diff
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
index 433239fb385a3..ab85272b75c53 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
@@ -80,6 +80,8 @@ bool isUniquePtrRelease(const CXXMethodDecl &MD);
 // https://en.cppreference.com/w/cpp/container#Iterator_invalidation
 bool isInvalidationMethod(const CXXMethodDecl &MD);
 
+// Returns true if the given function is a destructor/deleter (destroy_at).
+bool isDestructionFunc(const FunctionDecl &FD);
 /// Returns true for standard library callable wrappers (e.g., std::function)
 /// that can propagate the stored lambda's origins.
 bool isStdCallableWrapperType(const CXXRecordDecl *RD);
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 2c7cc6b38b3e1..d02039ec7bb74 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -768,18 +768,21 @@ void FactsGenerator::handleInvalidatingCall(const Expr *Call,
                                             const FunctionDecl *FD,
                                             ArrayRef<const Expr *> Args) {
   const auto *MD = dyn_cast<CXXMethodDecl>(FD);
-  if (!MD || !MD->isInstance())
+  const bool IsInvalidatingMethod = MD && isInvalidationMethod(*MD);
+  const bool IsDestruction = isDestructionFunc(*FD);
+  if (!IsInvalidatingMethod && !IsDestruction)
     return;
 
-  if (!isInvalidationMethod(*MD))
-    return;
-  // Heuristics to turn-down false positives.
-  auto *DRE = dyn_cast<DeclRefExpr>(Args[0]);
+  // `destroy_at` get an implicit cast for the object argument, methods already
+  // give us the right shape.
+  const Expr *Target = IsDestruction ? Args[0]->IgnoreImpCasts() : Args[0];
+
+  // Heuristics to turn down false positives.
+  const auto *DRE = dyn_cast<DeclRefExpr>(Target);
   if (!DRE || DRE->getDecl()->getType()->isReferenceType())
     return;
 
-  OriginList *ThisList = getOriginsList(*Args[0]);
-  if (ThisList)
+  if (OriginList *ThisList = getOriginsList(*Args[0]))
     CurrentBlockFacts.push_back(FactMgr.createFact<InvalidateOriginFact>(
         ThisList->getOuterOriginID(), Call));
 }
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
index e1cc6a098694c..2519c09cce525 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -387,6 +387,15 @@ bool isInvalidationMethod(const CXXMethodDecl &MD) {
   return InvalidatingMethods->contains(MD.getName());
 }
 
+bool isDestructionFunc(const FunctionDecl &FD) {
+  if (isa<CXXDestructorDecl>(FD))
+    return true;
+  if (const auto *II = FD.getIdentifier();
+      isInStlNamespace(&FD) && II && II->getName() == "destroy_at")
+    return true;
+  return false;
+}
+
 bool isStdCallableWrapperType(const CXXRecordDecl *RD) {
   if (!RD || !isInStlNamespace(RD))
     return false;
diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h b/clang/test/Sema/Inputs/lifetime-analysis.h
index c7473d2928adf..eaedba372ee5e 100644
--- a/clang/test/Sema/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/Inputs/lifetime-analysis.h
@@ -214,6 +214,9 @@ unique_ptr<T> make_unique(Args&&... args) {
   return unique_ptr<T>(new T(args...));
 }
 
+template <class T>
+void destroy_at(T *);
+
 template<typename T>
 struct shared_ptr {
   shared_ptr();
diff --git a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
index 19e2c9299a650..f21f7309af564 100644
--- a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
@@ -540,6 +540,73 @@ void function_captured_ref_invalidated() {
 
 } // namespace callable_wrappers
 
+namespace manual_destruction {
+
+void explicit_destructor_invalidates_pointer() {
+  std::string s = "42";
+  const char *p = s.data(); // expected-warning {{object whose reference is captured is later invalidated}}
+  s.~basic_string();        // expected-note {{invalidated here}}
+  (void)*p;                 // expected-note {{later used here}}
+}
+
+void pointer_destructor_invalidates_pointer() {
+  char storage[sizeof(std::string)];
+  std::string *obj = new (storage) std::string("42"); // expected-warning {{object whose reference is captured is later invalidated}}
+  const char *p = obj->data();
+  obj->~basic_string();                               // expected-note {{invalidated here}}
+  (void)*p;                                           // expected-note {{later used here}}
+}
+
+void destroy_at_invalidates_pointer() {
+  char storage[sizeof(std::string)];
+  std::string *obj = new (storage) std::string("42"); // expected-warning {{object whose reference is captured is later invalidated}}
+  const char *p = obj->data();
+  std::destroy_at(obj);                               // expected-note {{invalidated here}}
+  (void)*p;                                           // expected-note {{later used here}}
+}
+
+void destroy_at_then_placement_new_rescues_pointer() {
+  char storage[sizeof(std::string)];
+  std::string *obj = new (storage) std::string("42");
+  const char *p = obj->data();
+  std::destroy_at(obj);
+  obj = new (storage) std::string("23");
+  p = obj->data();
+  (void)*p;
+}
+
+// FIXME: False-negative
+void destroy_at_invalidates_array_pointer() {
+  std::string arr[1] = {"42"};
+  std::string (&arr_ref)[1] = arr;
+  const char *p = arr[0].data();
+  std::destroy_at(&arr_ref);
+  (void)*p;
+}
+
+// FIXME: False-negative
+void reference_destructor_invalidates_pointer() {
+  std::string s = "42";
+  std::string &ref = s;
+  const char *p = ref.data();
+  std::destroy_at(&ref);
+  (void)*p;
+}
+
+struct StringOwner {
+  std::string s;
+};
+
+// FIXME: False-negative
+void member_destructor_invalidates_pointer() {
+  StringOwner owner = {"42"};
+  const char *p = owner.s.data();
+  owner.s.~basic_string();
+  (void)*p;
+}
+
+} // namespace manual_destruction
+
 namespace unique_ptr_invalidation {
 
 void invalid_after_reset() {

``````````

</details>


https://github.com/llvm/llvm-project/pull/195064


More information about the cfe-commits mailing list