https://github.com/Xazax-hun updated https://github.com/llvm/llvm-project/pull/206458
From 30bef5ed27c8fa9ba2b22363852a70e7897b95fa Mon Sep 17 00:00:00 2001 From: Gabor Horvath <[email protected]> Date: Mon, 29 Jun 2026 11:43:49 +0100 Subject: [PATCH 1/3] [CallGraph] Collect callables from global variable initializers CallGraph::TraverseStmt is a no-op, so declaration traversal never descends into a variable initializer. A lambda or block defined in a global-storage variable's initializer was therefore never added to the graph. TU-end lifetime safety analysis walks the call graph to find functions to analyze, so such a lambda was silently skipped and a real lifetime bug in it went unreported, while the default per-function mode caught it. Walk the initializer of every global-storage variable during declaration visitation and add any callables defined within it. The hasGlobalStorage guard excludes parameter default arguments, which CGBuilder already handles at call sites. Assisted-by: Claude Opus 4.8 --- clang/include/clang/Analysis/CallGraph.h | 11 +++++++ clang/lib/Analysis/CallGraph.cpp | 14 +++++++++ clang/test/Sema/LifetimeSafety/safety.cpp | 38 +++++++++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/clang/include/clang/Analysis/CallGraph.h b/clang/include/clang/Analysis/CallGraph.h index f80790902e6b1..d7cb9bf74537f 100644 --- a/clang/include/clang/Analysis/CallGraph.h +++ b/clang/include/clang/Analysis/CallGraph.h @@ -135,12 +135,23 @@ class CallGraph : public DynamicRecursiveASTVisitor { return true; } + /// Part of recursive declaration visitation. A global-storage variable's + /// initializer can define callables (lambdas, blocks) no function body + /// reaches; TraverseStmt is a no-op, so collect them here. + bool VisitVarDecl(VarDecl *VD) override { + addNodesForVarInit(VD); + return true; + } + // We are only collecting the declarations, so do not step into the bodies. bool TraverseStmt(Stmt *S) override { return true; } private: /// Add the given declaration to the call graph. void addNodeForDecl(Decl *D, bool IsGlobal); + + /// Walk a variable's initializer to add any callables defined within it. + void addNodesForVarInit(VarDecl *VD); }; class CallGraphNode { diff --git a/clang/lib/Analysis/CallGraph.cpp b/clang/lib/Analysis/CallGraph.cpp index 26646cd6b6502..dd4074a10beb6 100644 --- a/clang/lib/Analysis/CallGraph.cpp +++ b/clang/lib/Analysis/CallGraph.cpp @@ -196,6 +196,20 @@ void CallGraph::addNodeForDecl(Decl* D, bool IsGlobal) { } } +void CallGraph::addNodesForVarInit(VarDecl *VD) { + // Only variables with static or thread storage duration need this: their + // initializers run with no function caller and are not reached by any body + // walk, so a lambda or block defined in one would be missed. Local variables + // are covered by the enclosing body walk, and parameter default arguments by + // CGBuilder at call sites. Attach discovered calls to the root. + if (!VD->hasGlobalStorage()) + return; + if (Expr *Init = VD->getInit()) { + CGBuilder builder(this, Root); + builder.Visit(Init); + } +} + CallGraphNode *CallGraph::getNode(const Decl *F) const { FunctionMapTy::const_iterator I = FunctionMap.find(F); if (I == FunctionMap.end()) return nullptr; diff --git a/clang/test/Sema/LifetimeSafety/safety.cpp b/clang/test/Sema/LifetimeSafety/safety.cpp index 2cbf651eb46b5..57369a95d0e82 100644 --- a/clang/test/Sema/LifetimeSafety/safety.cpp +++ b/clang/test/Sema/LifetimeSafety/safety.cpp @@ -2390,6 +2390,44 @@ auto capture_multilevel_pointer() { } } // namespace lambda_captures +namespace global_init_lambda { +// A lambda defined in a global-storage variable initializer is reached by the +// call graph, so TU-end analysis (not just per-function mode) sees a stack +// address escaping to a global. +int *leaked = nullptr; // expected-note 3 {{this global dangles}} + +int escaping = [] { + int local = 0; + leaked = &local; // expected-warning {{stack memory associated with local variable 'local' escapes to the global variable 'leaked' which will dangle}} + return 1; +}(); + +// A lambda nested inside another global-init lambda is reached too. +int nested = [] { + auto inner = [] { + int local = 0; + leaked = &local; // expected-warning {{stack memory associated with local variable 'local' escapes to the global variable 'leaked' which will dangle}} + return 0; + }; + return inner(); +}(); + +// A static data member initializer is reached the same way. +struct S { + static inline int member = [] { + int local = 0; + leaked = &local; // expected-warning {{stack memory associated with local variable 'local' escapes to the global variable 'leaked' which will dangle}} + return 1; + }(); +}; + +// A clean lambda introduces no false positive. +int ok = [] { + int local = 42; + return local; +}(); +} // namespace global_init_lambda + namespace LoopLocalPointers { void conditional_assignment_in_loop() { From 425cf54a01bf6b2eda357a99a83af397e0e398e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Horv=C3=A1th?= <[email protected]> Date: Mon, 29 Jun 2026 14:03:49 +0100 Subject: [PATCH 2/3] Update clang/lib/Analysis/CallGraph.cpp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Balázs Benics <[email protected]> --- clang/lib/Analysis/CallGraph.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clang/lib/Analysis/CallGraph.cpp b/clang/lib/Analysis/CallGraph.cpp index dd4074a10beb6..c0e73c1d3ce11 100644 --- a/clang/lib/Analysis/CallGraph.cpp +++ b/clang/lib/Analysis/CallGraph.cpp @@ -205,8 +205,7 @@ void CallGraph::addNodesForVarInit(VarDecl *VD) { if (!VD->hasGlobalStorage()) return; if (Expr *Init = VD->getInit()) { - CGBuilder builder(this, Root); - builder.Visit(Init); + CGBuilder{this, Root}.Visit(Init); } } From 876c611a29207f79c8782d550ab5d2040c13e362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Horv=C3=A1th?= <[email protected]> Date: Mon, 29 Jun 2026 14:38:15 +0100 Subject: [PATCH 3/3] Address review comment. --- clang/lib/Analysis/CallGraph.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clang/lib/Analysis/CallGraph.cpp b/clang/lib/Analysis/CallGraph.cpp index c0e73c1d3ce11..d5ba64475da10 100644 --- a/clang/lib/Analysis/CallGraph.cpp +++ b/clang/lib/Analysis/CallGraph.cpp @@ -204,9 +204,8 @@ void CallGraph::addNodesForVarInit(VarDecl *VD) { // CGBuilder at call sites. Attach discovered calls to the root. if (!VD->hasGlobalStorage()) return; - if (Expr *Init = VD->getInit()) { + if (Expr *Init = VD->getInit()) CGBuilder{this, Root}.Visit(Init); - } } CallGraphNode *CallGraph::getNode(const Decl *F) const { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
