[clang] [CallGraph] Collect callables from global variable initializers (PR #206458)
Gábor Horváth via cfe-commits
cfe-commits at lists.llvm.org
Mon Jun 29 06:38:26 PDT 2026
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 <gaborh at apple.com>
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?= <xazax.hun at gmail.com>
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 <benicsbalazs at gmail.com>
---
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?= <xazax.hun at gmail.com>
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 {
More information about the cfe-commits
mailing list