https://github.com/rniwa created
https://github.com/llvm/llvm-project/pull/224572
870d762546ee taught RawPtrRefLambdaCapturesChecker to ignore a lambda passed to
a call whose callee isn't resolved yet, by enumerating the expressions that
appear in a template pattern in place of a resolved call: an OverloadExpr, a
CXXDependentScopeMemberExpr or a DependentScopeDeclRefExpr as the callee, a
CXXUnresolvedConstructExpr, and a type-dependent ParenListExpr or InitListExpr.
That list can only ever be approximate. A call through an object of dependent
type, as in
template <typename T> void f(T& callable) {
RefCountable* obj = make_obj();
callable([obj] { obj->method(); });
}
has a plain DeclRefExpr to the parameter as its callee, so none of the cases
matched and the lambda was reported even when the instantiation passes it to a
NOESCAPE parameter.
Take the approach used by the call arguments and local variables checkers
instead: don't enter a template pattern at all. TraverseDecl now skips a
templated decl, so every callee a handler sees is resolved and the special
cases are no longer reachable. The body of a generic lambda is reached from the
LambdaExpr as a statement, so TraverseLambdaExpr hands the call operator to
TraverseDecl, which skips the pattern and traverses the instantiations.
Because an instantiation is now traversed as a declaration rather than as a
body, TraverseCXXMethodDecl would set the enclosing class to the closure type.
Keep the class of the enclosing method for a lambda's call operator, which is
what 'this' in the body refers to, so captures of this are still reported.
>From 72a7e419563fc7af1ca21d8522b0f692c83e38ad Mon Sep 17 00:00:00 2001
From: Ryosuke Niwa <[email protected]>
Date: Fri, 18 Sep 2026 02:01:03 -0700
Subject: [PATCH] [webkit.UncountedLambdaCapturesChecker] Skip template
patterns while traversing
870d762546ee taught RawPtrRefLambdaCapturesChecker to ignore a lambda passed
to a call whose callee isn't resolved yet, by enumerating the expressions that
appear in a template pattern in place of a resolved call: an OverloadExpr, a
CXXDependentScopeMemberExpr or a DependentScopeDeclRefExpr as the callee, a
CXXUnresolvedConstructExpr, and a type-dependent ParenListExpr or InitListExpr.
That list can only ever be approximate. A call through an object of dependent
type, as in
template <typename T> void f(T& callable) {
RefCountable* obj = make_obj();
callable([obj] { obj->method(); });
}
has a plain DeclRefExpr to the parameter as its callee, so none of the cases
matched and the lambda was reported even when the instantiation passes it to a
NOESCAPE parameter.
Take the approach used by the call arguments and local variables checkers
instead: don't enter a template pattern at all. TraverseDecl now skips a
templated decl, so every callee a handler sees is resolved and the special
cases are no longer reachable. The body of a generic lambda is reached from
the LambdaExpr as a statement, so TraverseLambdaExpr hands the call operator
to TraverseDecl, which skips the pattern and traverses the instantiations.
Because an instantiation is now traversed as a declaration rather than as a
body, TraverseCXXMethodDecl would set the enclosing class to the closure type.
Keep the class of the enclosing method for a lambda's call operator, which is
what 'this' in the body refers to, so captures of this are still reported.
---
.../WebKit/RawPtrRefLambdaCapturesChecker.cpp | 99 ++++++++-----------
.../WebKit/uncounted-lambda-captures.cpp | 35 +++++++
2 files changed, 75 insertions(+), 59 deletions(-)
diff --git
a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLambdaCapturesChecker.cpp
b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLambdaCapturesChecker.cpp
index 43db3065dd068..e0debb764af00 100644
---
a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLambdaCapturesChecker.cpp
+++
b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLambdaCapturesChecker.cpp
@@ -70,6 +70,17 @@ class RawPtrRefLambdaCapturesChecker
ShouldVisitImplicitCode = false;
}
+ bool TraverseDecl(Decl *D) override {
+ // A template pattern is checked through its instantiations, which are
+ // traversed from the TemplateDecl itself. In the pattern the callee of
+ // a call may still be an unresolved overload set, so whether a lambda
+ // argument can escape isn't known, and a lambda in a pattern which is
+ // never instantiated is never used. Don't enter it at all.
+ if (D && !isa<TemplateDecl>(D) && D->isTemplated())
+ return true;
+ return DynamicRecursiveASTVisitor::TraverseDecl(D);
+ }
+
bool TraverseCXXConstructorDecl(CXXConstructorDecl *Ctor) override {
llvm::SaveAndRestore SavedDecl(ClsType);
ClsType = Ctor->getThisType();
@@ -84,7 +95,11 @@ class RawPtrRefLambdaCapturesChecker
bool TraverseCXXMethodDecl(CXXMethodDecl *CXXMD) override {
llvm::SaveAndRestore SavedDecl(ClsType);
- if (CXXMD->isInstance())
+ // 'this' in the body of a lambda's call operator refers to the object
+ // of the enclosing method, so keep the class of that method. This
+ // matters for the instantiations of a generic lambda, which are
+ // traversed as declarations rather than as a body.
+ if (CXXMD->isInstance() && !CXXMD->getParent()->isLambda())
ClsType = CXXMD->getThisType();
return DynamicRecursiveASTVisitor::TraverseCXXMethodDecl(CXXMD);
}
@@ -119,21 +134,27 @@ class RawPtrRefLambdaCapturesChecker
}
bool TraverseLambdaExpr(LambdaExpr *L) override {
- if (!DynamicRecursiveASTVisitor::TraverseLambdaExpr(L))
+ auto *FTD = L->getLambdaClass()->getDependentLambdaCallOperator();
+ if (!FTD)
+ return DynamicRecursiveASTVisitor::TraverseLambdaExpr(L);
+ // The body of a generic lambda is the pattern of its call operator,
+ // but it is reached from the LambdaExpr as a statement, so
TraverseDecl
+ // never gets to skip it. Visit the lambda itself, traverse the capture
+ // initializers, which are evaluated in the enclosing scope, and then
+ // the call operator, of which the pattern is skipped like any other
and
+ // the instantiations are traversed. The initializers are traversed as
+ // expressions because the variable of an init capture is declared in
+ // the pattern.
+ if (!VisitLambdaExpr(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;
- }
- }
+ for (unsigned I = 0, N = L->capture_size(); I != N; ++I) {
+ if (!(L->capture_begin() + I)->isExplicit())
+ continue;
+ if (auto *Init = L->capture_init_begin()[I];
+ Init && !TraverseStmt(Init))
+ return false;
}
- return true;
+ return TraverseDecl(FTD);
}
bool VisitVarDecl(VarDecl *VD) override {
@@ -281,55 +302,15 @@ class RawPtrRefLambdaCapturesChecker
if (isVisitFunction(CE, Callee))
return true;
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;
+ } 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);
+ }
}
- // 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 d1cc7588ed8d6..3394315effe6f 100644
--- a/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp
+++ b/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp
@@ -685,3 +685,38 @@ void escape_in_generic_lambda(RefCountable* obj) {
(void)value;
});
}
+
+// The callee is a dependent object rather than an unresolved name, so nothing
+// about the call is known until the template is instantiated.
+struct NoEscapeCallable {
+ void operator()([[clang::noescape]] const WTF::Function<void()>&) const;
+};
+
+struct EscapeCallable {
+ void operator()(const WTF::Function<void()>&) const;
+};
+
+template <typename T>
+void call_through_noescape_callable(T& callable) {
+ RefCountable* obj = make_obj();
+ callable([obj] {
+ obj->method();
+ someFunction();
+ });
+}
+
+template <typename T>
+void call_through_escaping_callable(T& callable) {
+ RefCountable* obj = make_obj();
+ callable([obj] {
+ // expected-warning@-1{{Captured variable 'obj' is a raw pointer to
RefPtr-capable type 'RefCountable' [webkit.UncountedLambdaCapturesChecker]}}
+ obj->method();
+ someFunction();
+ });
+}
+
+void instantiate_dependent_callables(NoEscapeCallable& noEscape,
+ EscapeCallable& escape) {
+ call_through_noescape_callable(noEscape);
+ call_through_escaping_callable(escape);
+}
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits