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
