https://github.com/rniwa created https://github.com/llvm/llvm-project/pull/223942
RawPtrRefLambdaCapturesChecker only consults NOESCAPE when it can find a FunctionDecl for a call. Because a lambda argument is type-dependent, a call taking one inside a template is dependent, and its callee is often still unresolved in the template pattern: an UnresolvedLookupExpr for an unqualified call or a call to a function template, an UnresolvedMemberExpr for an overloaded member function or a member function template, and a CXXDependentScopeMemberExpr for a member of a dependent object. checkParameters never ran for those, so a lambda was reported even when the parameter it's passed to is annotated with NOESCAPE. The same happened for a lambda passed to a constructor which isn't resolved until the instantiation, which appears as a CXXUnresolvedConstructExpr or as a dependent ParenListExpr / InitListExpr. Whether such a lambda can escape isn't known before the enclosing template is instantiated, so ignore these lambdas and let the instantiation check them, matching what RetainPtrCtorAdoptChecker already does. A lambda in a template which is never instantiated is never used, so not checking it is fine. Waiting for the instantiation also requires traversing the instantiations of a generic lambda's call operator. RecursiveASTVisitor::TraverseLambdaExpr only traverses the lambda's body, which is a template pattern for a generic lambda, so those instantiations were never visited and the calls in them were never resolved. Traverse the body of each specialization, not the CXXMethodDecl, so that the lambda stays associated with the class enclosing it and captures of this are still reported. >From 1c2ea534250c8241a3e06d7a7503fd4f2aded686 Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa <[email protected]> Date: Wed, 16 Sep 2026 00:57:04 -0700 Subject: [PATCH] [webkit.UncountedLambdaCapturesChecker] NOESCAPE is ignored in a template function RawPtrRefLambdaCapturesChecker only consults NOESCAPE when it can find a FunctionDecl for a call. Because a lambda argument is type-dependent, a call taking one inside a template is dependent, and its callee is often still unresolved in the template pattern: an UnresolvedLookupExpr for an unqualified call or a call to a function template, an UnresolvedMemberExpr for an overloaded member function or a member function template, and a CXXDependentScopeMemberExpr for a member of a dependent object. checkParameters never ran for those, so a lambda was reported even when the parameter it's passed to is annotated with NOESCAPE. The same happened for a lambda passed to a constructor which isn't resolved until the instantiation, which appears as a CXXUnresolvedConstructExpr or as a dependent ParenListExpr / InitListExpr. Whether such a lambda can escape isn't known before the enclosing template is instantiated, so ignore these lambdas and let the instantiation check them, matching what RetainPtrCtorAdoptChecker already does. A lambda in a template which is never instantiated is never used, so not checking it is fine. Waiting for the instantiation also requires traversing the instantiations of a generic lambda's call operator. RecursiveASTVisitor::TraverseLambdaExpr only traverses the lambda's body, which is a template pattern for a generic lambda, so those instantiations were never visited and the calls in them were never resolved. Traverse the body of each specialization, not the CXXMethodDecl, so that the lambda stays associated with the class enclosing it and captures of this are still reported. --- .../WebKit/RawPtrRefLambdaCapturesChecker.cpp | 68 +++++++- .../WebKit/uncounted-lambda-captures.cpp | 165 ++++++++++++++++++ 2 files changed, 228 insertions(+), 5 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLambdaCapturesChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLambdaCapturesChecker.cpp index 02b6c31c47223..43db3065dd068 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLambdaCapturesChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLambdaCapturesChecker.cpp @@ -118,6 +118,24 @@ class RawPtrRefLambdaCapturesChecker return true; } + bool TraverseLambdaExpr(LambdaExpr *L) override { + if (!DynamicRecursiveASTVisitor::TraverseLambdaExpr(L)) + return false; + // The body of a generic lambda is a template pattern in which calls may + // not have been resolved yet, so traverse the instantiations of its + // call operator as well. Only the body is traversed so that the lambda + // stays associated with the class enclosing it, like the pattern is. + if (auto *FTD = L->getLambdaClass()->getDependentLambdaCallOperator()) { + for (auto *Spec : FTD->specializations()) { + if (auto *Body = Spec->getBody()) { + if (!TraverseStmt(Body)) + return false; + } + } + } + return true; + } + bool VisitVarDecl(VarDecl *VD) override { auto *Init = VD->getInit(); if (!Init) @@ -263,15 +281,55 @@ class RawPtrRefLambdaCapturesChecker if (isVisitFunction(CE, Callee)) return true; checkParameters(CE, Callee); - } else if (auto *CalleeE = CE->getCallee()) { - if (auto *DRE = dyn_cast<DeclRefExpr>(CalleeE->IgnoreParenCasts())) { - if (auto *Callee = dyn_cast_or_null<FunctionDecl>(DRE->getDecl())) - checkParameters(CE, Callee); - } + return true; } + auto *CalleeE = CE->getCallee(); + if (!CalleeE) + return true; + CalleeE = CalleeE->IgnoreParenCasts(); + if (auto *DRE = dyn_cast<DeclRefExpr>(CalleeE)) { + if (auto *Callee = dyn_cast_or_null<FunctionDecl>(DRE->getDecl())) + checkParameters(CE, Callee); + return true; + } + // The callee of a call in an uninstantiated template may not have been + // resolved yet, in which case whether each lambda argument can escape + // isn't known. Wait for the instantiation to check those lambdas. + if (isa<OverloadExpr, CXXDependentScopeMemberExpr, + DependentScopeDeclRefExpr>(CalleeE)) + ignoreLambdasInArgs({CE->getArgs(), CE->getNumArgs()}); return true; } + // Lambdas passed to a constructor which isn't resolved until the + // enclosing template is instantiated are checked in the instantiation. + bool + VisitCXXUnresolvedConstructExpr(CXXUnresolvedConstructExpr *CE) override { + ignoreLambdasInArgs({CE->arg_begin(), CE->arg_end()}); + return true; + } + + bool VisitParenListExpr(ParenListExpr *PLE) override { + if (PLE->isTypeDependent()) + ignoreLambdasInArgs(PLE->exprs()); + return true; + } + + bool VisitInitListExpr(InitListExpr *ILE) override { + if (ILE->isTypeDependent()) + ignoreLambdasInArgs(ILE->inits()); + return true; + } + + void ignoreLambdasInArgs(ArrayRef<Expr *> Args) { + for (auto *Arg : Args) { + if (!Arg) + continue; + if (auto *L = findLambdaInArg(Arg->IgnoreParenCasts())) + LambdasToIgnore.insert(L); + } + } + bool isVisitFunction(CallExpr *CallExpr, FunctionDecl *FnDecl) { bool IsVisitFn = safeGetName(FnDecl) == "visit"; if (!IsVisitFn) diff --git a/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp b/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp index 3609014deedd8..d1cc7588ed8d6 100644 --- a/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp +++ b/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp @@ -520,3 +520,168 @@ class LambdaInConstructorDestructor { void doWork(); }; + +void callNoEscape([[clang::noescape]] const WTF::Function<void()>&); + +template <typename... Callbacks> +void variadicNoEscape([[clang::noescape]] Callbacks&&... callbacks) { + someFunction(); +} + +template <typename Callback> +void templateNoEscape([[clang::noescape]] Callback&& callback) { + someFunction(); +} + +struct NoEscapeHolder { + NoEscapeHolder([[clang::noescape]] const WTF::Function<void()>&); + void member([[clang::noescape]] const WTF::Function<void()>&); + void overloaded([[clang::noescape]] const WTF::Function<void()>&); + void overloaded(int); + template <typename Callback> void memberTemplate([[clang::noescape]] Callback&&); +}; + +template <typename T> +void noescape_in_template(NoEscapeHolder& holder, T& dependentHolder) { + RefCountable* obj = make_obj(); + callNoEscape([obj] { + obj->method(); + someFunction(); + }); + templateNoEscape([obj] { + obj->method(); + someFunction(); + }); + variadicNoEscape([obj] { + obj->method(); + someFunction(); + }, [obj] { + obj->method(); + someFunction(); + }); + holder.overloaded([obj] { + obj->method(); + someFunction(); + }); + holder.memberTemplate([obj] { + obj->method(); + someFunction(); + }); + dependentHolder.member([obj] { + obj->method(); + someFunction(); + }); + NoEscapeHolder holderFromParenInit([obj] { + obj->method(); + someFunction(); + }); + NoEscapeHolder holderFromListInit { [obj] { + obj->method(); + someFunction(); + } }; + auto holderFromTemporary = NoEscapeHolder([obj] { + obj->method(); + someFunction(); + }); +} + +struct EscapeHolder { + EscapeHolder(const WTF::Function<void()>&); + void member(const WTF::Function<void()>&); +}; + +template <typename Callback> +void templateEscape(Callback&& callback); + +template <typename T> +void escape_in_template(EscapeHolder& holder, T& dependentHolder) { + RefCountable* obj = make_obj(); + callAsync([obj] { + // expected-warning@-1{{Captured variable 'obj' is a raw pointer to RefPtr-capable type 'RefCountable' [webkit.UncountedLambdaCapturesChecker]}} + obj->method(); + someFunction(); + }); + templateEscape([obj] { + // expected-warning@-1{{Captured variable 'obj' is a raw pointer to RefPtr-capable type 'RefCountable' [webkit.UncountedLambdaCapturesChecker]}} + obj->method(); + someFunction(); + }); + holder.member([obj] { + // expected-warning@-1{{Captured variable 'obj' is a raw pointer to RefPtr-capable type 'RefCountable' [webkit.UncountedLambdaCapturesChecker]}} + obj->method(); + someFunction(); + }); + dependentHolder.member([obj] { + // expected-warning@-1{{Captured variable 'obj' is a raw pointer to RefPtr-capable type 'RefCountable' [webkit.UncountedLambdaCapturesChecker]}} + obj->method(); + someFunction(); + }); + EscapeHolder holderFromParenInit([obj] { + // expected-warning@-1{{Captured variable 'obj' is a raw pointer to RefPtr-capable type 'RefCountable' [webkit.UncountedLambdaCapturesChecker]}} + obj->method(); + someFunction(); + }); +} + +void instantiate_templates(NoEscapeHolder& noEscapeHolder, EscapeHolder& escapeHolder) { + noescape_in_template(noEscapeHolder, noEscapeHolder); + escape_in_template(escapeHolder, escapeHolder); +} + +// The overloads disagree about NOESCAPE, so which one is picked isn't known +// until the template is instantiated. +void mixedNoEscape([[clang::noescape]] const WTF::Function<void()>&, int); +void mixedNoEscape(const WTF::Function<void()>&, const char*); + +template <typename T> +void mixed_noescape_overloads_in_template() { + RefCountable* obj = make_obj(); + mixedNoEscape([obj] { + obj->method(); + someFunction(); + }, 1); + mixedNoEscape([obj] { + // expected-warning@-1{{Captured variable 'obj' is a raw pointer to RefPtr-capable type 'RefCountable' [webkit.UncountedLambdaCapturesChecker]}} + obj->method(); + someFunction(); + }, ""); +} + +void instantiate_mixed_noescape_overloads() { + mixed_noescape_overloads_in_template<int>(); +} + +// The body of a generic lambda is a template pattern, so the calls in it are +// checked in the instantiations of its call operator. +template <typename Callback> +void withValue(Callback callback) { + callback(3); + callback(4U); +} + +void noescape_in_generic_lambda(RefCountable* obj) { + withValue([obj](auto value) { + // expected-warning@-1{{Captured variable 'obj' is a raw pointer to RefPtr-capable type 'RefCountable' [webkit.UncountedLambdaCapturesChecker]}} + callNoEscape([obj] { + obj->method(); + someFunction(); + }); + templateNoEscape([obj] { + obj->method(); + someFunction(); + }); + (void)value; + }); +} + +void escape_in_generic_lambda(RefCountable* obj) { + withValue([obj](auto value) { + // expected-warning@-1{{Captured variable 'obj' is a raw pointer to RefPtr-capable type 'RefCountable' [webkit.UncountedLambdaCapturesChecker]}} + callAsync([obj] { + // expected-warning@-1{{Captured variable 'obj' is a raw pointer to RefPtr-capable type 'RefCountable' [webkit.UncountedLambdaCapturesChecker]}} + obj->method(); + someFunction(); + }); + (void)value; + }); +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
