Skip to content

Commit 198fb5e

Browse files
authored
[clang][dataflow] Add captured parameters to ReferencedDecls for lamb… (#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.
1 parent 8d6c73c commit 198fb5e

File tree

3 files changed

+68
-0
lines changed

3 files changed

+68
-0
lines changed

clang/include/clang/Analysis/FlowSensitive/ASTOps.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ struct ReferencedDecls {
146146
/// Free functions and member functions which are referenced (but not
147147
/// necessarily called).
148148
llvm::DenseSet<const FunctionDecl *> Functions;
149+
/// When analyzing a lambda's call operator, the set of all parameters (from
150+
/// the surrounding function) that the lambda captures. Captured local
151+
/// variables are already included in `Locals` above.
152+
llvm::DenseSet<const ParmVarDecl *> LambdaCapturedParams;
149153
};
150154

151155
/// Returns declarations that are declared in or referenced from `FD`.

clang/lib/Analysis/FlowSensitive/ASTOps.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
#include "clang/Analysis/FlowSensitive/ASTOps.h"
14+
#include "clang/AST/ASTLambda.h"
1415
#include "clang/AST/ComputeDependence.h"
1516
#include "clang/AST/Decl.h"
1617
#include "clang/AST/DeclBase.h"
@@ -282,6 +283,28 @@ ReferencedDecls getReferencedDecls(const FunctionDecl &FD) {
282283
if (const auto *CtorDecl = dyn_cast<CXXConstructorDecl>(&FD))
283284
Visitor.traverseConstructorInits(CtorDecl);
284285

286+
// If analyzing a lambda call operator, collect all captures of parameters (of
287+
// the surrounding function). This collects them even if they are not
288+
// referenced in the body of the lambda call operator. Non-parameter local
289+
// variables that are captured are already collected into
290+
// `ReferencedDecls.Locals` when traversing the call operator body, but we
291+
// collect parameters here to avoid needing to check at each referencing node
292+
// whether the parameter is a lambda capture from a surrounding function or is
293+
// a parameter of the current function. If it becomes necessary to limit this
294+
// set to the parameters actually referenced in the body, alternative
295+
// optimizations can be implemented to minimize duplicative work.
296+
if (const auto *Method = dyn_cast<CXXMethodDecl>(&FD);
297+
Method && isLambdaCallOperator(Method)) {
298+
for (const auto &Capture : Method->getParent()->captures()) {
299+
if (Capture.capturesVariable()) {
300+
if (const auto *Param =
301+
dyn_cast<ParmVarDecl>(Capture.getCapturedVar())) {
302+
Result.LambdaCapturedParams.insert(Param);
303+
}
304+
}
305+
}
306+
}
307+
285308
return Result;
286309
}
287310

clang/unittests/Analysis/FlowSensitive/ASTOpsTest.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,23 @@
88

99
#include "clang/Analysis/FlowSensitive/ASTOps.h"
1010
#include "TestingSupport.h"
11+
#include "clang/AST/Decl.h"
12+
#include "clang/AST/DeclCXX.h"
13+
#include "clang/ASTMatchers/ASTMatchers.h"
14+
#include "clang/Basic/LLVM.h"
15+
#include "clang/Frontend/ASTUnit.h"
16+
#include "clang/Tooling/Tooling.h"
1117
#include "gmock/gmock.h"
1218
#include "gtest/gtest.h"
1319
#include <memory>
20+
#include <string>
1421

1522
namespace {
1623

1724
using namespace clang;
1825
using namespace dataflow;
1926

27+
using ast_matchers::cxxMethodDecl;
2028
using ast_matchers::cxxRecordDecl;
2129
using ast_matchers::hasName;
2230
using ast_matchers::hasType;
@@ -107,4 +115,37 @@ TEST(ASTOpsTest, ReferencedDeclsLocalsNotParamsOrStatics) {
107115
UnorderedElementsAre(LocalDecl));
108116
}
109117

118+
TEST(ASTOpsTest, LambdaCaptures) {
119+
std::string Code = R"cc(
120+
void func(int CapturedByRef, int CapturedByValue, int NotCaptured) {
121+
int Local;
122+
auto Lambda = [&CapturedByRef, CapturedByValue, &Local](int LambdaParam) {
123+
};
124+
}
125+
)cc";
126+
std::unique_ptr<ASTUnit> Unit =
127+
tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++17"});
128+
auto &ASTCtx = Unit->getASTContext();
129+
130+
ASSERT_EQ(ASTCtx.getDiagnostics().getClient()->getNumErrors(), 0U);
131+
132+
auto *LambdaCallOp = selectFirst<CXXMethodDecl>(
133+
"l", match(cxxMethodDecl(hasName("operator()")).bind("l"), ASTCtx));
134+
ASSERT_NE(LambdaCallOp, nullptr);
135+
auto *Func = cast<FunctionDecl>(findValueDecl(ASTCtx, "func"));
136+
ASSERT_NE(Func, nullptr);
137+
auto *CapturedByRefDecl = Func->getParamDecl(0);
138+
ASSERT_NE(CapturedByRefDecl, nullptr);
139+
auto *CapturedByValueDecl = Func->getParamDecl(1);
140+
ASSERT_NE(CapturedByValueDecl, nullptr);
141+
142+
EXPECT_THAT(getReferencedDecls(*Func).LambdaCapturedParams, IsEmpty());
143+
ReferencedDecls ForLambda = getReferencedDecls(*LambdaCallOp);
144+
EXPECT_THAT(ForLambda.LambdaCapturedParams,
145+
UnorderedElementsAre(CapturedByRefDecl, CapturedByValueDecl));
146+
// Captured locals must be seen in the body for them to appear in
147+
// ReferencedDecls.
148+
EXPECT_THAT(ForLambda.Locals, IsEmpty());
149+
}
150+
110151
} // namespace

0 commit comments

Comments
 (0)