[clang] [LifetimeSafety] Diagnose invalidated-field and invalidated-global (PR #196680)

2026-05-09 Thread Zeyi Xu via cfe-commits

https://github.com/zeyi2 updated 
https://github.com/llvm/llvm-project/pull/196680

>From 678ef78a052179af827329ef8e794a5dacf87e15 Mon Sep 17 00:00:00 2001
From: Zeyi Xu 
Date: Sat, 9 May 2026 10:39:13 +0800
Subject: [PATCH 1/2] [LifetimeSafety] Diagnose invalidated-field and
 invalidated-global

---
 .../Analyses/LifetimeSafety/LifetimeSafety.h  |   6 +
 .../clang/Basic/DiagnosticSemaKinds.td|   8 +
 clang/lib/Analysis/LifetimeSafety/Checker.cpp |  20 +-
 .../LifetimeSafety/FactsGenerator.cpp |   7 +-
 clang/lib/Sema/SemaLifetimeSafety.h   |  35 +++
 .../warn-lifetime-safety-invalidations.cpp| 215 --
 6 files changed, 266 insertions(+), 25 deletions(-)

diff --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index d20ac87a7c8d9..2332ce36646ed 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -88,6 +88,12 @@ class LifetimeSafetySemaHelper {
   virtual void reportUseAfterInvalidation(const ParmVarDecl *PVD,
   const Expr *UseExpr,
   const Expr *InvalidationExpr) {}
+  virtual void reportInvalidatedField(const Expr *IssueExpr,
+  const FieldDecl *Field,
+  const Expr *InvalidationExpr) {}
+  virtual void reportInvalidatedGlobal(const Expr *IssueExpr,
+   const VarDecl *DanglingGlobal,
+   const Expr *InvalidationExpr) {}
 
   using EscapingTarget =
   llvm::PointerUnion;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index c69b2ce3648f8..07846cd761ee3 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10985,6 +10985,14 @@ def warn_lifetime_safety_invalidation
 : Warning<"%select{object whose reference is captured|parameter}0 is later 
invalidated">,
   InGroup,
   DefaultIgnore;
+def warn_lifetime_safety_invalidated_field
+: Warning<"object whose reference is stored in a field is later 
invalidated">,
+  InGroup,
+  DefaultIgnore;
+def warn_lifetime_safety_invalidated_global
+: Warning<"object whose reference is stored in global or static storage is 
later invalidated">,
+  InGroup,
+  DefaultIgnore;
 
 def warn_lifetime_safety_dangling_field
 : Warning<"address of stack memory escapes to a field">,
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp 
b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 4ae90cf751ec3..fb292caf5c5de 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -259,7 +259,25 @@ class LifetimeChecker {
   MovedExpr, ExpiryLoc);
   } else if (const auto *OEF =
  CausingFact.dyn_cast()) {
-if (const auto *RetEscape = dyn_cast(OEF))
+if (Warning.InvalidatedByExpr) {
+  if (const auto *FieldEscape = dyn_cast(OEF))
+// Field escape later invalidated.
+SemaHelper->reportInvalidatedField(IssueExpr,
+   FieldEscape->getFieldDecl(),
+   Warning.InvalidatedByExpr);
+  else if (const auto *GlobalEscape = dyn_cast(OEF))
+// Global escape later invalidated.
+SemaHelper->reportInvalidatedGlobal(IssueExpr,
+GlobalEscape->getGlobal(),
+Warning.InvalidatedByExpr);
+  else if (isa(OEF))
+// Return escape.
+SemaHelper->reportUseAfterReturn(
+IssueExpr, cast(OEF)->getReturnExpr(),
+MovedExpr, ExpiryLoc);
+  else
+llvm_unreachable("Unhandled OriginEscapesFact type");
+} else if (const auto *RetEscape = dyn_cast(OEF))
   // Return stack address.
   SemaHelper->reportUseAfterReturn(
   IssueExpr, RetEscape->getReturnExpr(), MovedExpr, ExpiryLoc);
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 0a06548d881d1..0e6b032914b05 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -810,9 +810,10 @@ void FactsGenerator::handleInvalidatingCall(const Expr 
*Call,
 
   if (!isInvalidationMethod(*MD))
 return;
-  // Heuristics to turn-down false positives.
-  auto *DRE = dyn_cast(Args[0]);
-  if (!DRE || DRE->getDecl()->getType()->isReferenceType())
+  // Heuristics to turn-down false positives. Skip membe

[clang] [LifetimeSafety] Diagnose invalidated-field and invalidated-global (PR #196680)

2026-05-09 Thread Zeyi Xu via cfe-commits


@@ -810,9 +810,10 @@ void FactsGenerator::handleInvalidatingCall(const Expr 
*Call,
 
   if (!isInvalidationMethod(*MD))
 return;
-  // Heuristics to turn-down false positives.
-  auto *DRE = dyn_cast(Args[0]);
-  if (!DRE || DRE->getDecl()->getType()->isReferenceType())
+  // Heuristics to turn-down false positives. Skip member field expressions for
+  // now. This is not a perfect filter and will still surface some false
+  // positives (e.g. `auto& r = s.v`).
+  if (!isa(Args[0]->IgnoreParenImpCasts()))

zeyi2 wrote:

This is from the reverted PR: https://github.com/llvm/llvm-project/pull/195231

These are technically two separate changes. But to support the new field/global 
diagnostics, we need to make that heuristic slightly less conservative first, 
so I merged these two PRs.

https://github.com/llvm/llvm-project/pull/196680
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Diagnose invalidated-field and invalidated-global (PR #196680)

2026-05-09 Thread via cfe-commits

llvmorg-github-actions[bot] wrote:



@llvm/pr-subscribers-clang-temporal-safety

@llvm/pr-subscribers-clang

Author: Zeyi Xu (zeyi2)


Changes

Teach lifetime safety invalidation diagnostics to handle origins that escape 
through fields or global/static storage before the referenced object is 
invalidated. Previously they were skipped.

Closes https://github.com/llvm/llvm-project/issues/195706
Closes https://github.com/llvm/llvm-project/issues/193044

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


6 Files Affected:

- (modified) 
clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h (+6) 
- (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+8) 
- (modified) clang/lib/Analysis/LifetimeSafety/Checker.cpp (+19-1) 
- (modified) clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp (+4-3) 
- (modified) clang/lib/Sema/SemaLifetimeSafety.h (+35) 
- (modified) clang/test/Sema/warn-lifetime-safety-invalidations.cpp (+204-21) 


``diff
diff --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index d20ac87a7c8d9..2332ce36646ed 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -88,6 +88,12 @@ class LifetimeSafetySemaHelper {
   virtual void reportUseAfterInvalidation(const ParmVarDecl *PVD,
   const Expr *UseExpr,
   const Expr *InvalidationExpr) {}
+  virtual void reportInvalidatedField(const Expr *IssueExpr,
+  const FieldDecl *Field,
+  const Expr *InvalidationExpr) {}
+  virtual void reportInvalidatedGlobal(const Expr *IssueExpr,
+   const VarDecl *DanglingGlobal,
+   const Expr *InvalidationExpr) {}
 
   using EscapingTarget =
   llvm::PointerUnion;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index c69b2ce3648f8..07846cd761ee3 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10985,6 +10985,14 @@ def warn_lifetime_safety_invalidation
 : Warning<"%select{object whose reference is captured|parameter}0 is later 
invalidated">,
   InGroup,
   DefaultIgnore;
+def warn_lifetime_safety_invalidated_field
+: Warning<"object whose reference is stored in a field is later 
invalidated">,
+  InGroup,
+  DefaultIgnore;
+def warn_lifetime_safety_invalidated_global
+: Warning<"object whose reference is stored in global or static storage is 
later invalidated">,
+  InGroup,
+  DefaultIgnore;
 
 def warn_lifetime_safety_dangling_field
 : Warning<"address of stack memory escapes to a field">,
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp 
b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 4ae90cf751ec3..fb292caf5c5de 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -259,7 +259,25 @@ class LifetimeChecker {
   MovedExpr, ExpiryLoc);
   } else if (const auto *OEF =
  CausingFact.dyn_cast()) {
-if (const auto *RetEscape = dyn_cast(OEF))
+if (Warning.InvalidatedByExpr) {
+  if (const auto *FieldEscape = dyn_cast(OEF))
+// Field escape later invalidated.
+SemaHelper->reportInvalidatedField(IssueExpr,
+   FieldEscape->getFieldDecl(),
+   Warning.InvalidatedByExpr);
+  else if (const auto *GlobalEscape = dyn_cast(OEF))
+// Global escape later invalidated.
+SemaHelper->reportInvalidatedGlobal(IssueExpr,
+GlobalEscape->getGlobal(),
+Warning.InvalidatedByExpr);
+  else if (isa(OEF))
+// Return escape.
+SemaHelper->reportUseAfterReturn(
+IssueExpr, cast(OEF)->getReturnExpr(),
+MovedExpr, ExpiryLoc);
+  else
+llvm_unreachable("Unhandled OriginEscapesFact type");
+} else if (const auto *RetEscape = dyn_cast(OEF))
   // Return stack address.
   SemaHelper->reportUseAfterReturn(
   IssueExpr, RetEscape->getReturnExpr(), MovedExpr, ExpiryLoc);
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 0a06548d881d1..0e6b032914b05 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -810,9 +810,10 @@ void FactsGenerator:

[clang] [LifetimeSafety] Diagnose invalidated-field and invalidated-global (PR #196680)

2026-05-09 Thread Zeyi Xu via cfe-commits

https://github.com/zeyi2 ready_for_review 
https://github.com/llvm/llvm-project/pull/196680
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Diagnose invalidated-field and invalidated-global (PR #196680)

2026-05-09 Thread Zeyi Xu via cfe-commits

https://github.com/zeyi2 edited https://github.com/llvm/llvm-project/pull/196680
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Diagnose invalidated-field and invalidated-global (PR #196680)

2026-05-09 Thread Zeyi Xu via cfe-commits

https://github.com/zeyi2 edited https://github.com/llvm/llvm-project/pull/196680
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Diagnose invalidated-field and invalidated-global (PR #196680)

2026-05-09 Thread Zeyi Xu via cfe-commits

https://github.com/zeyi2 created 
https://github.com/llvm/llvm-project/pull/196680

None

>From 678ef78a052179af827329ef8e794a5dacf87e15 Mon Sep 17 00:00:00 2001
From: Zeyi Xu 
Date: Sat, 9 May 2026 10:39:13 +0800
Subject: [PATCH] [LifetimeSafety] Diagnose invalidated-field and
 invalidated-global

---
 .../Analyses/LifetimeSafety/LifetimeSafety.h  |   6 +
 .../clang/Basic/DiagnosticSemaKinds.td|   8 +
 clang/lib/Analysis/LifetimeSafety/Checker.cpp |  20 +-
 .../LifetimeSafety/FactsGenerator.cpp |   7 +-
 clang/lib/Sema/SemaLifetimeSafety.h   |  35 +++
 .../warn-lifetime-safety-invalidations.cpp| 215 --
 6 files changed, 266 insertions(+), 25 deletions(-)

diff --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index d20ac87a7c8d9..2332ce36646ed 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -88,6 +88,12 @@ class LifetimeSafetySemaHelper {
   virtual void reportUseAfterInvalidation(const ParmVarDecl *PVD,
   const Expr *UseExpr,
   const Expr *InvalidationExpr) {}
+  virtual void reportInvalidatedField(const Expr *IssueExpr,
+  const FieldDecl *Field,
+  const Expr *InvalidationExpr) {}
+  virtual void reportInvalidatedGlobal(const Expr *IssueExpr,
+   const VarDecl *DanglingGlobal,
+   const Expr *InvalidationExpr) {}
 
   using EscapingTarget =
   llvm::PointerUnion;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index c69b2ce3648f8..07846cd761ee3 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10985,6 +10985,14 @@ def warn_lifetime_safety_invalidation
 : Warning<"%select{object whose reference is captured|parameter}0 is later 
invalidated">,
   InGroup,
   DefaultIgnore;
+def warn_lifetime_safety_invalidated_field
+: Warning<"object whose reference is stored in a field is later 
invalidated">,
+  InGroup,
+  DefaultIgnore;
+def warn_lifetime_safety_invalidated_global
+: Warning<"object whose reference is stored in global or static storage is 
later invalidated">,
+  InGroup,
+  DefaultIgnore;
 
 def warn_lifetime_safety_dangling_field
 : Warning<"address of stack memory escapes to a field">,
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp 
b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 4ae90cf751ec3..fb292caf5c5de 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -259,7 +259,25 @@ class LifetimeChecker {
   MovedExpr, ExpiryLoc);
   } else if (const auto *OEF =
  CausingFact.dyn_cast()) {
-if (const auto *RetEscape = dyn_cast(OEF))
+if (Warning.InvalidatedByExpr) {
+  if (const auto *FieldEscape = dyn_cast(OEF))
+// Field escape later invalidated.
+SemaHelper->reportInvalidatedField(IssueExpr,
+   FieldEscape->getFieldDecl(),
+   Warning.InvalidatedByExpr);
+  else if (const auto *GlobalEscape = dyn_cast(OEF))
+// Global escape later invalidated.
+SemaHelper->reportInvalidatedGlobal(IssueExpr,
+GlobalEscape->getGlobal(),
+Warning.InvalidatedByExpr);
+  else if (isa(OEF))
+// Return escape.
+SemaHelper->reportUseAfterReturn(
+IssueExpr, cast(OEF)->getReturnExpr(),
+MovedExpr, ExpiryLoc);
+  else
+llvm_unreachable("Unhandled OriginEscapesFact type");
+} else if (const auto *RetEscape = dyn_cast(OEF))
   // Return stack address.
   SemaHelper->reportUseAfterReturn(
   IssueExpr, RetEscape->getReturnExpr(), MovedExpr, ExpiryLoc);
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 0a06548d881d1..0e6b032914b05 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -810,9 +810,10 @@ void FactsGenerator::handleInvalidatingCall(const Expr 
*Call,
 
   if (!isInvalidationMethod(*MD))
 return;
-  // Heuristics to turn-down false positives.
-  auto *DRE = dyn_cast(Args[0]);
-  if (!DRE || DRE->getDecl()->getType()->isReferenceType())
+  // Heuristics to turn-down false positives. Skip mem

[clang] [LifetimeSafety] Diagnose invalidated-field and invalidated-global (PR #196680)

2026-05-09 Thread Zeyi Xu via cfe-commits

https://github.com/zeyi2 edited https://github.com/llvm/llvm-project/pull/196680
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits