From cc7305af699191867bf6d3c9e73f72f2d8754ebd Mon Sep 17 00:00:00 2001 From: Samira Bazuzi Date: Tue, 26 Nov 2024 14:19:16 -0500 Subject: [PATCH 1/2] [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 6294c810626a7..eee355f3cbab1 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 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 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 9e7821bfc1e89..52e54deaaf0dc 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(&FD)) Visitor.traverseConstructorInits(CtorDecl); + if (const auto *Method = dyn_cast(&FD); + Method && isLambdaCallOperator(Method)) { + for (const auto &Capture : Method->getParent()->captures()) { + if (Capture.capturesVariable()) { + if (const auto *Param = + dyn_cast(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 834aa7f4c2ac2..e086ea3c892f1 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 +#include 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 Unit = + tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++17"}); + auto &ASTCtx = Unit->getASTContext(); + + ASSERT_EQ(ASTCtx.getDiagnostics().getClient()->getNumErrors(), 0U); + + auto *LambdaCallOp = selectFirst( + "l", match(cxxMethodDecl(hasName("operator()")).bind("l"), ASTCtx)); + ASSERT_NE(LambdaCallOp, nullptr); + auto *Func = cast(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 From e38f44d1710c217e4f8f9e63d0c0757b7a065292 Mon Sep 17 00:00:00 2001 From: Samira Bazuzi Date: Tue, 26 Nov 2024 18:24:40 -0500 Subject: [PATCH 2/2] Expand and update comments. --- clang/include/clang/Analysis/FlowSensitive/ASTOps.h | 6 +++--- clang/lib/Analysis/FlowSensitive/ASTOps.cpp | 11 +++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/clang/include/clang/Analysis/FlowSensitive/ASTOps.h b/clang/include/clang/Analysis/FlowSensitive/ASTOps.h index eee355f3cbab1..8c7ee86d15c06 100644 --- a/clang/include/clang/Analysis/FlowSensitive/ASTOps.h +++ b/clang/include/clang/Analysis/FlowSensitive/ASTOps.h @@ -146,9 +146,9 @@ struct ReferencedDecls { /// Free functions and member functions which are referenced (but not /// necessarily called). llvm::DenseSet 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. + /// When analyzing a lambda's call operator, the set of all parameters (from + /// the surrounding function) that the lambda captures. Captured local + /// variables are already included in `Locals` above. llvm::DenseSet LambdaCapturedParams; }; diff --git a/clang/lib/Analysis/FlowSensitive/ASTOps.cpp b/clang/lib/Analysis/FlowSensitive/ASTOps.cpp index 52e54deaaf0dc..9a46e79cf4a67 100644 --- a/clang/lib/Analysis/FlowSensitive/ASTOps.cpp +++ b/clang/lib/Analysis/FlowSensitive/ASTOps.cpp @@ -282,6 +282,17 @@ ReferencedDecls getReferencedDecls(const FunctionDecl &FD) { Visitor.TraverseStmt(FD.getBody()); if (const auto *CtorDecl = dyn_cast(&FD)) Visitor.traverseConstructorInits(CtorDecl); + + // If analyzing a lambda call operator, collect all captures of parameters (of + // the surrounding function). This collects them even if they are not + // referenced in the body of the lambda call operator. Non-parameter local + // variables that are captured are already collected into + // `ReferencedDecls.Locals` when traversing the call operator body, but we + // collect parameters here to avoid needing to check at each referencing node + // whether the parameter is a lambda capture from a surrounding function or is + // a parameter of the current function. If it becomes necessary to limit this + // set to the parameters actually referenced in the body, alternative + // optimizations can be implemented to minimize duplicative work. if (const auto *Method = dyn_cast(&FD); Method && isLambdaCallOperator(Method)) { for (const auto &Capture : Method->getParent()->captures()) {