clang/unittests/Analysis/FlowSensitive/TransferTest.cpp (449 lines of code) (raw):

//===- unittests/Analysis/FlowSensitive/TransferTest.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 "NoopAnalysis.h" #include "TestingSupport.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" #include "clang/Analysis/FlowSensitive/StorageLocation.h" #include "clang/Analysis/FlowSensitive/Value.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include <cassert> #include <string> #include <utility> namespace { using namespace clang; using namespace dataflow; using ::testing::_; using ::testing::ElementsAre; using ::testing::IsNull; using ::testing::NotNull; using ::testing::Pair; class TransferTest : public ::testing::Test { protected: template <typename Matcher> void runDataflow(llvm::StringRef Code, Matcher Match) { test::checkDataflow<NoopAnalysis>( Code, "target", [](ASTContext &C, Environment &) { return NoopAnalysis(C); }, [&Match](llvm::ArrayRef< std::pair<std::string, DataflowAnalysisState<NoopLattice>>> Results, ASTContext &ASTCtx) { Match(Results, ASTCtx); }, {"-fsyntax-only", "-std=c++17"}); } }; /// Returns the `ValueDecl` for the given identifier. /// /// Requirements: /// /// `Name` must be unique in `ASTCtx`. static const ValueDecl *findValueDecl(ASTContext &ASTCtx, llvm::StringRef Name) { auto TargetNodes = ast_matchers::match( ast_matchers::valueDecl(ast_matchers::hasName(Name)).bind("v"), ASTCtx); assert(TargetNodes.size() == 1 && "Name must be unique"); auto *const Result = ast_matchers::selectFirst<ValueDecl>("v", TargetNodes); assert(Result != nullptr); return Result; } TEST_F(TransferTest, IntVarDecl) { std::string Code = R"( void target() { int foo; // [[p]] } )"; runDataflow( Code, [](llvm::ArrayRef< std::pair<std::string, DataflowAnalysisState<NoopLattice>>> Results, ASTContext &ASTCtx) { ASSERT_THAT(Results, ElementsAre(Pair("p", _))); const Environment &Env = Results[0].second.Env; const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo"); ASSERT_THAT(FooDecl, NotNull()); const StorageLocation *FooLoc = Env.getStorageLocation(*FooDecl); ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(FooLoc)); const Value *FooVal = Env.getValue(*FooLoc); ASSERT_TRUE(isa_and_nonnull<IntegerValue>(FooVal)); }); } TEST_F(TransferTest, StructVarDecl) { std::string Code = R"( struct Foo { int Bar; }; void target() { Foo foo; // [[p]] } )"; runDataflow( Code, [](llvm::ArrayRef< std::pair<std::string, DataflowAnalysisState<NoopLattice>>> Results, ASTContext &ASTCtx) { ASSERT_THAT(Results, ElementsAre(Pair("p", _))); const Environment &Env = Results[0].second.Env; const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo"); ASSERT_THAT(FooDecl, NotNull()); ASSERT_TRUE(FooDecl->getType()->isStructureType()); auto FooFields = FooDecl->getType()->getAsRecordDecl()->fields(); FieldDecl *BarDecl = nullptr; for (FieldDecl *Field : FooFields) { if (Field->getNameAsString() == "Bar") { BarDecl = Field; } else { FAIL() << "Unexpected field: " << Field->getNameAsString(); } } ASSERT_THAT(BarDecl, NotNull()); const auto *FooLoc = cast<AggregateStorageLocation>(Env.getStorageLocation(*FooDecl)); const auto *BarLoc = cast<ScalarStorageLocation>(&FooLoc->getChild(*BarDecl)); const auto *FooVal = cast<StructValue>(Env.getValue(*FooLoc)); const auto *BarVal = cast<IntegerValue>(&FooVal->getChild(*BarDecl)); ASSERT_EQ(Env.getValue(*BarLoc), BarVal); }); } TEST_F(TransferTest, ClassVarDecl) { std::string Code = R"( class Foo { int Bar; }; void target() { Foo foo; // [[p]] } )"; runDataflow( Code, [](llvm::ArrayRef< std::pair<std::string, DataflowAnalysisState<NoopLattice>>> Results, ASTContext &ASTCtx) { ASSERT_THAT(Results, ElementsAre(Pair("p", _))); const Environment &Env = Results[0].second.Env; const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo"); ASSERT_THAT(FooDecl, NotNull()); ASSERT_TRUE(FooDecl->getType()->isClassType()); auto FooFields = FooDecl->getType()->getAsRecordDecl()->fields(); FieldDecl *BarDecl = nullptr; for (FieldDecl *Field : FooFields) { if (Field->getNameAsString() == "Bar") { BarDecl = Field; } else { FAIL() << "Unexpected field: " << Field->getNameAsString(); } } ASSERT_THAT(BarDecl, NotNull()); const auto *FooLoc = cast<AggregateStorageLocation>(Env.getStorageLocation(*FooDecl)); const auto *BarLoc = cast<ScalarStorageLocation>(&FooLoc->getChild(*BarDecl)); const auto *FooVal = cast<StructValue>(Env.getValue(*FooLoc)); const auto *BarVal = cast<IntegerValue>(&FooVal->getChild(*BarDecl)); ASSERT_EQ(Env.getValue(*BarLoc), BarVal); }); } TEST_F(TransferTest, ReferenceVarDecl) { std::string Code = R"( struct Foo {}; Foo& getFoo(); void target() { Foo& foo = getFoo(); // [[p]] } )"; runDataflow( Code, [](llvm::ArrayRef< std::pair<std::string, DataflowAnalysisState<NoopLattice>>> Results, ASTContext &ASTCtx) { ASSERT_THAT(Results, ElementsAre(Pair("p", _))); const Environment &Env = Results[0].second.Env; const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo"); ASSERT_THAT(FooDecl, NotNull()); const StorageLocation *FooLoc = Env.getStorageLocation(*FooDecl); ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(FooLoc)); const ReferenceValue *FooVal = cast<ReferenceValue>(Env.getValue(*FooLoc)); const StorageLocation &FooPointeeLoc = FooVal->getPointeeLoc(); ASSERT_TRUE(isa<AggregateStorageLocation>(&FooPointeeLoc)); const Value *FooPointeeVal = Env.getValue(FooPointeeLoc); ASSERT_TRUE(isa_and_nonnull<StructValue>(FooPointeeVal)); }); } TEST_F(TransferTest, SelfReferentialReferenceVarDecl) { std::string Code = R"( struct Foo; struct Baz {}; struct Bar { Foo& FooRef; Foo* FooPtr; Baz& BazRef; Baz* BazPtr; }; struct Foo { Bar& Bar; }; Foo& getFoo(); void target() { Foo& foo = getFoo(); // [[p]] } )"; runDataflow(Code, [](llvm::ArrayRef<std::pair< std::string, DataflowAnalysisState<NoopLattice>>> Results, ASTContext &ASTCtx) { ASSERT_THAT(Results, ElementsAre(Pair("p", _))); const Environment &Env = Results[0].second.Env; const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo"); ASSERT_THAT(FooDecl, NotNull()); ASSERT_TRUE(FooDecl->getType()->isReferenceType()); ASSERT_TRUE(FooDecl->getType().getNonReferenceType()->isStructureType()); const auto FooFields = FooDecl->getType().getNonReferenceType()->getAsRecordDecl()->fields(); FieldDecl *BarDecl = nullptr; for (FieldDecl *Field : FooFields) { if (Field->getNameAsString() == "Bar") { BarDecl = Field; } else { FAIL() << "Unexpected field: " << Field->getNameAsString(); } } ASSERT_THAT(BarDecl, NotNull()); ASSERT_TRUE(BarDecl->getType()->isReferenceType()); ASSERT_TRUE(BarDecl->getType().getNonReferenceType()->isStructureType()); const auto BarFields = BarDecl->getType().getNonReferenceType()->getAsRecordDecl()->fields(); FieldDecl *FooRefDecl = nullptr; FieldDecl *FooPtrDecl = nullptr; FieldDecl *BazRefDecl = nullptr; FieldDecl *BazPtrDecl = nullptr; for (FieldDecl *Field : BarFields) { if (Field->getNameAsString() == "FooRef") { FooRefDecl = Field; } else if (Field->getNameAsString() == "FooPtr") { FooPtrDecl = Field; } else if (Field->getNameAsString() == "BazRef") { BazRefDecl = Field; } else if (Field->getNameAsString() == "BazPtr") { BazPtrDecl = Field; } else { FAIL() << "Unexpected field: " << Field->getNameAsString(); } } ASSERT_THAT(FooRefDecl, NotNull()); ASSERT_THAT(FooPtrDecl, NotNull()); ASSERT_THAT(BazRefDecl, NotNull()); ASSERT_THAT(BazPtrDecl, NotNull()); const auto *FooLoc = cast<ScalarStorageLocation>(Env.getStorageLocation(*FooDecl)); const auto *FooVal = cast<ReferenceValue>(Env.getValue(*FooLoc)); const auto *FooPointeeVal = cast<StructValue>(Env.getValue(FooVal->getPointeeLoc())); const auto *BarVal = cast<ReferenceValue>(&FooPointeeVal->getChild(*BarDecl)); const auto *BarPointeeVal = cast<StructValue>(Env.getValue(BarVal->getPointeeLoc())); const auto *FooRefVal = cast<ReferenceValue>(&BarPointeeVal->getChild(*FooRefDecl)); const StorageLocation &FooRefPointeeLoc = FooRefVal->getPointeeLoc(); ASSERT_THAT(Env.getValue(FooRefPointeeLoc), IsNull()); const auto *FooPtrVal = cast<PointerValue>(&BarPointeeVal->getChild(*FooPtrDecl)); const StorageLocation &FooPtrPointeeLoc = FooPtrVal->getPointeeLoc(); ASSERT_THAT(Env.getValue(FooPtrPointeeLoc), IsNull()); const auto *BazRefVal = cast<ReferenceValue>(&BarPointeeVal->getChild(*BazRefDecl)); const StorageLocation &BazRefPointeeLoc = BazRefVal->getPointeeLoc(); ASSERT_THAT(Env.getValue(BazRefPointeeLoc), NotNull()); const auto *BazPtrVal = cast<PointerValue>(&BarPointeeVal->getChild(*BazPtrDecl)); const StorageLocation &BazPtrPointeeLoc = BazPtrVal->getPointeeLoc(); ASSERT_THAT(Env.getValue(BazPtrPointeeLoc), NotNull()); }); } TEST_F(TransferTest, PointerVarDecl) { std::string Code = R"( struct Foo {}; Foo* getFoo(); void target() { Foo* foo = getFoo(); // [[p]] } )"; runDataflow( Code, [](llvm::ArrayRef< std::pair<std::string, DataflowAnalysisState<NoopLattice>>> Results, ASTContext &ASTCtx) { ASSERT_THAT(Results, ElementsAre(Pair("p", _))); const Environment &Env = Results[0].second.Env; const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo"); ASSERT_THAT(FooDecl, NotNull()); const StorageLocation *FooLoc = Env.getStorageLocation(*FooDecl); ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(FooLoc)); const PointerValue *FooVal = cast<PointerValue>(Env.getValue(*FooLoc)); const StorageLocation &FooPointeeLoc = FooVal->getPointeeLoc(); ASSERT_TRUE(isa<AggregateStorageLocation>(&FooPointeeLoc)); const Value *FooPointeeVal = Env.getValue(FooPointeeLoc); ASSERT_TRUE(isa_and_nonnull<StructValue>(FooPointeeVal)); }); } TEST_F(TransferTest, SelfReferentialPointerVarDecl) { std::string Code = R"( struct Foo; struct Baz {}; struct Bar { Foo& FooRef; Foo* FooPtr; Baz& BazRef; Baz* BazPtr; }; struct Foo { Bar* Bar; }; Foo* getFoo(); void target() { Foo* foo = getFoo(); // [[p]] } )"; runDataflow( Code, [](llvm::ArrayRef< std::pair<std::string, DataflowAnalysisState<NoopLattice>>> Results, ASTContext &ASTCtx) { ASSERT_THAT(Results, ElementsAre(Pair("p", _))); const Environment &Env = Results[0].second.Env; const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo"); ASSERT_THAT(FooDecl, NotNull()); ASSERT_TRUE(FooDecl->getType()->isPointerType()); ASSERT_TRUE(FooDecl->getType() ->getAs<PointerType>() ->getPointeeType() ->isStructureType()); const auto FooFields = FooDecl->getType() ->getAs<PointerType>() ->getPointeeType() ->getAsRecordDecl() ->fields(); FieldDecl *BarDecl = nullptr; for (FieldDecl *Field : FooFields) { if (Field->getNameAsString() == "Bar") { BarDecl = Field; } else { FAIL() << "Unexpected field: " << Field->getNameAsString(); } } ASSERT_THAT(BarDecl, NotNull()); ASSERT_TRUE(BarDecl->getType()->isPointerType()); ASSERT_TRUE(BarDecl->getType() ->getAs<PointerType>() ->getPointeeType() ->isStructureType()); const auto BarFields = BarDecl->getType() ->getAs<PointerType>() ->getPointeeType() ->getAsRecordDecl() ->fields(); FieldDecl *FooRefDecl = nullptr; FieldDecl *FooPtrDecl = nullptr; FieldDecl *BazRefDecl = nullptr; FieldDecl *BazPtrDecl = nullptr; for (FieldDecl *Field : BarFields) { if (Field->getNameAsString() == "FooRef") { FooRefDecl = Field; } else if (Field->getNameAsString() == "FooPtr") { FooPtrDecl = Field; } else if (Field->getNameAsString() == "BazRef") { BazRefDecl = Field; } else if (Field->getNameAsString() == "BazPtr") { BazPtrDecl = Field; } else { FAIL() << "Unexpected field: " << Field->getNameAsString(); } } ASSERT_THAT(FooRefDecl, NotNull()); ASSERT_THAT(FooPtrDecl, NotNull()); ASSERT_THAT(BazRefDecl, NotNull()); ASSERT_THAT(BazPtrDecl, NotNull()); const auto *FooLoc = cast<ScalarStorageLocation>(Env.getStorageLocation(*FooDecl)); const auto *FooVal = cast<PointerValue>(Env.getValue(*FooLoc)); const auto *FooPointeeVal = cast<StructValue>(Env.getValue(FooVal->getPointeeLoc())); const auto *BarVal = cast<PointerValue>(&FooPointeeVal->getChild(*BarDecl)); const auto *BarPointeeVal = cast<StructValue>(Env.getValue(BarVal->getPointeeLoc())); const auto *FooRefVal = cast<ReferenceValue>(&BarPointeeVal->getChild(*FooRefDecl)); const StorageLocation &FooRefPointeeLoc = FooRefVal->getPointeeLoc(); ASSERT_THAT(Env.getValue(FooRefPointeeLoc), IsNull()); const auto *FooPtrVal = cast<PointerValue>(&BarPointeeVal->getChild(*FooPtrDecl)); const StorageLocation &FooPtrPointeeLoc = FooPtrVal->getPointeeLoc(); ASSERT_THAT(Env.getValue(FooPtrPointeeLoc), IsNull()); const auto *BazRefVal = cast<ReferenceValue>(&BarPointeeVal->getChild(*BazRefDecl)); const StorageLocation &BazRefPointeeLoc = BazRefVal->getPointeeLoc(); ASSERT_THAT(Env.getValue(BazRefPointeeLoc), NotNull()); const auto *BazPtrVal = cast<PointerValue>(&BarPointeeVal->getChild(*BazPtrDecl)); const StorageLocation &BazPtrPointeeLoc = BazPtrVal->getPointeeLoc(); ASSERT_THAT(Env.getValue(BazPtrPointeeLoc), NotNull()); }); } TEST_F(TransferTest, JoinVarDecl) { std::string Code = R"( void target(bool b) { int foo; // [[p1]] if (b) { int bar; // [[p2]] } else { int baz; // [[p3]] } (void)0; // [[p4]] } )"; runDataflow( Code, [](llvm::ArrayRef< std::pair<std::string, DataflowAnalysisState<NoopLattice>>> Results, ASTContext &ASTCtx) { ASSERT_THAT(Results, ElementsAre(Pair("p4", _), Pair("p3", _), Pair("p2", _), Pair("p1", _))); const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo"); ASSERT_THAT(FooDecl, NotNull()); const ValueDecl *BarDecl = findValueDecl(ASTCtx, "bar"); ASSERT_THAT(BarDecl, NotNull()); const ValueDecl *BazDecl = findValueDecl(ASTCtx, "baz"); ASSERT_THAT(BazDecl, NotNull()); const Environment &Env1 = Results[3].second.Env; const StorageLocation *FooLoc = Env1.getStorageLocation(*FooDecl); ASSERT_THAT(FooLoc, NotNull()); ASSERT_THAT(Env1.getStorageLocation(*BarDecl), IsNull()); ASSERT_THAT(Env1.getStorageLocation(*BazDecl), IsNull()); const Environment &Env2 = Results[2].second.Env; ASSERT_EQ(Env2.getStorageLocation(*FooDecl), FooLoc); ASSERT_THAT(Env2.getStorageLocation(*BarDecl), NotNull()); ASSERT_THAT(Env2.getStorageLocation(*BazDecl), IsNull()); const Environment &Env3 = Results[1].second.Env; ASSERT_EQ(Env3.getStorageLocation(*FooDecl), FooLoc); ASSERT_THAT(Env3.getStorageLocation(*BarDecl), IsNull()); ASSERT_THAT(Env3.getStorageLocation(*BazDecl), NotNull()); const Environment &Env4 = Results[0].second.Env; ASSERT_EQ(Env4.getStorageLocation(*FooDecl), FooLoc); ASSERT_THAT(Env4.getStorageLocation(*BarDecl), IsNull()); ASSERT_THAT(Env4.getStorageLocation(*BazDecl), IsNull()); }); } } // namespace