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

Reply via email to