[clang] [clang][dataflow] Add captured parameters to ReferencedDecls for lamb… (PR #117771)

Samira Bazuzi via cfe-commits cfe-commits at lists.llvm.org
Tue Nov 26 11:23:09 PST 2024


https://github.com/bazuzi created https://github.com/llvm/llvm-project/pull/117771

…da call operators.

This doesn't require that they be used in the operator's body, unlike other ReferencedDecls. This is most obviously different from captured local variables, which can be captured but will not appear in ReferencedDecls unless they appear in the operator's body.

This difference simplifies the collection of the captured parameters, but probably could be eliminated if desirable.

>From cc7305af699191867bf6d3c9e73f72f2d8754ebd Mon Sep 17 00:00:00 2001
From: Samira Bazuzi <bazuzi at google.com>
Date: Tue, 26 Nov 2024 14:19:16 -0500
Subject: [PATCH] [clang][dataflow] Add captured parameters to ReferencedDecls
 for lambda call operators.

This doesn't require that they be used in the operator's body, unlike other ReferencedDecls. This is most obviously different from captured local variables, which can be captured but will not appear in ReferencedDecls unless they appear in the operator's body.

This difference simplifies the collection of the captured parameters, but probably could be eliminated if desirable.
---
 .../clang/Analysis/FlowSensitive/ASTOps.h     |  4 ++
 clang/lib/Analysis/FlowSensitive/ASTOps.cpp   | 12 ++++++
 .../Analysis/FlowSensitive/ASTOpsTest.cpp     | 41 +++++++++++++++++++
 3 files changed, 57 insertions(+)

diff --git a/clang/include/clang/Analysis/FlowSensitive/ASTOps.h b/clang/include/clang/Analysis/FlowSensitive/ASTOps.h
index 6294c810626a70..eee355f3cbab16 100644
--- a/clang/include/clang/Analysis/FlowSensitive/ASTOps.h
+++ b/clang/include/clang/Analysis/FlowSensitive/ASTOps.h
@@ -146,6 +146,10 @@ struct ReferencedDecls {
   /// Free functions and member functions which are referenced (but not
   /// necessarily called).
   llvm::DenseSet<const FunctionDecl *> Functions;
+  /// Parameters of other functions, captured by reference by a lambda. This is
+  /// empty except when ReferencedDecls are computed for a lambda's call
+  /// operator.
+  llvm::DenseSet<const ParmVarDecl *> LambdaCapturedParams;
 };
 
 /// Returns declarations that are declared in or referenced from `FD`.
diff --git a/clang/lib/Analysis/FlowSensitive/ASTOps.cpp b/clang/lib/Analysis/FlowSensitive/ASTOps.cpp
index 9e7821bfc1e89e..52e54deaaf0dc3 100644
--- a/clang/lib/Analysis/FlowSensitive/ASTOps.cpp
+++ b/clang/lib/Analysis/FlowSensitive/ASTOps.cpp
@@ -11,6 +11,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "clang/Analysis/FlowSensitive/ASTOps.h"
+#include "clang/AST/ASTLambda.h"
 #include "clang/AST/ComputeDependence.h"
 #include "clang/AST/Decl.h"
 #include "clang/AST/DeclBase.h"
@@ -281,6 +282,17 @@ ReferencedDecls getReferencedDecls(const FunctionDecl &FD) {
   Visitor.TraverseStmt(FD.getBody());
   if (const auto *CtorDecl = dyn_cast<CXXConstructorDecl>(&FD))
     Visitor.traverseConstructorInits(CtorDecl);
+  if (const auto *Method = dyn_cast<CXXMethodDecl>(&FD);
+      Method && isLambdaCallOperator(Method)) {
+    for (const auto &Capture : Method->getParent()->captures()) {
+      if (Capture.capturesVariable()) {
+        if (const auto *Param =
+                dyn_cast<ParmVarDecl>(Capture.getCapturedVar())) {
+          Result.LambdaCapturedParams.insert(Param);
+        }
+      }
+    }
+  }
 
   return Result;
 }
diff --git a/clang/unittests/Analysis/FlowSensitive/ASTOpsTest.cpp b/clang/unittests/Analysis/FlowSensitive/ASTOpsTest.cpp
index 834aa7f4c2ac2e..e086ea3c892f16 100644
--- a/clang/unittests/Analysis/FlowSensitive/ASTOpsTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/ASTOpsTest.cpp
@@ -8,15 +8,23 @@
 
 #include "clang/Analysis/FlowSensitive/ASTOps.h"
 #include "TestingSupport.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Basic/LLVM.h"
+#include "clang/Frontend/ASTUnit.h"
+#include "clang/Tooling/Tooling.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include <memory>
+#include <string>
 
 namespace {
 
 using namespace clang;
 using namespace dataflow;
 
+using ast_matchers::cxxMethodDecl;
 using ast_matchers::cxxRecordDecl;
 using ast_matchers::hasName;
 using ast_matchers::hasType;
@@ -107,4 +115,37 @@ TEST(ASTOpsTest, ReferencedDeclsLocalsNotParamsOrStatics) {
               UnorderedElementsAre(LocalDecl));
 }
 
+TEST(ASTOpsTest, LambdaCaptures) {
+  std::string Code = R"cc(
+    void func(int CapturedByRef, int CapturedByValue, int NotCaptured) {
+    int Local;
+      auto Lambda = [&CapturedByRef, CapturedByValue, &Local](int LambdaParam) {
+      };
+    }
+  )cc";
+  std::unique_ptr<ASTUnit> Unit =
+      tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++17"});
+  auto &ASTCtx = Unit->getASTContext();
+
+  ASSERT_EQ(ASTCtx.getDiagnostics().getClient()->getNumErrors(), 0U);
+
+  auto *LambdaCallOp = selectFirst<CXXMethodDecl>(
+      "l", match(cxxMethodDecl(hasName("operator()")).bind("l"), ASTCtx));
+  ASSERT_NE(LambdaCallOp, nullptr);
+  auto *Func = cast<FunctionDecl>(findValueDecl(ASTCtx, "func"));
+  ASSERT_NE(Func, nullptr);
+  auto *CapturedByRefDecl = Func->getParamDecl(0);
+  ASSERT_NE(CapturedByRefDecl, nullptr);
+  auto *CapturedByValueDecl = Func->getParamDecl(1);
+  ASSERT_NE(CapturedByValueDecl, nullptr);
+
+  EXPECT_THAT(getReferencedDecls(*Func).LambdaCapturedParams, IsEmpty());
+  ReferencedDecls ForLambda = getReferencedDecls(*LambdaCallOp);
+  EXPECT_THAT(ForLambda.LambdaCapturedParams,
+              UnorderedElementsAre(CapturedByRefDecl, CapturedByValueDecl));
+  // Captured locals must be seen in the body for them to appear in
+  // ReferencedDecls.
+  EXPECT_THAT(ForLambda.Locals, IsEmpty());
+}
+
 } // namespace



More information about the cfe-commits mailing list