| //===- unittests/Analysis/FlowSensitive/DataflowEnvironmentTest.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/DataflowEnvironment.h" |
| #include "TestingSupport.h" |
| #include "clang/AST/DeclCXX.h" |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| #include "clang/ASTMatchers/ASTMatchers.h" |
| #include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h" |
| #include "clang/Analysis/FlowSensitive/StorageLocation.h" |
| #include "clang/Analysis/FlowSensitive/Value.h" |
| #include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h" |
| #include "clang/Tooling/Tooling.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include <memory> |
| |
| namespace { |
| |
| using namespace clang; |
| using namespace dataflow; |
| using ::clang::dataflow::test::getFieldValue; |
| using ::testing::IsNull; |
| using ::testing::NotNull; |
| |
| class EnvironmentTest : public ::testing::Test { |
| protected: |
| EnvironmentTest() : DAContext(std::make_unique<WatchedLiteralsSolver>()) {} |
| |
| DataflowAnalysisContext DAContext; |
| }; |
| |
| TEST_F(EnvironmentTest, FlowCondition) { |
| Environment Env(DAContext); |
| auto &A = Env.arena(); |
| |
| EXPECT_TRUE(Env.flowConditionImplies(A.makeLiteral(true))); |
| EXPECT_FALSE(Env.flowConditionImplies(A.makeLiteral(false))); |
| |
| auto &X = A.makeAtomRef(A.makeAtom()); |
| EXPECT_FALSE(Env.flowConditionImplies(X)); |
| |
| Env.addToFlowCondition(X); |
| EXPECT_TRUE(Env.flowConditionImplies(X)); |
| |
| auto &NotX = A.makeNot(X); |
| EXPECT_FALSE(Env.flowConditionImplies(NotX)); |
| } |
| |
| TEST_F(EnvironmentTest, CreateValueRecursiveType) { |
| using namespace ast_matchers; |
| |
| std::string Code = R"cc( |
| struct Recursive { |
| bool X; |
| Recursive *R; |
| }; |
| // Use both fields to force them to be created with `createValue`. |
| void Usage(Recursive R) { (void)R.X; (void)R.R; } |
| )cc"; |
| |
| auto Unit = |
| tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++11"}); |
| auto &Context = Unit->getASTContext(); |
| |
| ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U); |
| |
| auto Results = |
| match(qualType(hasDeclaration(recordDecl( |
| hasName("Recursive"), |
| has(fieldDecl(hasName("R")).bind("field-r"))))) |
| .bind("target"), |
| Context); |
| const QualType *TyPtr = selectFirst<QualType>("target", Results); |
| ASSERT_THAT(TyPtr, NotNull()); |
| QualType Ty = *TyPtr; |
| ASSERT_FALSE(Ty.isNull()); |
| |
| const FieldDecl *R = selectFirst<FieldDecl>("field-r", Results); |
| ASSERT_THAT(R, NotNull()); |
| |
| Results = match(functionDecl(hasName("Usage")).bind("fun"), Context); |
| const auto *Fun = selectFirst<FunctionDecl>("fun", Results); |
| ASSERT_THAT(Fun, NotNull()); |
| |
| // Verify that the struct and the field (`R`) with first appearance of the |
| // type is created successfully. |
| Environment Env(DAContext, *Fun); |
| StructValue *SVal = cast<StructValue>(Env.createValue(Ty)); |
| PointerValue *PV = cast_or_null<PointerValue>(getFieldValue(SVal, *R, Env)); |
| EXPECT_THAT(PV, NotNull()); |
| } |
| |
| TEST_F(EnvironmentTest, InitGlobalVarsFun) { |
| using namespace ast_matchers; |
| |
| std::string Code = R"cc( |
| int Global = 0; |
| int Target () { return Global; } |
| )cc"; |
| |
| auto Unit = |
| tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++11"}); |
| auto &Context = Unit->getASTContext(); |
| |
| ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U); |
| |
| auto Results = |
| match(decl(anyOf(varDecl(hasName("Global")).bind("global"), |
| functionDecl(hasName("Target")).bind("target"))), |
| Context); |
| const auto *Fun = selectFirst<FunctionDecl>("target", Results); |
| const auto *Var = selectFirst<VarDecl>("global", Results); |
| ASSERT_THAT(Fun, NotNull()); |
| ASSERT_THAT(Var, NotNull()); |
| |
| // Verify the global variable is populated when we analyze `Target`. |
| Environment Env(DAContext, *Fun); |
| EXPECT_THAT(Env.getValue(*Var), NotNull()); |
| } |
| |
| // Tests that fields mentioned only in default member initializers are included |
| // in the set of tracked fields. |
| TEST_F(EnvironmentTest, IncludeFieldsFromDefaultInitializers) { |
| using namespace ast_matchers; |
| |
| std::string Code = R"cc( |
| struct S { |
| S() {} |
| int X = 3; |
| int Y = X; |
| }; |
| S foo(); |
| )cc"; |
| |
| auto Unit = |
| tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++11"}); |
| auto &Context = Unit->getASTContext(); |
| |
| ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U); |
| |
| auto Results = match( |
| qualType(hasDeclaration( |
| cxxRecordDecl(hasName("S"), |
| hasMethod(cxxConstructorDecl().bind("target"))) |
| .bind("struct"))) |
| .bind("ty"), |
| Context); |
| const auto *Constructor = selectFirst<FunctionDecl>("target", Results); |
| const auto *Rec = selectFirst<RecordDecl>("struct", Results); |
| const auto QTy = *selectFirst<QualType>("ty", Results); |
| ASSERT_THAT(Constructor, NotNull()); |
| ASSERT_THAT(Rec, NotNull()); |
| ASSERT_FALSE(QTy.isNull()); |
| |
| auto Fields = Rec->fields(); |
| FieldDecl *XDecl = nullptr; |
| for (FieldDecl *Field : Fields) { |
| if (Field->getNameAsString() == "X") { |
| XDecl = Field; |
| break; |
| } |
| } |
| ASSERT_THAT(XDecl, NotNull()); |
| |
| // Verify that the `X` field of `S` is populated when analyzing the |
| // constructor, even though it is not referenced directly in the constructor. |
| Environment Env(DAContext, *Constructor); |
| auto *Val = cast<StructValue>(Env.createValue(QTy)); |
| EXPECT_THAT(getFieldValue(Val, *XDecl, Env), NotNull()); |
| } |
| |
| TEST_F(EnvironmentTest, InitGlobalVarsFieldFun) { |
| using namespace ast_matchers; |
| |
| std::string Code = R"cc( |
| struct S { int Bar; }; |
| S Global = {0}; |
| int Target () { return Global.Bar; } |
| )cc"; |
| |
| auto Unit = |
| tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++11"}); |
| auto &Context = Unit->getASTContext(); |
| |
| ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U); |
| |
| auto Results = |
| match(decl(anyOf(varDecl(hasName("Global")).bind("global"), |
| functionDecl(hasName("Target")).bind("target"))), |
| Context); |
| const auto *Fun = selectFirst<FunctionDecl>("target", Results); |
| const auto *GlobalDecl = selectFirst<VarDecl>("global", Results); |
| ASSERT_THAT(Fun, NotNull()); |
| ASSERT_THAT(GlobalDecl, NotNull()); |
| |
| ASSERT_TRUE(GlobalDecl->getType()->isStructureType()); |
| auto GlobalFields = GlobalDecl->getType()->getAsRecordDecl()->fields(); |
| |
| FieldDecl *BarDecl = nullptr; |
| for (FieldDecl *Field : GlobalFields) { |
| if (Field->getNameAsString() == "Bar") { |
| BarDecl = Field; |
| break; |
| } |
| FAIL() << "Unexpected field: " << Field->getNameAsString(); |
| } |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| // Verify the global variable is populated when we analyze `Target`. |
| Environment Env(DAContext, *Fun); |
| const auto *GlobalLoc = |
| cast<AggregateStorageLocation>(Env.getStorageLocation(*GlobalDecl)); |
| const auto *GlobalVal = cast<StructValue>(Env.getValue(*GlobalLoc)); |
| auto *BarVal = getFieldValue(GlobalVal, *BarDecl, Env); |
| EXPECT_TRUE(isa<IntegerValue>(BarVal)); |
| } |
| |
| TEST_F(EnvironmentTest, InitGlobalVarsConstructor) { |
| using namespace ast_matchers; |
| |
| std::string Code = R"cc( |
| int Global = 0; |
| struct Target { |
| Target() : Field(Global) {} |
| int Field; |
| }; |
| )cc"; |
| |
| auto Unit = |
| tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++11"}); |
| auto &Context = Unit->getASTContext(); |
| |
| ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U); |
| |
| auto Results = |
| match(decl(anyOf( |
| varDecl(hasName("Global")).bind("global"), |
| cxxConstructorDecl(ofClass(hasName("Target"))).bind("target"))), |
| Context); |
| const auto *Ctor = selectFirst<CXXConstructorDecl>("target", Results); |
| const auto *Var = selectFirst<VarDecl>("global", Results); |
| ASSERT_TRUE(Ctor != nullptr); |
| ASSERT_THAT(Var, NotNull()); |
| |
| // Verify the global variable is populated when we analyze `Target`. |
| Environment Env(DAContext, *Ctor); |
| EXPECT_THAT(Env.getValue(*Var), NotNull()); |
| } |
| |
| TEST_F(EnvironmentTest, RefreshStructValue) { |
| using namespace ast_matchers; |
| |
| std::string Code = R"cc( |
| struct S {}; |
| void target () { |
| S s; |
| s; |
| } |
| )cc"; |
| |
| auto Unit = |
| tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++11"}); |
| auto &Context = Unit->getASTContext(); |
| |
| ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U); |
| |
| auto Results = match(functionDecl(hasName("target")).bind("target"), Context); |
| const auto *Target = selectFirst<FunctionDecl>("target", Results); |
| ASSERT_THAT(Target, NotNull()); |
| |
| Results = match(declRefExpr(to(varDecl(hasName("s")))).bind("s"), Context); |
| const auto *DRE = selectFirst<DeclRefExpr>("s", Results); |
| ASSERT_THAT(DRE, NotNull()); |
| |
| Environment Env(DAContext, *Target); |
| EXPECT_THAT(Env.getStorageLocationStrict(*DRE), IsNull()); |
| refreshStructValue(*DRE, Env); |
| EXPECT_THAT(Env.getStorageLocationStrict(*DRE), NotNull()); |
| } |
| |
| } // namespace |