[clang] [LifetimeSafety] Handle escape through assignment to global storage (PR #181646)
Abhinav Pradeep via cfe-commits
cfe-commits at lists.llvm.org
Mon Feb 16 04:13:59 PST 2026
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 <abhinav.pradeep at oracle.com>
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");
}
More information about the cfe-commits
mailing list