Samira Bazuzi 198fb5ed4a
[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.
2024-11-27 10:08:57 -05:00

152 lines
4.9 KiB
C++

//===- unittests/Analysis/FlowSensitive/ASTOpsTest.cpp --------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#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;
using ast_matchers::initListExpr;
using ast_matchers::match;
using ast_matchers::selectFirst;
using test::findValueDecl;
using testing::IsEmpty;
using testing::UnorderedElementsAre;
TEST(ASTOpsTest, RecordInitListHelperOnEmptyUnionInitList) {
// This is a regression test: The `RecordInitListHelper` used to assert-fail
// when called for the `InitListExpr` of an empty union.
std::string Code = R"cc(
struct S {
S() : UField{} {};
union U {} UField;
};
)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 *InitList = selectFirst<InitListExpr>(
"init",
match(initListExpr(hasType(cxxRecordDecl(hasName("U")))).bind("init"),
ASTCtx));
ASSERT_NE(InitList, nullptr);
RecordInitListHelper Helper(InitList);
EXPECT_THAT(Helper.base_inits(), IsEmpty());
EXPECT_THAT(Helper.field_inits(), IsEmpty());
}
TEST(ASTOpsTest, ReferencedDeclsOnUnionInitList) {
// This is a regression test: `getReferencedDecls()` used to return a null
// `FieldDecl` in this case (in addition to the correct non-null `FieldDecl`)
// because `getInitializedFieldInUnion()` returns null for the syntactic form
// of the `InitListExpr`.
std::string Code = R"cc(
struct S {
S() : UField{0} {};
union U {
int I;
} UField;
};
)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 *InitList = selectFirst<InitListExpr>(
"init",
match(initListExpr(hasType(cxxRecordDecl(hasName("U")))).bind("init"),
ASTCtx));
ASSERT_NE(InitList, nullptr);
auto *IDecl = cast<FieldDecl>(findValueDecl(ASTCtx, "I"));
EXPECT_THAT(getReferencedDecls(*InitList).Fields,
UnorderedElementsAre(IDecl));
}
TEST(ASTOpsTest, ReferencedDeclsLocalsNotParamsOrStatics) {
std::string Code = R"cc(
void func(int Param) {
static int Static = 0;
int Local = Param;
Local = Static;
}
)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);
auto *LocalDecl = cast<VarDecl>(findValueDecl(ASTCtx, "Local"));
EXPECT_THAT(getReferencedDecls(*Func).Locals,
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