https://github.com/AbhinavPradeep updated 
https://github.com/llvm/llvm-project/pull/181646

>From 11b0f9865cdf2d5b7358d8e10d3dbeecceeb45af Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <[email protected]>
Date: Mon, 16 Feb 2026 22:10:24 +1000
Subject: [PATCH 1/6] Create and emit GlobalEscapeFact.

---
 .../Analysis/Analyses/LifetimeSafety/Facts.h  | 20 +++++++++++++++++-
 .../Analyses/LifetimeSafety/LifetimeSafety.h  |  4 ++++
 clang/lib/Analysis/LifetimeSafety/Checker.cpp |  3 +++
 clang/lib/Analysis/LifetimeSafety/Facts.cpp   |  8 +++++++
 .../LifetimeSafety/FactsGenerator.cpp         | 21 +++++++++++++++++++
 .../Analysis/LifetimeSafety/LiveOrigins.cpp   |  2 ++
 6 files changed, 57 insertions(+), 1 deletion(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index f9d55991f2e09..f797e93fdd85a 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -14,6 +14,7 @@
 #ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_FACTS_H
 #define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_FACTS_H
 
+#include "clang/AST/Decl.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/Loans.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/Origins.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/Utils.h"
@@ -151,7 +152,7 @@ class OriginEscapesFact : public Fact {
   enum class EscapeKind : uint8_t {
     Return, /// Escapes via return statement.
     Field,  /// Escapes via assignment to a field.
-    // FIXME: Add support for escape to global (dangling global ptr).
+    Global,  /// Escapes via assignment to global storage.
   } EscKind;
 
   static bool classof(const Fact *F) {
@@ -202,6 +203,23 @@ class FieldEscapeFact : public OriginEscapesFact {
             const OriginManager &OM) const override;
 };
 
+class GlobalEscapeFact : public OriginEscapesFact {
+  const VarDecl *VDecl;
+
+public:
+  GlobalEscapeFact(OriginID OID, const VarDecl *VDecl)
+        : OriginEscapesFact(OID, EscapeKind::Global), VDecl(VDecl) {}
+
+  static bool classof(const Fact *F) {
+    return F->getKind() == Kind::OriginEscapes &&
+           static_cast<const OriginEscapesFact *>(F)->getEscapeKind() ==
+               EscapeKind::Global;
+  }
+  const VarDecl *getVarDecl() const { return VDecl; };
+  void dump(llvm::raw_ostream &OS, const LoanManager &,
+            const OriginManager &OM) const override;
+};
+
 class UseFact : public Fact {
   const Expr *UseExpr;
   const OriginList *OList;
diff --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index d7aadf4cf04ca..3f16efcbb1817 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -104,6 +104,10 @@ class LifetimeSafetySemaHelper {
   // Reports misuse of [[clang::noescape]] when parameter escapes through field
   virtual void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape,
                                        const FieldDecl *EscapeField) {}
+  // Reports misuse of [[clang::noescape]] when parameter escapes through 
+  // assignment to a global variable
+  virtual void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape,
+                                       const ValueDecl *EscapeGlobal) {}
 
   // Suggests lifetime bound annotations for implicit this.
   virtual void suggestLifetimeboundToImplicitThis(SuggestionScope Scope,
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp 
b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 78c2a6dba3eb6..3daeba353ca2a 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -270,6 +270,9 @@ class LifetimeChecker {
         else if (const auto *FieldEscape = dyn_cast<FieldEscapeFact>(OEF))
           SemaHelper->reportDanglingField(
               IssueExpr, FieldEscape->getFieldDecl(), MovedExpr, ExpiryLoc);
+        else if (const auto *GlobalEscape = dyn_cast<GlobalEscapeFact>(OEF))
+          // Wire up
+          ;
         else
           llvm_unreachable("Unhandled OriginEscapesFact type");
       } else
diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp 
b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
index c963d9c45fa9d..fca10310824ea 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -8,6 +8,7 @@
 
 #include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
 #include "clang/AST/Decl.h"
+#include "clang/AST/DeclID.h"
 #include "clang/Analysis/Analyses/PostOrderCFGView.h"
 #include "clang/Analysis/FlowSensitive/DataflowWorklist.h"
 #include "llvm/ADT/STLFunctionalExtras.h"
@@ -68,6 +69,13 @@ void FieldEscapeFact::dump(llvm::raw_ostream &OS, const 
LoanManager &,
   OS << ", via Field)\n";
 }
 
+void GlobalEscapeFact::dump(llvm::raw_ostream &OS, const LoanManager &,
+                           const OriginManager &OM) const {
+  OS << "OriginEscapes (";
+  OM.dump(getEscapedOriginID(), OS);
+  OS << ", via Global)\n";
+}
+
 void UseFact::dump(llvm::raw_ostream &OS, const LoanManager &,
                    const OriginManager &OM) const {
   OS << "Use (";
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index f39d677758393..686000897969a 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -9,6 +9,7 @@
 #include <cassert>
 #include <string>
 
+#include "clang/AST/Decl.h"
 #include "clang/AST/DeclCXX.h"
 #include "clang/AST/Expr.h"
 #include "clang/AST/ExprCXX.h"
@@ -325,10 +326,17 @@ void FactsGenerator::handleAssignment(const Expr *LHSExpr,
                                       const Expr *RHSExpr) {
   LHSExpr = LHSExpr->IgnoreParenImpCasts();
   OriginList *LHSList = nullptr;
+  const VarDecl *GlobalLHS = nullptr;
 
   if (const auto *DRE_LHS = dyn_cast<DeclRefExpr>(LHSExpr)) {
     LHSList = getOriginsList(*DRE_LHS);
     assert(LHSList && "LHS is a DRE and should have an origin list");
+    // Check if we are assigning to a global variable
+    if (const VarDecl *VarD = dyn_cast<VarDecl>(DRE_LHS->getDecl())) {
+      if (VarD->hasGlobalStorage()) {
+        GlobalLHS = VarD;
+      };
+    };
   }
   // Handle assignment to member fields (e.g., `this->view = s` or `view = s`).
   // This enables detection of dangling fields when local values escape to
@@ -336,6 +344,12 @@ void FactsGenerator::handleAssignment(const Expr *LHSExpr,
   if (const auto *ME_LHS = dyn_cast<MemberExpr>(LHSExpr)) {
     LHSList = getOriginsList(*ME_LHS);
     assert(LHSList && "LHS is a MemberExpr and should have an origin list");
+    // Check if we are assigning to a static data member
+    if (const VarDecl *VarD = dyn_cast<VarDecl>(ME_LHS->getMemberDecl())) {
+      if (VarD->hasGlobalStorage()) {
+        GlobalLHS = VarD;
+      };
+    };
   }
   if (!LHSList)
     return;
@@ -347,6 +361,13 @@ void FactsGenerator::handleAssignment(const Expr *LHSExpr,
   // assigned.
   RHSList = getRValueOrigins(RHSExpr, RHSList);
 
+  if (GlobalLHS) {
+    for (OriginList *L = RHSList; L != nullptr; L = L->peelOuterOrigin()) {
+      EscapesInCurrentBlock.push_back(FactMgr.createFact<GlobalEscapeFact>(
+            L->getOuterOriginID(), GlobalLHS));
+    }
+  };
+  
   if (const auto *DRE_LHS = dyn_cast<DeclRefExpr>(LHSExpr))
     markUseAsWrite(DRE_LHS);
   // Kill the old loans of the destination origin and flow the new loans
diff --git a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp 
b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
index f210fb4d752d4..f818266fcda88 100644
--- a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
@@ -62,6 +62,8 @@ static SourceLocation GetFactLoc(CausingFactType F) {
       return ReturnEsc->getReturnExpr()->getExprLoc();
     if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(OEF))
       return FieldEsc->getFieldDecl()->getLocation();
+    if (auto *GlobalEsc = dyn_cast<GlobalEscapeFact>(OEF))
+      return GlobalEsc->getVarDecl()->getLocation();
   }
   llvm_unreachable("unhandled causing fact in PointerUnion");
 }

>From 0df84c372a946b4559f0f683cb828a57093371f2 Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <[email protected]>
Date: Wed, 25 Feb 2026 21:30:34 +1000
Subject: [PATCH 2/6] Fixed where the fact is emitted and plugged into the
 warning reporting

---
 .../Analysis/Analyses/LifetimeSafety/Facts.h  | 10 ++++---
 .../Analyses/LifetimeSafety/LifetimeSafety.h  |  9 ++++--
 clang/include/clang/Basic/DiagnosticGroups.td | 18 ++++++++++-
 .../clang/Basic/DiagnosticSemaKinds.td        | 12 ++++++++
 clang/lib/Analysis/LifetimeSafety/Checker.cpp | 12 ++++++--
 clang/lib/Analysis/LifetimeSafety/Facts.cpp   |  2 +-
 .../LifetimeSafety/FactsGenerator.cpp         | 30 ++++++-------------
 .../Analysis/LifetimeSafety/LiveOrigins.cpp   |  2 +-
 clang/lib/Sema/AnalysisBasedWarnings.cpp      | 28 +++++++++++++++++
 .../Sema/warn-lifetime-safety-noescape.cpp    |  5 ++--
 10 files changed, 92 insertions(+), 36 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index f797e93fdd85a..e4fbe07b3e9bd 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -152,7 +152,7 @@ class OriginEscapesFact : public Fact {
   enum class EscapeKind : uint8_t {
     Return, /// Escapes via return statement.
     Field,  /// Escapes via assignment to a field.
-    Global,  /// Escapes via assignment to global storage.
+    Global, /// Escapes via assignment to global storage.
   } EscKind;
 
   static bool classof(const Fact *F) {
@@ -203,19 +203,21 @@ class FieldEscapeFact : public OriginEscapesFact {
             const OriginManager &OM) const override;
 };
 
+/// Represents that an origin escapes via assignment to global storage.
+/// Example: `global_storage = local_var;`
 class GlobalEscapeFact : public OriginEscapesFact {
-  const VarDecl *VDecl;
+  const VarDecl *Global;
 
 public:
   GlobalEscapeFact(OriginID OID, const VarDecl *VDecl)
-        : OriginEscapesFact(OID, EscapeKind::Global), VDecl(VDecl) {}
+      : OriginEscapesFact(OID, EscapeKind::Global), Global(VDecl) {}
 
   static bool classof(const Fact *F) {
     return F->getKind() == Kind::OriginEscapes &&
            static_cast<const OriginEscapesFact *>(F)->getEscapeKind() ==
                EscapeKind::Global;
   }
-  const VarDecl *getVarDecl() const { return VDecl; };
+  const VarDecl *getGlobal() const { return Global; };
   void dump(llvm::raw_ostream &OS, const LoanManager &,
             const OriginManager &OM) const override;
 };
diff --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index 3f16efcbb1817..2bcd0ddd7a16c 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -84,6 +84,11 @@ class LifetimeSafetySemaHelper {
                                    const Expr *MovedExpr,
                                    SourceLocation ExpiryLoc) {}
 
+  virtual void reportDanglingGlobal(const Expr *IssueExpr,
+                                    const VarDecl *DanglingGlobal,
+                                    const Expr *MovedExpr,
+                                    SourceLocation ExpiryLoc) {}
+
   // Reports when a reference/iterator is used after the container operation
   // that invalidated it.
   virtual void reportUseAfterInvalidation(const Expr *IssueExpr,
@@ -104,10 +109,10 @@ class LifetimeSafetySemaHelper {
   // Reports misuse of [[clang::noescape]] when parameter escapes through field
   virtual void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape,
                                        const FieldDecl *EscapeField) {}
-  // Reports misuse of [[clang::noescape]] when parameter escapes through 
+  // Reports misuse of [[clang::noescape]] when parameter escapes through
   // assignment to a global variable
   virtual void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape,
-                                       const ValueDecl *EscapeGlobal) {}
+                                       const VarDecl *EscapeGlobal) {}
 
   // Suggests lifetime bound annotations for implicit this.
   virtual void suggestLifetimeboundToImplicitThis(SuggestionScope Scope,
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index 7df83d2a4011f..72a50f4141e27 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -595,6 +595,19 @@ This may contain false-positives, e.g. when the borrowed 
storage is potentially
   }];
 }
 
+def LifetimeSafetyDanglingGlobal : 
DiagGroup<"lifetime-safety-dangling-global"> {
+  code Documentation = [{
+Warning to detect dangling global references.
+  }];
+}
+
+def LifetimeSafetyDanglingGlobalMoved : 
DiagGroup<"lifetime-safety-dangling-global-moved"> {
+  code Documentation = [{
+Warning to detect dangling global references.
+This may contain false-positives, e.g. when the borrowed storage is 
potentially moved and is not destroyed at function exit.
+  }];
+}
+
 def LifetimeSafetyInvalidation : DiagGroup<"lifetime-safety-invalidation"> {
   code Documentation = [{
 Warning to detect invalidation of references.
@@ -604,11 +617,14 @@ Warning to detect invalidation of references.
 def LifetimeSafetyPermissive : DiagGroup<"lifetime-safety-permissive",
                                          [LifetimeSafetyUseAfterScope,
                                          LifetimeSafetyReturnStackAddr,
-                                         LifetimeSafetyDanglingField]>;
+                                         LifetimeSafetyDanglingField,
+                                         LifetimeSafetyDanglingGlobal]>;
+
 def LifetimeSafetyStrict : DiagGroup<"lifetime-safety-strict",
                                     [LifetimeSafetyUseAfterScopeMoved,
                                     LifetimeSafetyReturnStackAddrMoved,
                                     LifetimeSafetyDanglingFieldMoved,
+                                    LifetimeSafetyDanglingGlobal,
                                     LifetimeSafetyInvalidation]>;
 
 def LifetimeSafety : DiagGroup<"lifetime-safety",
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 68016ec4d58a3..2d791bf3ce447 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10931,6 +10931,16 @@ def warn_lifetime_safety_dangling_field_moved
       "Consider moving first and then aliasing later to resolve the issue">,
       InGroup<LifetimeSafetyDanglingFieldMoved>,
       DefaultIgnore;
+def warn_lifetime_safety_dangling_global
+    : Warning<"address of stack memory escapes to a global">,
+      InGroup<LifetimeSafetyDanglingGlobal>,
+      DefaultIgnore;
+def warn_lifetime_safety_dangling_global_moved
+    : Warning<"address of stack memory escapes to a global. "
+      "This could be a false positive as the storage may have been moved. "
+      "Consider moving first and then aliasing later to resolve the issue">,
+      InGroup<LifetimeSafetyDanglingGlobalMoved>,
+      DefaultIgnore;
 
 def note_lifetime_safety_used_here : Note<"later used here">;
 def note_lifetime_safety_invalidated_here : Note<"invalidated here">;
@@ -10938,7 +10948,9 @@ def note_lifetime_safety_destroyed_here : 
Note<"destroyed here">;
 def note_lifetime_safety_returned_here : Note<"returned here">;
 def note_lifetime_safety_moved_here : Note<"potentially moved here">;
 def note_lifetime_safety_dangling_field_here: Note<"this field dangles">;
+def note_lifetime_safety_dangling_global_here: Note<"this global dangles">;
 def note_lifetime_safety_escapes_to_field_here: Note<"escapes to this field">;
+def note_lifetime_safety_escapes_to_global_here: Note<"escapes to this 
global">;
 
 def warn_lifetime_safety_intra_tu_param_suggestion
     : Warning<"parameter in intra-TU function should be marked "
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp 
b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 3daeba353ca2a..7399fa1c2dbd2 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -25,6 +25,7 @@
 #include "clang/Basic/SourceManager.h"
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/DenseSet.h"
+#include "llvm/Support/Casting.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/TimeProfiler.h"
 
@@ -55,7 +56,8 @@ struct PendingWarning {
 
 using AnnotationTarget =
     llvm::PointerUnion<const ParmVarDecl *, const CXXMethodDecl *>;
-using EscapingTarget = llvm::PointerUnion<const Expr *, const FieldDecl *>;
+using EscapingTarget =
+    llvm::PointerUnion<const Expr *, const FieldDecl *, const VarDecl *>;
 
 class LifetimeChecker {
 private:
@@ -109,6 +111,8 @@ class LifetimeChecker {
           NoescapeWarningsMap.try_emplace(PVD, ReturnEsc->getReturnExpr());
         if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(OEF))
           NoescapeWarningsMap.try_emplace(PVD, FieldEsc->getFieldDecl());
+        if (auto *GlobalEsc = dyn_cast<GlobalEscapeFact>(OEF))
+          NoescapeWarningsMap.try_emplace(PVD, GlobalEsc->getGlobal());
         return;
       }
       // Suggest lifetimebound for parameter escaping through return.
@@ -271,8 +275,8 @@ class LifetimeChecker {
           SemaHelper->reportDanglingField(
               IssueExpr, FieldEscape->getFieldDecl(), MovedExpr, ExpiryLoc);
         else if (const auto *GlobalEscape = dyn_cast<GlobalEscapeFact>(OEF))
-          // Wire up
-          ;
+          SemaHelper->reportDanglingGlobal(IssueExpr, 
GlobalEscape->getGlobal(),
+                                           MovedExpr, ExpiryLoc);
         else
           llvm_unreachable("Unhandled OriginEscapesFact type");
       } else
@@ -345,6 +349,8 @@ class LifetimeChecker {
         SemaHelper->reportNoescapeViolation(PVD, E);
       else if (const auto *FD = EscapeTarget.dyn_cast<const FieldDecl *>())
         SemaHelper->reportNoescapeViolation(PVD, FD);
+      else if (const auto *G = EscapeTarget.dyn_cast<const VarDecl *>())
+        SemaHelper->reportNoescapeViolation(PVD, G);
       else
         llvm_unreachable("Unhandled EscapingTarget type");
     }
diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp 
b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
index fca10310824ea..4ffc8b4195949 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -70,7 +70,7 @@ void FieldEscapeFact::dump(llvm::raw_ostream &OS, const 
LoanManager &,
 }
 
 void GlobalEscapeFact::dump(llvm::raw_ostream &OS, const LoanManager &,
-                           const OriginManager &OM) const {
+                            const OriginManager &OM) const {
   OS << "OriginEscapes (";
   OM.dump(getEscapedOriginID(), OS);
   OS << ", via Global)\n";
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 686000897969a..8238cf69edfcd 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -326,17 +326,10 @@ void FactsGenerator::handleAssignment(const Expr *LHSExpr,
                                       const Expr *RHSExpr) {
   LHSExpr = LHSExpr->IgnoreParenImpCasts();
   OriginList *LHSList = nullptr;
-  const VarDecl *GlobalLHS = nullptr;
 
   if (const auto *DRE_LHS = dyn_cast<DeclRefExpr>(LHSExpr)) {
     LHSList = getOriginsList(*DRE_LHS);
     assert(LHSList && "LHS is a DRE and should have an origin list");
-    // Check if we are assigning to a global variable
-    if (const VarDecl *VarD = dyn_cast<VarDecl>(DRE_LHS->getDecl())) {
-      if (VarD->hasGlobalStorage()) {
-        GlobalLHS = VarD;
-      };
-    };
   }
   // Handle assignment to member fields (e.g., `this->view = s` or `view = s`).
   // This enables detection of dangling fields when local values escape to
@@ -344,12 +337,6 @@ void FactsGenerator::handleAssignment(const Expr *LHSExpr,
   if (const auto *ME_LHS = dyn_cast<MemberExpr>(LHSExpr)) {
     LHSList = getOriginsList(*ME_LHS);
     assert(LHSList && "LHS is a MemberExpr and should have an origin list");
-    // Check if we are assigning to a static data member
-    if (const VarDecl *VarD = dyn_cast<VarDecl>(ME_LHS->getMemberDecl())) {
-      if (VarD->hasGlobalStorage()) {
-        GlobalLHS = VarD;
-      };
-    };
   }
   if (!LHSList)
     return;
@@ -361,13 +348,6 @@ void FactsGenerator::handleAssignment(const Expr *LHSExpr,
   // assigned.
   RHSList = getRValueOrigins(RHSExpr, RHSList);
 
-  if (GlobalLHS) {
-    for (OriginList *L = RHSList; L != nullptr; L = L->peelOuterOrigin()) {
-      EscapesInCurrentBlock.push_back(FactMgr.createFact<GlobalEscapeFact>(
-            L->getOuterOriginID(), GlobalLHS));
-    }
-  };
-  
   if (const auto *DRE_LHS = dyn_cast<DeclRefExpr>(LHSExpr))
     markUseAsWrite(DRE_LHS);
   // Kill the old loans of the destination origin and flow the new loans
@@ -490,11 +470,19 @@ void FactsGenerator::handleFullExprCleanup(
 }
 
 void FactsGenerator::handleExitBlock() {
-  // Creates FieldEscapeFacts for all field origins that remain live at exit.
   for (const Origin &O : FactMgr.getOriginMgr().getOrigins())
     if (auto *FD = dyn_cast_if_present<FieldDecl>(O.getDecl()))
+      // Create FieldEscapeFacts for all field origins that remain live at 
exit.
       EscapesInCurrentBlock.push_back(
           FactMgr.createFact<FieldEscapeFact>(O.ID, FD));
+    else if (auto *VD = dyn_cast_if_present<VarDecl>(O.getDecl())) {
+      // Create GlobalEscapeFacts for all origins with global-storage that
+      // remain live at exit.
+      if (VD->hasGlobalStorage()) {
+        EscapesInCurrentBlock.push_back(
+            FactMgr.createFact<GlobalEscapeFact>(O.ID, VD));
+      }
+    }
 }
 
 void FactsGenerator::handleGSLPointerConstruction(const CXXConstructExpr *CCE) 
{
diff --git a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp 
b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
index f818266fcda88..fe20d779669c1 100644
--- a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
@@ -63,7 +63,7 @@ static SourceLocation GetFactLoc(CausingFactType F) {
     if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(OEF))
       return FieldEsc->getFieldDecl()->getLocation();
     if (auto *GlobalEsc = dyn_cast<GlobalEscapeFact>(OEF))
-      return GlobalEsc->getVarDecl()->getLocation();
+      return GlobalEsc->getGlobal()->getLocation();
   }
   llvm_unreachable("unhandled causing fact in PointerUnion");
 }
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp 
b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 20c41096501fb..9769a6d678dcd 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2888,6 +2888,7 @@ class LifetimeSafetySemaHelperImpl : public 
LifetimeSafetySemaHelper {
     S.Diag(ReturnExpr->getExprLoc(), diag::note_lifetime_safety_returned_here)
         << ReturnExpr->getSourceRange();
   }
+
   void reportDanglingField(const Expr *IssueExpr,
                            const FieldDecl *DanglingField,
                            const Expr *MovedExpr,
@@ -2904,6 +2905,22 @@ class LifetimeSafetySemaHelperImpl : public 
LifetimeSafetySemaHelper {
         << DanglingField->getEndLoc();
   }
 
+  void reportDanglingGlobal(const Expr *IssueExpr,
+                            const VarDecl *DanglingGlobal,
+                            const Expr *MovedExpr,
+                            SourceLocation ExpiryLoc) override {
+    S.Diag(IssueExpr->getExprLoc(),
+           MovedExpr ? diag::warn_lifetime_safety_dangling_global_moved
+                     : diag::warn_lifetime_safety_dangling_global)
+        << IssueExpr->getSourceRange();
+    if (MovedExpr)
+      S.Diag(MovedExpr->getExprLoc(), diag::note_lifetime_safety_moved_here)
+          << MovedExpr->getSourceRange();
+    S.Diag(DanglingGlobal->getLocation(),
+           diag::note_lifetime_safety_dangling_global_here)
+        << DanglingGlobal->getEndLoc();
+  }
+
   void reportUseAfterInvalidation(const Expr *IssueExpr, const Expr *UseExpr,
                                   const Expr *InvalidationExpr) override {
     S.Diag(IssueExpr->getExprLoc(), diag::warn_lifetime_safety_invalidation)
@@ -3005,6 +3022,17 @@ class LifetimeSafetySemaHelperImpl : public 
LifetimeSafetySemaHelper {
         << EscapeField->getEndLoc();
   }
 
+  void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape,
+                               const VarDecl *EscapeGlobal) override {
+    S.Diag(ParmWithNoescape->getBeginLoc(),
+           diag::warn_lifetime_safety_noescape_escapes)
+        << ParmWithNoescape->getSourceRange();
+
+    S.Diag(EscapeGlobal->getLocation(),
+           diag::note_lifetime_safety_escapes_to_global_here)
+        << EscapeGlobal->getEndLoc();
+  }
+
   void addLifetimeBoundToImplicitThis(const CXXMethodDecl *MD) override {
     S.addLifetimeBoundToImplicitThis(const_cast<CXXMethodDecl *>(MD));
   }
diff --git a/clang/test/Sema/warn-lifetime-safety-noescape.cpp 
b/clang/test/Sema/warn-lifetime-safety-noescape.cpp
index ee661add0acc8..36542a2e15977 100644
--- a/clang/test/Sema/warn-lifetime-safety-noescape.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-noescape.cpp
@@ -113,10 +113,9 @@ View escape_through_unannotated_call(const MyObj& in 
[[clang::noescape]]) { // e
   return no_annotation_identity(in); // expected-note {{returned here}}
 }
 
-View global_view;
+View global_view; // expected-note {{escapes to this global}}
 
-// FIXME: Escaping through a global variable is not detected.
-void escape_through_global_var(const MyObj& in [[clang::noescape]]) {
+void escape_through_global_var(const MyObj& in [[clang::noescape]]) { // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes}}
   global_view = in;
 }
 

>From 5a555e880829f8e829298a47086c5b197e8ee339 Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <[email protected]>
Date: Fri, 27 Feb 2026 16:56:06 +1000
Subject: [PATCH 3/6] Added tests and rephrased reporting a bit

---
 .../clang/Basic/DiagnosticSemaKinds.td        |  2 +-
 .../Sema/warn-lifetime-safety-noescape.cpp    | 22 ++++++++++++++++++-
 2 files changed, 22 insertions(+), 2 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 2d791bf3ce447..b3c44eb1a120a 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10950,7 +10950,7 @@ def note_lifetime_safety_moved_here : Note<"potentially 
moved here">;
 def note_lifetime_safety_dangling_field_here: Note<"this field dangles">;
 def note_lifetime_safety_dangling_global_here: Note<"this global dangles">;
 def note_lifetime_safety_escapes_to_field_here: Note<"escapes to this field">;
-def note_lifetime_safety_escapes_to_global_here: Note<"escapes to this 
global">;
+def note_lifetime_safety_escapes_to_global_here: Note<"escapes to this global 
storage">;
 
 def warn_lifetime_safety_intra_tu_param_suggestion
     : Warning<"parameter in intra-TU function should be marked "
diff --git a/clang/test/Sema/warn-lifetime-safety-noescape.cpp 
b/clang/test/Sema/warn-lifetime-safety-noescape.cpp
index 36542a2e15977..03f4c24a0956f 100644
--- a/clang/test/Sema/warn-lifetime-safety-noescape.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-noescape.cpp
@@ -113,11 +113,31 @@ View escape_through_unannotated_call(const MyObj& in 
[[clang::noescape]]) { // e
   return no_annotation_identity(in); // expected-note {{returned here}}
 }
 
-View global_view; // expected-note {{escapes to this global}}
+View global_view; // expected-note {{escapes to this global storage}}
 
 void escape_through_global_var(const MyObj& in [[clang::noescape]]) { // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes}}
   global_view = in;
 }
+struct ObjWithStaticField {
+  static int *static_field; // expected-note {{escapes to this global storage}}
+}; 
+  
+void escape_to_static_data_member(int *data [[clang::noescape]]) { // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes}}
+  ObjWithStaticField::static_field = data;
+}
+
+
+
+void escape_through_static_local(int *data [[clang::noescape]]) { // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes}}
+  static int *static_local; // expected-note {{escapes to this global storage}}
+  static_local = data;
+}
+
+thread_local int *thread_local_storage; // expected-note {{escapes to this 
global storage}}
+
+void escape_through_thread_local(int *data [[clang::noescape]]) { // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes}}
+  thread_local_storage = data;
+}
 
 struct ObjConsumer {
   void escape_through_member(const MyObj& in [[clang::noescape]]) { // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes}}

>From 0a8cbb523b9c04218f92750f40b58ba59eb7d28c Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <[email protected]>
Date: Tue, 3 Mar 2026 14:30:04 +1000
Subject: [PATCH 4/6] Added more dangling global tests.

---
 .../warn-lifetime-safety-dangling-global.cpp  | 44 +++++++++++++++++++
 1 file changed, 44 insertions(+)
 create mode 100644 clang/test/Sema/warn-lifetime-safety-dangling-global.cpp

diff --git a/clang/test/Sema/warn-lifetime-safety-dangling-global.cpp 
b/clang/test/Sema/warn-lifetime-safety-dangling-global.cpp
new file mode 100644
index 0000000000000..b40e7d23f90b9
--- /dev/null
+++ b/clang/test/Sema/warn-lifetime-safety-dangling-global.cpp
@@ -0,0 +1,44 @@
+// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -verify %s
+
+int *global; // expected-note {{this global dangles}}
+int *global_backup; // expected-note {{this global dangles}}
+
+struct ObjWithStaticField {
+  static int *static_field; // expected-note {{this global dangles}}
+}; 
+
+void save_global() {
+  global_backup = global;
+}
+
+// Here, by action of save_global, we have that global_backup points to stack 
memory. This is currently not caught.
+void invoke_function_with_side_effects() {
+  int local;
+  global = &local;
+  save_global(); 
+  global = nullptr;
+} 
+
+// We can however catch the inlined one of course!
+void inlined() {
+  int local;
+  global = &local; // expected-warning {{address of stack memory escapes to a 
global}}
+  global_backup = global; 
+  global = nullptr;
+}
+
+void store_local_in_global() {
+  int local;
+  global = &local; // expected-warning {{address of stack memory escapes to a 
global}}
+}
+
+void store_then_clear() {
+  int local;
+  global = &local;
+  global = nullptr;
+}
+
+void dangling_static_field() {
+  int local;
+  ObjWithStaticField::static_field = &local; // expected-warning {{address of 
stack memory escapes to a global}}
+}
\ No newline at end of file

>From 95e39b77a76e608fba9a327ada9ea053aaec2d4f Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <[email protected]>
Date: Thu, 5 Mar 2026 21:33:45 +1000
Subject: [PATCH 5/6] Added FIXME and improved reporting.

---
 .../clang/Basic/DiagnosticSemaKinds.td        |  6 +++--
 clang/lib/Sema/AnalysisBasedWarnings.cpp      | 23 +++++++++++++------
 .../warn-lifetime-safety-dangling-global.cpp  |  8 +++----
 .../Sema/warn-lifetime-safety-noescape.cpp    |  7 ++++--
 4 files changed, 29 insertions(+), 15 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index b3c44eb1a120a..f866c8915e759 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10932,11 +10932,11 @@ def warn_lifetime_safety_dangling_field_moved
       InGroup<LifetimeSafetyDanglingFieldMoved>,
       DefaultIgnore;
 def warn_lifetime_safety_dangling_global
-    : Warning<"address of stack memory escapes to a global">,
+    : Warning<"address of stack memory escapes to global or static storage">,
       InGroup<LifetimeSafetyDanglingGlobal>,
       DefaultIgnore;
 def warn_lifetime_safety_dangling_global_moved
-    : Warning<"address of stack memory escapes to a global. "
+    : Warning<"address of stack memory escapes to global or static storage. "
       "This could be a false positive as the storage may have been moved. "
       "Consider moving first and then aliasing later to resolve the issue">,
       InGroup<LifetimeSafetyDanglingGlobalMoved>,
@@ -10949,8 +10949,10 @@ def note_lifetime_safety_returned_here : 
Note<"returned here">;
 def note_lifetime_safety_moved_here : Note<"potentially moved here">;
 def note_lifetime_safety_dangling_field_here: Note<"this field dangles">;
 def note_lifetime_safety_dangling_global_here: Note<"this global dangles">;
+def note_lifetime_safety_dangling_static_here: Note<"this static storage 
dangles">;
 def note_lifetime_safety_escapes_to_field_here: Note<"escapes to this field">;
 def note_lifetime_safety_escapes_to_global_here: Note<"escapes to this global 
storage">;
+def note_lifetime_safety_escapes_to_static_storage_here: Note<"escapes to this 
static storage">;
 
 def warn_lifetime_safety_intra_tu_param_suggestion
     : Warning<"parameter in intra-TU function should be marked "
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp 
b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 9769a6d678dcd..daa31074ab25b 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2916,9 +2916,14 @@ class LifetimeSafetySemaHelperImpl : public 
LifetimeSafetySemaHelper {
     if (MovedExpr)
       S.Diag(MovedExpr->getExprLoc(), diag::note_lifetime_safety_moved_here)
           << MovedExpr->getSourceRange();
-    S.Diag(DanglingGlobal->getLocation(),
-           diag::note_lifetime_safety_dangling_global_here)
-        << DanglingGlobal->getEndLoc();
+    if (DanglingGlobal->isStaticLocal() || 
DanglingGlobal->isStaticDataMember())
+      S.Diag(DanglingGlobal->getLocation(),
+             diag::note_lifetime_safety_dangling_static_here)
+          << DanglingGlobal->getEndLoc();
+    else
+      S.Diag(DanglingGlobal->getLocation(),
+             diag::note_lifetime_safety_dangling_global_here)
+          << DanglingGlobal->getEndLoc();
   }
 
   void reportUseAfterInvalidation(const Expr *IssueExpr, const Expr *UseExpr,
@@ -3027,10 +3032,14 @@ class LifetimeSafetySemaHelperImpl : public 
LifetimeSafetySemaHelper {
     S.Diag(ParmWithNoescape->getBeginLoc(),
            diag::warn_lifetime_safety_noescape_escapes)
         << ParmWithNoescape->getSourceRange();
-
-    S.Diag(EscapeGlobal->getLocation(),
-           diag::note_lifetime_safety_escapes_to_global_here)
-        << EscapeGlobal->getEndLoc();
+    if (EscapeGlobal->isStaticLocal() || EscapeGlobal->isStaticDataMember())
+      S.Diag(EscapeGlobal->getLocation(),
+             diag::note_lifetime_safety_escapes_to_static_storage_here)
+          << EscapeGlobal->getEndLoc();
+    else
+      S.Diag(EscapeGlobal->getLocation(),
+             diag::note_lifetime_safety_escapes_to_global_here)
+          << EscapeGlobal->getEndLoc();
   }
 
   void addLifetimeBoundToImplicitThis(const CXXMethodDecl *MD) override {
diff --git a/clang/test/Sema/warn-lifetime-safety-dangling-global.cpp 
b/clang/test/Sema/warn-lifetime-safety-dangling-global.cpp
index b40e7d23f90b9..ac40c7df5a8f5 100644
--- a/clang/test/Sema/warn-lifetime-safety-dangling-global.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-dangling-global.cpp
@@ -4,7 +4,7 @@ int *global; // expected-note {{this global dangles}}
 int *global_backup; // expected-note {{this global dangles}}
 
 struct ObjWithStaticField {
-  static int *static_field; // expected-note {{this global dangles}}
+  static int *static_field; // expected-note {{this static storage dangles}}
 }; 
 
 void save_global() {
@@ -22,14 +22,14 @@ void invoke_function_with_side_effects() {
 // We can however catch the inlined one of course!
 void inlined() {
   int local;
-  global = &local; // expected-warning {{address of stack memory escapes to a 
global}}
+  global = &local; // expected-warning {{address of stack memory escapes to 
global or static storage}}
   global_backup = global; 
   global = nullptr;
 }
 
 void store_local_in_global() {
   int local;
-  global = &local; // expected-warning {{address of stack memory escapes to a 
global}}
+  global = &local; // expected-warning {{address of stack memory escapes to 
global or static storage}}
 }
 
 void store_then_clear() {
@@ -40,5 +40,5 @@ void store_then_clear() {
 
 void dangling_static_field() {
   int local;
-  ObjWithStaticField::static_field = &local; // expected-warning {{address of 
stack memory escapes to a global}}
+  ObjWithStaticField::static_field = &local; // expected-warning {{address of 
stack memory escapes to global or static storage}}
 }
