Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions clang/include/clang/Analysis/FlowSensitive/ASTOps.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,17 @@ struct ReferencedDecls {
/// 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.
///
/// When analyzing a standalone `Stmt` directly, this set includes any
/// referenced function parameters. This supports the collection of
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a little simpler (of possibly losing some information):

... This supports the collection of
/// ReferencedDecls from a DeclStmt constructed for analysis of lambda init-capture
/// VarDecls.

/// ReferencedDecls from a DeclStmt constructed for lambda init-capture
/// VarDecls for the purpose of performing a dataflow analysis on the
/// declaration/initialization. When analyzing any `FunctionDecl`, this set
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super nit: I might preface with "Note:" to highlight that this is an aside and no longer describing the variable.

/// would also include any parameters of other functions that are referenced
/// in the analyzed function's body, though there is no known case where this
/// happens. If other (especially non-lambda-related) cases are found, this
/// group of parameters could be moved out of `LambdaCapturedParams` into a
/// separate set.
llvm::SetVector<const ParmVarDecl *> LambdaCapturedParams;
};

Expand Down
22 changes: 18 additions & 4 deletions clang/lib/Analysis/FlowSensitive/ASTOps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,9 @@ static MemberExpr *getMemberForAccessor(const CXXMemberCallExpr &C) {

class ReferencedDeclsVisitor : public AnalysisASTVisitor {
public:
ReferencedDeclsVisitor(ReferencedDecls &Referenced)
: Referenced(Referenced) {}
ReferencedDeclsVisitor(ReferencedDecls &Referenced,
const FunctionDecl *AnalyzedFunction)
: Referenced(Referenced), AnalyzedFunction(AnalyzedFunction) {}

void traverseConstructorInits(const CXXConstructorDecl *Ctor) {
for (const CXXCtorInitializer *Init : Ctor->inits()) {
Expand Down Expand Up @@ -235,6 +236,17 @@ class ReferencedDeclsVisitor : public AnalysisASTVisitor {
insertIfGlobal(*E->getDecl(), Referenced.Globals);
insertIfLocal(*E->getDecl(), Referenced.Locals);
insertIfFunction(*E->getDecl(), Referenced.Functions);

// Collect referenced parameters of functions other than the function being
// analyzed, or of any function if we are analyzing a standalone statement.
// See comments on `LambdaCapturedParams` for motivations.
if (auto *P = dyn_cast<ParmVarDecl>(E->getDecl())) {
if (!AnalyzedFunction ||
P->getParentFunctionOrMethod() != AnalyzedFunction) {
Referenced.LambdaCapturedParams.insert(P);
}
}

return true;
}

Expand Down Expand Up @@ -271,11 +283,13 @@ class ReferencedDeclsVisitor : public AnalysisASTVisitor {

private:
ReferencedDecls &Referenced;
// May be null, if we are visiting a statement that is not a function body.
const FunctionDecl *const AnalyzedFunction;
};

ReferencedDecls getReferencedDecls(const FunctionDecl &FD) {
ReferencedDecls Result;
ReferencedDeclsVisitor Visitor(Result);
ReferencedDeclsVisitor Visitor(Result, &FD);
Visitor.TraverseStmt(FD.getBody());
if (const auto *CtorDecl = dyn_cast<CXXConstructorDecl>(&FD))
Visitor.traverseConstructorInits(CtorDecl);
Expand Down Expand Up @@ -307,7 +321,7 @@ ReferencedDecls getReferencedDecls(const FunctionDecl &FD) {

ReferencedDecls getReferencedDecls(const Stmt &S) {
ReferencedDecls Result;
ReferencedDeclsVisitor Visitor(Result);
ReferencedDeclsVisitor Visitor(Result, nullptr);
Visitor.TraverseStmt(const_cast<Stmt *>(&S));
return Result;
}
Expand Down
59 changes: 56 additions & 3 deletions clang/unittests/Analysis/FlowSensitive/ASTOpsTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
#include "TestingSupport.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclGroup.h"
#include "clang/AST/Stmt.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Basic/LLVM.h"
#include "clang/Frontend/ASTUnit.h"
Expand All @@ -29,8 +31,11 @@ using ast_matchers::cxxRecordDecl;
using ast_matchers::hasName;
using ast_matchers::hasType;
using ast_matchers::initListExpr;
using ast_matchers::isInitCapture;
using ast_matchers::match;
using ast_matchers::parmVarDecl;
using ast_matchers::selectFirst;
using ast_matchers::varDecl;
using test::findValueDecl;
using testing::IsEmpty;
using testing::UnorderedElementsAre;
Expand Down Expand Up @@ -118,9 +123,8 @@ TEST(ASTOpsTest, ReferencedDeclsLocalsNotParamsOrStatics) {
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) {
};
int Local;
auto Lambda = [&CapturedByRef, CapturedByValue, &Local](int LambdaParam) {};
}
)cc";
std::unique_ptr<ASTUnit> Unit =
Expand Down Expand Up @@ -148,4 +152,53 @@ TEST(ASTOpsTest, LambdaCaptures) {
EXPECT_THAT(ForLambda.Locals, IsEmpty());
}

TEST(ASTOpsTest, LambdaInitCapture) {
std::string Code = R"cc(
void func(int I) {
auto Lambda = [C = I]() {};
}
)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 *IDecl = selectFirst<ParmVarDecl>(
"i", match(parmVarDecl(hasName("I")).bind("i"), ASTCtx));

// Synthesize a temporary DeclStmt for the assignment of Q to
// its initializing expression. This is an unusual pattern that does not
// perfectly reflect the CFG or AST for declaration or assignment of an
// init-capture, but is used for dataflow analysis, which requires a Stmt and
// not just a VarDecl with an initializer.
auto *CDecl = selectFirst<VarDecl>(
"c", match(varDecl(isInitCapture()).bind("c"), ASTCtx));
DeclStmt DS(DeclGroupRef(const_cast<VarDecl *>(CDecl)), CDecl->getBeginLoc(),
CDecl->getEndLoc());
EXPECT_THAT(getReferencedDecls(DS).LambdaCapturedParams,
UnorderedElementsAre(IDecl));
}

TEST(ASTOpsTest, NoLambdaCapturedParamsWhenAnalyzingFunctionNotLambdaOperator) {
std::string Code = R"cc(
void func(int I) {
I++; // Parameters of the function being analyzed don't get included in `LambdaCapturedParams`.
auto Lambda = [&I](int L) { // We don't see the capture of `I` when analyzing `func`.
L++; // We don't see the lambda body when analyzing `func`.
I++; // This is a parameter of the function being analyzed, and it's not seen when analyzing `func`.
};
}
)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 *Func = cast<FunctionDecl>(findValueDecl(ASTCtx, "func"));
ASSERT_NE(Func, nullptr);
EXPECT_THAT(getReferencedDecls(*Func).LambdaCapturedParams, IsEmpty());
}

} // namespace