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

**Draft PR**. Will fail currently as I have not wired it in. So far:
1. Created `GlobalEscapeFact` as a subclass of `OriginEscapesFact`
2. Emit this fact when, in `FactsGenerator::handleAssignment`, we are assigning 
to a global variable or a static data member.

>From c7565677556963ff4a33028a2b341ef1e5f727e9 Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <[email protected]>
Date: Mon, 16 Feb 2026 22:10:24 +1000
Subject: [PATCH] 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 6148f86091110..20b89d5e1f54f 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -95,6 +95,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 b69f69ddbae34..6b0b1a10a6371 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"
@@ -332,10 +333,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
@@ -343,6 +351,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;
@@ -354,6 +368,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");
 }

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

Reply via email to