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..d5ba64475da10 100644 --- a/clang/lib/Analysis/CallGraph.cpp +++ b/clang/lib/Analysis/CallGraph.cpp @@ -196,6 +196,18 @@ 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{this, Root}.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() {