\ No newline at end of file
diff --git a/clang/test/Sema/warn-lifetime-safety-noescape.cpp 
b/clang/test/Sema/warn-lifetime-safety-noescape.cpp
index 03f4c24a0956f..f233ec546faa5 100644
--- a/clang/test/Sema/warn-lifetime-safety-noescape.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-noescape.cpp
@@ -119,7 +119,7 @@ void escape_through_global_var(const MyObj& in 
[[clang::noescape]]) { // expecte
   global_view = in;
 }
 struct ObjWithStaticField {
-  static int *static_field; // expected-note {{escapes to this global storage}}
+  static int *static_field; // expected-note {{escapes to this static storage}}
 }; 
   
 void escape_to_static_data_member(int *data [[clang::noescape]]) { // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes}}
@@ -129,13 +129,16 @@ void escape_to_static_data_member(int *data 
[[clang::noescape]]) { // expected-w
 
 
 void escape_through_static_local(int *data [[clang::noescape]]) { // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes}}
-  static int *static_local; // expected-note {{escapes to this global storage}}
+  static int *static_local; // expected-note {{escapes to this static storage}}
   static_local = data;
 }
 
 thread_local int *thread_local_storage; // expected-note {{escapes to this 
global storage}}
 
 void escape_through_thread_local(int *data [[clang::noescape]]) { // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes}}
+  // FIXME: We might want to report whenever anything derived from a 
+  // noescape parameter is assigned to a global, as this is likely 
+  // undesired behavior in most cases.
   thread_local_storage = data;
 }
 

>From f89a641888141943b930c57ede0310de91fb2bf3 Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <[email protected]>
Date: Thu, 5 Mar 2026 21:57:18 +1000
Subject: [PATCH 6/6] Fixed comment

---
 clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index e4fbe07b3e9bd..21669bba7c84b 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -203,7 +203,7 @@ class FieldEscapeFact : public OriginEscapesFact {
             const OriginManager &OM) const override;
 };
 
-/// Represents that an origin escapes via assignment to global storage.
+/// Represents that an origin escapes via assignment to global or static 
storage.
 /// Example: `global_storage = local_var;`
 class GlobalEscapeFact : public OriginEscapesFact {
   const VarDecl *Global;

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to