| //===- 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 "TestingSupport.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/AST/Decl.h" |
| #include "clang/ASTMatchers/ASTMatchers.h" |
| #include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h" |
| #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" |
| #include "clang/Analysis/FlowSensitive/NoopAnalysis.h" |
| #include "clang/Analysis/FlowSensitive/RecordOps.h" |
| #include "clang/Analysis/FlowSensitive/StorageLocation.h" |
| #include "clang/Analysis/FlowSensitive/Value.h" |
| #include "clang/Basic/LangStandard.h" |
| #include "clang/Testing/TestAST.h" |
| #include "llvm/ADT/SmallVector.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/Testing/Support/Error.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include <optional> |
| #include <string> |
| #include <utility> |
| |
| namespace clang { |
| namespace dataflow { |
| namespace { |
| AST_MATCHER(FunctionDecl, isTemplated) { return Node.isTemplated(); } |
| } // namespace |
| } // namespace dataflow |
| } // namespace clang |
| |
| namespace { |
| |
| using namespace clang; |
| using namespace dataflow; |
| using namespace test; |
| using ::testing::Eq; |
| using ::testing::IsNull; |
| using ::testing::Ne; |
| using ::testing::NotNull; |
| using ::testing::UnorderedElementsAre; |
| |
| // Declares a minimal coroutine library. |
| constexpr llvm::StringRef CoroutineLibrary = R"cc( |
| struct promise; |
| struct task; |
| |
| namespace std { |
| template <class, class...> |
| struct coroutine_traits {}; |
| template <> |
| struct coroutine_traits<task> { |
| using promise_type = promise; |
| }; |
| |
| template <class Promise = void> |
| struct coroutine_handle { |
| static constexpr coroutine_handle from_address(void *addr) { return {}; } |
| }; |
| } // namespace std |
| |
| struct awaitable { |
| bool await_ready() const noexcept; |
| void await_suspend(std::coroutine_handle<promise>) const noexcept; |
| void await_resume() const noexcept; |
| }; |
| struct task {}; |
| struct promise { |
| task get_return_object(); |
| awaitable initial_suspend(); |
| awaitable final_suspend() noexcept; |
| void unhandled_exception(); |
| void return_void(); |
| }; |
| )cc"; |
| |
| void runDataflow( |
| llvm::StringRef Code, |
| std::function< |
| void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &, |
| ASTContext &)> |
| VerifyResults, |
| DataflowAnalysisOptions Options, |
| LangStandard::Kind Std = LangStandard::lang_cxx17, |
| llvm::StringRef TargetFun = "target") { |
| ASSERT_THAT_ERROR(checkDataflowWithNoopAnalysis(Code, VerifyResults, Options, |
| Std, TargetFun), |
| llvm::Succeeded()); |
| } |
| |
| void runDataflow( |
| llvm::StringRef Code, |
| std::function< |
| void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &, |
| ASTContext &)> |
| VerifyResults, |
| LangStandard::Kind Std = LangStandard::lang_cxx17, |
| bool ApplyBuiltinTransfer = true, llvm::StringRef TargetFun = "target") { |
| runDataflow(Code, std::move(VerifyResults), |
| {ApplyBuiltinTransfer ? BuiltinOptions{} |
| : std::optional<BuiltinOptions>()}, |
| Std, TargetFun); |
| } |
| |
| void runDataflowOnLambda( |
| llvm::StringRef Code, |
| std::function< |
| void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &, |
| ASTContext &)> |
| VerifyResults, |
| DataflowAnalysisOptions Options, |
| LangStandard::Kind Std = LangStandard::lang_cxx17) { |
| ASSERT_THAT_ERROR( |
| checkDataflowWithNoopAnalysis( |
| Code, |
| ast_matchers::hasDeclContext( |
| ast_matchers::cxxRecordDecl(ast_matchers::isLambda())), |
| VerifyResults, Options, Std), |
| llvm::Succeeded()); |
| } |
| |
| void runDataflowOnLambda( |
| llvm::StringRef Code, |
| std::function< |
| void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &, |
| ASTContext &)> |
| VerifyResults, |
| LangStandard::Kind Std = LangStandard::lang_cxx17, |
| bool ApplyBuiltinTransfer = true) { |
| runDataflowOnLambda(Code, std::move(VerifyResults), |
| {ApplyBuiltinTransfer ? BuiltinOptions{} |
| : std::optional<BuiltinOptions>()}, |
| Std); |
| } |
| |
| const Formula &getFormula(const ValueDecl &D, const Environment &Env) { |
| return cast<BoolValue>(Env.getValue(D))->formula(); |
| } |
| |
| TEST(TransferTest, CNotSupported) { |
| TestInputs Inputs("void target() {}"); |
| Inputs.Language = TestLanguage::Lang_C89; |
| clang::TestAST AST(Inputs); |
| const auto *Target = |
| cast<FunctionDecl>(test::findValueDecl(AST.context(), "target")); |
| ASSERT_THAT_ERROR(AdornedCFG::build(*Target).takeError(), |
| llvm::FailedWithMessage("Can only analyze C++")); |
| } |
| |
| TEST(TransferTest, ObjectiveCNotSupported) { |
| TestInputs Inputs("void target() {}"); |
| Inputs.Language = TestLanguage::Lang_OBJC; |
| clang::TestAST AST(Inputs); |
| const auto *Target = |
| cast<FunctionDecl>(test::findValueDecl(AST.context(), "target")); |
| ASSERT_THAT_ERROR(AdornedCFG::build(*Target).takeError(), |
| llvm::FailedWithMessage("Can only analyze C++")); |
| } |
| |
| TEST(TransferTest, ObjectiveCXXNotSupported) { |
| TestInputs Inputs("void target() {}"); |
| Inputs.Language = TestLanguage::Lang_OBJCXX; |
| clang::TestAST AST(Inputs); |
| const auto *Target = |
| cast<FunctionDecl>(test::findValueDecl(AST.context(), "target")); |
| ASSERT_THAT_ERROR(AdornedCFG::build(*Target).takeError(), |
| llvm::FailedWithMessage("Can only analyze C++")); |
| } |
| |
| TEST(TransferTest, IntVarDeclNotTrackedWhenTransferDisabled) { |
| std::string Code = R"( |
| void target() { |
| int Foo; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| EXPECT_EQ(Env.getStorageLocation(*FooDecl), nullptr); |
| }, |
| LangStandard::lang_cxx17, |
| /*ApplyBuiltinTransfer=*/false); |
| } |
| |
| TEST(TransferTest, BoolVarDecl) { |
| std::string Code = R"( |
| void target() { |
| bool Foo; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| 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); |
| EXPECT_TRUE(isa_and_nonnull<BoolValue>(FooVal)); |
| }); |
| } |
| |
| TEST(TransferTest, IntVarDecl) { |
| std::string Code = R"( |
| void target() { |
| int Foo; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| 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); |
| EXPECT_TRUE(isa_and_nonnull<IntegerValue>(FooVal)); |
| }); |
| } |
| |
| TEST(TransferTest, StructIncomplete) { |
| std::string Code = R"( |
| struct A; |
| |
| void target() { |
| A* Foo; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| auto *FooValue = dyn_cast_or_null<PointerValue>(Env.getValue(*FooDecl)); |
| ASSERT_THAT(FooValue, NotNull()); |
| |
| EXPECT_TRUE(isa<RecordStorageLocation>(FooValue->getPointeeLoc())); |
| }); |
| } |
| |
| // As a memory optimization, we prevent modeling fields nested below a certain |
| // level (currently, depth 3). This test verifies this lack of modeling. We also |
| // include a regression test for the case that the unmodeled field is a |
| // reference to a struct; previously, we crashed when accessing such a field. |
| TEST(TransferTest, StructFieldUnmodeled) { |
| std::string Code = R"( |
| struct S { int X; }; |
| S GlobalS; |
| struct A { S &Unmodeled = GlobalS; }; |
| struct B { A F3; }; |
| struct C { B F2; }; |
| struct D { C F1; }; |
| |
| void target() { |
| D Bar; |
| A &Foo = Bar.F1.F2.F3; |
| int Zab = Foo.Unmodeled.X; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| QualType FooReferentType = FooDecl->getType()->getPointeeType(); |
| ASSERT_TRUE(FooReferentType->isStructureType()); |
| auto FooFields = FooReferentType->getAsRecordDecl()->fields(); |
| |
| FieldDecl *UnmodeledDecl = nullptr; |
| for (FieldDecl *Field : FooFields) { |
| if (Field->getNameAsString() == "Unmodeled") { |
| UnmodeledDecl = Field; |
| } else { |
| FAIL() << "Unexpected field: " << Field->getNameAsString(); |
| } |
| } |
| ASSERT_THAT(UnmodeledDecl, NotNull()); |
| |
| const auto *FooLoc = |
| cast<RecordStorageLocation>(Env.getStorageLocation(*FooDecl)); |
| const auto &UnmodeledLoc = |
| *cast<RecordStorageLocation>(FooLoc->getChild(*UnmodeledDecl)); |
| StorageLocation &UnmodeledXLoc = getFieldLoc(UnmodeledLoc, "X", ASTCtx); |
| EXPECT_EQ(Env.getValue(UnmodeledXLoc), nullptr); |
| |
| const ValueDecl *ZabDecl = findValueDecl(ASTCtx, "Zab"); |
| ASSERT_THAT(ZabDecl, NotNull()); |
| EXPECT_THAT(Env.getValue(*ZabDecl), NotNull()); |
| }); |
| } |
| |
| TEST(TransferTest, StructVarDecl) { |
| std::string Code = R"( |
| struct A { |
| int Bar; |
| }; |
| |
| void target() { |
| A Foo; |
| (void)Foo.Bar; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| 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<RecordStorageLocation>(Env.getStorageLocation(*FooDecl)); |
| EXPECT_TRUE(isa<IntegerValue>(getFieldValue(FooLoc, *BarDecl, Env))); |
| }); |
| } |
| |
| TEST(TransferTest, StructVarDeclWithInit) { |
| std::string Code = R"( |
| struct A { |
| int Bar; |
| }; |
| |
| A Gen(); |
| |
| void target() { |
| A Foo = Gen(); |
| (void)Foo.Bar; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| 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<RecordStorageLocation>(Env.getStorageLocation(*FooDecl)); |
| EXPECT_TRUE(isa<IntegerValue>(getFieldValue(FooLoc, *BarDecl, Env))); |
| }); |
| } |
| |
| TEST(TransferTest, StructArrayVarDecl) { |
| std::string Code = R"( |
| struct A {}; |
| |
| void target() { |
| A Array[2]; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *ArrayDecl = findValueDecl(ASTCtx, "Array"); |
| |
| // We currently don't create values for arrays. |
| ASSERT_THAT(Env.getValue(*ArrayDecl), IsNull()); |
| }); |
| } |
| |
| TEST(TransferTest, ClassVarDecl) { |
| std::string Code = R"( |
| class A { |
| public: |
| int Bar; |
| }; |
| |
| void target() { |
| A Foo; |
| (void)Foo.Bar; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| 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<RecordStorageLocation>(Env.getStorageLocation(*FooDecl)); |
| EXPECT_TRUE(isa<IntegerValue>(getFieldValue(FooLoc, *BarDecl, Env))); |
| }); |
| } |
| |
| TEST(TransferTest, ReferenceVarDecl) { |
| std::string Code = R"( |
| struct A {}; |
| |
| A &getA(); |
| |
| void target() { |
| A &Foo = getA(); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const StorageLocation *FooLoc = Env.getStorageLocation(*FooDecl); |
| ASSERT_TRUE(isa_and_nonnull<RecordStorageLocation>(FooLoc)); |
| }); |
| } |
| |
| TEST(TransferTest, SelfReferentialReferenceVarDecl) { |
| std::string Code = R"( |
| struct A; |
| |
| struct B {}; |
| |
| struct C { |
| A &FooRef; |
| A *FooPtr; |
| B &BazRef; |
| B *BazPtr; |
| }; |
| |
| struct A { |
| C &Bar; |
| }; |
| |
| A &getA(); |
| |
| void target() { |
| A &Foo = getA(); |
| (void)Foo.Bar.FooRef; |
| (void)Foo.Bar.FooPtr; |
| (void)Foo.Bar.BazRef; |
| (void)Foo.Bar.BazPtr; |
| // [[p]] |
| } |
| )"; |
| runDataflow(Code, [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> |
| &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| 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<RecordStorageLocation>(Env.getStorageLocation(*FooDecl)); |
| |
| const auto &BarLoc = |
| *cast<RecordStorageLocation>(FooLoc.getChild(*BarDecl)); |
| |
| const auto &FooReferentLoc = |
| *cast<RecordStorageLocation>(BarLoc.getChild(*FooRefDecl)); |
| EXPECT_EQ(Env.getValue(*cast<RecordStorageLocation>( |
| FooReferentLoc.getChild(*BarDecl)) |
| ->getChild(*FooPtrDecl)), |
| nullptr); |
| |
| const auto &FooPtrVal = |
| *cast<PointerValue>(getFieldValue(&BarLoc, *FooPtrDecl, Env)); |
| const auto &FooPtrPointeeLoc = |
| cast<RecordStorageLocation>(FooPtrVal.getPointeeLoc()); |
| EXPECT_EQ(Env.getValue(*cast<RecordStorageLocation>( |
| FooPtrPointeeLoc.getChild(*BarDecl)) |
| ->getChild(*FooPtrDecl)), |
| nullptr); |
| |
| EXPECT_TRUE(isa<PointerValue>(getFieldValue(&BarLoc, *BazPtrDecl, Env))); |
| }); |
| } |
| |
| TEST(TransferTest, PointerVarDecl) { |
| std::string Code = R"( |
| struct A {}; |
| |
| A *getA(); |
| |
| void target() { |
| A *Foo = getA(); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| 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(); |
| EXPECT_TRUE(isa<RecordStorageLocation>(&FooPointeeLoc)); |
| }); |
| } |
| |
| TEST(TransferTest, SelfReferentialPointerVarDecl) { |
| std::string Code = R"( |
| struct A; |
| |
| struct B {}; |
| |
| struct C { |
| A &FooRef; |
| A *FooPtr; |
| B &BazRef; |
| B *BazPtr; |
| }; |
| |
| struct A { |
| C *Bar; |
| }; |
| |
| A *getA(); |
| |
| void target() { |
| A *Foo = getA(); |
| (void)Foo->Bar->FooRef; |
| (void)Foo->Bar->FooPtr; |
| (void)Foo->Bar->BazRef; |
| (void)Foo->Bar->BazPtr; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| 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 &FooPointeeLoc = |
| cast<RecordStorageLocation>(FooVal.getPointeeLoc()); |
| |
| const auto &BarVal = |
| *cast<PointerValue>(getFieldValue(&FooPointeeLoc, *BarDecl, Env)); |
| const auto &BarPointeeLoc = |
| cast<RecordStorageLocation>(BarVal.getPointeeLoc()); |
| |
| const auto &FooPtrVal = *cast<PointerValue>( |
| getFieldValue(&BarPointeeLoc, *FooPtrDecl, Env)); |
| const auto &FooPtrPointeeLoc = |
| cast<RecordStorageLocation>(FooPtrVal.getPointeeLoc()); |
| EXPECT_EQ(Env.getValue(*FooPtrPointeeLoc.getChild(*BarDecl)), nullptr); |
| |
| EXPECT_TRUE( |
| isa<PointerValue>(getFieldValue(&BarPointeeLoc, *BazPtrDecl, Env))); |
| }); |
| } |
| |
| TEST(TransferTest, DirectlySelfReferentialReference) { |
| std::string Code = R"( |
| struct target { |
| target() { |
| (void)0; |
| // [[p]] |
| } |
| target &self = *this; |
| }; |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| const ValueDecl *SelfDecl = findValueDecl(ASTCtx, "self"); |
| |
| auto *ThisLoc = Env.getThisPointeeStorageLocation(); |
| ASSERT_EQ(ThisLoc->getChild(*SelfDecl), ThisLoc); |
| }); |
| } |
| |
| TEST(TransferTest, MultipleVarsDecl) { |
| std::string Code = R"( |
| void target() { |
| int Foo, Bar; |
| (void)0; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const StorageLocation *FooLoc = Env.getStorageLocation(*FooDecl); |
| ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(FooLoc)); |
| |
| const StorageLocation *BarLoc = Env.getStorageLocation(*BarDecl); |
| ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(BarLoc)); |
| |
| const Value *FooVal = Env.getValue(*FooLoc); |
| EXPECT_TRUE(isa_and_nonnull<IntegerValue>(FooVal)); |
| |
| const Value *BarVal = Env.getValue(*BarLoc); |
| EXPECT_TRUE(isa_and_nonnull<IntegerValue>(BarVal)); |
| }); |
| } |
| |
| TEST(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, [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> |
| &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p1", "p2", "p3", "p4")); |
| |
| 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 = getEnvironmentAtAnnotation(Results, "p1"); |
| |
| const StorageLocation *FooLoc = Env1.getStorageLocation(*FooDecl); |
| EXPECT_THAT(FooLoc, NotNull()); |
| EXPECT_THAT(Env1.getStorageLocation(*BarDecl), IsNull()); |
| EXPECT_THAT(Env1.getStorageLocation(*BazDecl), IsNull()); |
| |
| const Environment &Env2 = getEnvironmentAtAnnotation(Results, "p2"); |
| EXPECT_EQ(Env2.getStorageLocation(*FooDecl), FooLoc); |
| EXPECT_THAT(Env2.getStorageLocation(*BarDecl), NotNull()); |
| EXPECT_THAT(Env2.getStorageLocation(*BazDecl), IsNull()); |
| |
| const Environment &Env3 = getEnvironmentAtAnnotation(Results, "p3"); |
| EXPECT_EQ(Env3.getStorageLocation(*FooDecl), FooLoc); |
| EXPECT_THAT(Env3.getStorageLocation(*BarDecl), IsNull()); |
| EXPECT_THAT(Env3.getStorageLocation(*BazDecl), NotNull()); |
| |
| const Environment &Env4 = getEnvironmentAtAnnotation(Results, "p4"); |
| EXPECT_EQ(Env4.getStorageLocation(*FooDecl), FooLoc); |
| EXPECT_THAT(Env4.getStorageLocation(*BarDecl), IsNull()); |
| EXPECT_THAT(Env4.getStorageLocation(*BazDecl), IsNull()); |
| }); |
| } |
| |
| TEST(TransferTest, BinaryOperatorAssign) { |
| std::string Code = R"( |
| void target() { |
| int Foo; |
| int Bar; |
| (Bar) = (Foo); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const Value *FooVal = Env.getValue(*FooDecl); |
| ASSERT_TRUE(isa_and_nonnull<IntegerValue>(FooVal)); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| EXPECT_EQ(Env.getValue(*BarDecl), FooVal); |
| }); |
| } |
| |
| TEST(TransferTest, BinaryOperatorAssignIntegerLiteral) { |
| std::string Code = R"( |
| void target() { |
| int Foo = 1; |
| // [[before]] |
| Foo = 2; |
| // [[after]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Before = |
| getEnvironmentAtAnnotation(Results, "before"); |
| const Environment &After = getEnvironmentAtAnnotation(Results, "after"); |
| |
| const auto &ValBefore = |
| getValueForDecl<IntegerValue>(ASTCtx, Before, "Foo"); |
| const auto &ValAfter = |
| getValueForDecl<IntegerValue>(ASTCtx, After, "Foo"); |
| EXPECT_NE(&ValBefore, &ValAfter); |
| }); |
| } |
| |
| TEST(TransferTest, VarDeclInitAssign) { |
| std::string Code = R"( |
| void target() { |
| int Foo; |
| int Bar = Foo; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const Value *FooVal = Env.getValue(*FooDecl); |
| ASSERT_TRUE(isa_and_nonnull<IntegerValue>(FooVal)); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| EXPECT_EQ(Env.getValue(*BarDecl), FooVal); |
| }); |
| } |
| |
| TEST(TransferTest, VarDeclInitAssignChained) { |
| std::string Code = R"( |
| void target() { |
| int Foo; |
| int Bar; |
| int Baz = (Bar = Foo); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const Value *FooVal = Env.getValue(*FooDecl); |
| ASSERT_TRUE(isa_and_nonnull<IntegerValue>(FooVal)); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz"); |
| ASSERT_THAT(BazDecl, NotNull()); |
| |
| EXPECT_EQ(Env.getValue(*BarDecl), FooVal); |
| EXPECT_EQ(Env.getValue(*BazDecl), FooVal); |
| }); |
| } |
| |
| TEST(TransferTest, VarDeclInitAssignPtrDeref) { |
| std::string Code = R"( |
| void target() { |
| int Foo; |
| int *Bar; |
| *(Bar) = Foo; |
| int Baz = *(Bar); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const Value *FooVal = Env.getValue(*FooDecl); |
| ASSERT_TRUE(isa_and_nonnull<IntegerValue>(FooVal)); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const auto *BarVal = cast<PointerValue>(Env.getValue(*BarDecl)); |
| EXPECT_EQ(Env.getValue(BarVal->getPointeeLoc()), FooVal); |
| |
| const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz"); |
| ASSERT_THAT(BazDecl, NotNull()); |
| |
| EXPECT_EQ(Env.getValue(*BazDecl), FooVal); |
| }); |
| } |
| |
| TEST(TransferTest, AssignToAndFromReference) { |
| std::string Code = R"( |
| void target() { |
| int Foo; |
| int Bar; |
| int &Baz = Foo; |
| // [[p1]] |
| Baz = Bar; |
| int Qux = Baz; |
| int &Quux = Baz; |
| // [[p2]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p1", "p2")); |
| const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1"); |
| const Environment &Env2 = getEnvironmentAtAnnotation(Results, "p2"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const Value *FooVal = Env1.getValue(*FooDecl); |
| ASSERT_TRUE(isa_and_nonnull<IntegerValue>(FooVal)); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const Value *BarVal = Env1.getValue(*BarDecl); |
| ASSERT_TRUE(isa_and_nonnull<IntegerValue>(BarVal)); |
| |
| const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz"); |
| ASSERT_THAT(BazDecl, NotNull()); |
| |
| EXPECT_EQ(Env1.getValue(*BazDecl), FooVal); |
| |
| EXPECT_EQ(Env2.getValue(*BazDecl), BarVal); |
| EXPECT_EQ(Env2.getValue(*FooDecl), BarVal); |
| |
| const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "Qux"); |
| ASSERT_THAT(QuxDecl, NotNull()); |
| EXPECT_EQ(Env2.getValue(*QuxDecl), BarVal); |
| |
| const ValueDecl *QuuxDecl = findValueDecl(ASTCtx, "Quux"); |
| ASSERT_THAT(QuuxDecl, NotNull()); |
| EXPECT_EQ(Env2.getValue(*QuuxDecl), BarVal); |
| }); |
| } |
| |
| TEST(TransferTest, MultipleParamDecls) { |
| std::string Code = R"( |
| void target(int Foo, int Bar) { |
| (void)0; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| 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)); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const StorageLocation *BarLoc = Env.getStorageLocation(*BarDecl); |
| ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(BarLoc)); |
| |
| const Value *BarVal = Env.getValue(*BarLoc); |
| EXPECT_TRUE(isa_and_nonnull<IntegerValue>(BarVal)); |
| }); |
| } |
| |
| TEST(TransferTest, StructParamDecl) { |
| std::string Code = R"( |
| struct A { |
| int Bar; |
| }; |
| |
| void target(A Foo) { |
| (void)Foo.Bar; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| 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<RecordStorageLocation>(Env.getStorageLocation(*FooDecl)); |
| EXPECT_TRUE(isa<IntegerValue>(getFieldValue(FooLoc, *BarDecl, Env))); |
| }); |
| } |
| |
| TEST(TransferTest, ReferenceParamDecl) { |
| std::string Code = R"( |
| struct A {}; |
| |
| void target(A &Foo) { |
| (void)0; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const StorageLocation *FooLoc = Env.getStorageLocation(*FooDecl); |
| ASSERT_TRUE(isa_and_nonnull<RecordStorageLocation>(FooLoc)); |
| }); |
| } |
| |
| TEST(TransferTest, PointerParamDecl) { |
| std::string Code = R"( |
| struct A {}; |
| |
| void target(A *Foo) { |
| (void)0; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| 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(); |
| EXPECT_TRUE(isa<RecordStorageLocation>(&FooPointeeLoc)); |
| }); |
| } |
| |
| TEST(TransferTest, StructMember) { |
| std::string Code = R"( |
| struct A { |
| int Bar; |
| }; |
| |
| void target(A Foo) { |
| int Baz = Foo.Bar; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| 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<RecordStorageLocation>(Env.getStorageLocation(*FooDecl)); |
| const auto *BarVal = |
| cast<IntegerValue>(getFieldValue(FooLoc, *BarDecl, Env)); |
| |
| const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz"); |
| ASSERT_THAT(BazDecl, NotNull()); |
| |
| EXPECT_EQ(Env.getValue(*BazDecl), BarVal); |
| }); |
| } |
| |
| TEST(TransferTest, StructMemberEnum) { |
| std::string Code = R"( |
| struct A { |
| int Bar; |
| enum E { ONE, TWO }; |
| }; |
| |
| void target(A Foo) { |
| A::E Baz = Foo.ONE; |
| // [[p]] |
| } |
| )"; |
| // Minimal expectations -- we're just testing that it doesn't crash, since |
| // enums aren't interpreted. |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| EXPECT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| }); |
| } |
| |
| TEST(TransferTest, DerivedBaseMemberClass) { |
| std::string Code = R"( |
| class A { |
| int ADefault; |
| protected: |
| int AProtected; |
| private: |
| int APrivate; |
| public: |
| int APublic; |
| |
| private: |
| friend void target(); |
| }; |
| |
| class B : public A { |
| int BDefault; |
| protected: |
| int BProtected; |
| private: |
| int BPrivate; |
| |
| private: |
| friend void target(); |
| }; |
| |
| void target() { |
| B Foo; |
| (void)Foo.ADefault; |
| (void)Foo.AProtected; |
| (void)Foo.APrivate; |
| (void)Foo.APublic; |
| (void)Foo.BDefault; |
| (void)Foo.BProtected; |
| (void)Foo.BPrivate; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| ASSERT_TRUE(FooDecl->getType()->isRecordType()); |
| |
| // Derived-class fields. |
| const FieldDecl *BDefaultDecl = nullptr; |
| const FieldDecl *BProtectedDecl = nullptr; |
| const FieldDecl *BPrivateDecl = nullptr; |
| for (const FieldDecl *Field : |
| FooDecl->getType()->getAsRecordDecl()->fields()) { |
| if (Field->getNameAsString() == "BDefault") { |
| BDefaultDecl = Field; |
| } else if (Field->getNameAsString() == "BProtected") { |
| BProtectedDecl = Field; |
| } else if (Field->getNameAsString() == "BPrivate") { |
| BPrivateDecl = Field; |
| } else { |
| FAIL() << "Unexpected field: " << Field->getNameAsString(); |
| } |
| } |
| ASSERT_THAT(BDefaultDecl, NotNull()); |
| ASSERT_THAT(BProtectedDecl, NotNull()); |
| ASSERT_THAT(BPrivateDecl, NotNull()); |
| |
| // Base-class fields. |
| const FieldDecl *ADefaultDecl = nullptr; |
| const FieldDecl *APrivateDecl = nullptr; |
| const FieldDecl *AProtectedDecl = nullptr; |
| const FieldDecl *APublicDecl = nullptr; |
| for (const clang::CXXBaseSpecifier &Base : |
| FooDecl->getType()->getAsCXXRecordDecl()->bases()) { |
| QualType BaseType = Base.getType(); |
| ASSERT_TRUE(BaseType->isRecordType()); |
| for (const FieldDecl *Field : BaseType->getAsRecordDecl()->fields()) { |
| if (Field->getNameAsString() == "ADefault") { |
| ADefaultDecl = Field; |
| } else if (Field->getNameAsString() == "AProtected") { |
| AProtectedDecl = Field; |
| } else if (Field->getNameAsString() == "APrivate") { |
| APrivateDecl = Field; |
| } else if (Field->getNameAsString() == "APublic") { |
| APublicDecl = Field; |
| } else { |
| FAIL() << "Unexpected field: " << Field->getNameAsString(); |
| } |
| } |
| } |
| ASSERT_THAT(ADefaultDecl, NotNull()); |
| ASSERT_THAT(AProtectedDecl, NotNull()); |
| ASSERT_THAT(APrivateDecl, NotNull()); |
| ASSERT_THAT(APublicDecl, NotNull()); |
| |
| ASSERT_TRUE( |
| isa<RecordStorageLocation>(Env.getStorageLocation(*FooDecl))); |
| }); |
| } |
| |
| static void derivedBaseMemberExpectations( |
| const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| ASSERT_TRUE(FooDecl->getType()->isRecordType()); |
| const FieldDecl *BarDecl = nullptr; |
| for (const clang::CXXBaseSpecifier &Base : |
| FooDecl->getType()->getAsCXXRecordDecl()->bases()) { |
| QualType BaseType = Base.getType(); |
| ASSERT_TRUE(BaseType->isStructureType()); |
| |
| for (const FieldDecl *Field : BaseType->getAsRecordDecl()->fields()) { |
| if (Field->getNameAsString() == "Bar") { |
| BarDecl = Field; |
| } else { |
| FAIL() << "Unexpected field: " << Field->getNameAsString(); |
| } |
| } |
| } |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const auto &FooLoc = |
| *cast<RecordStorageLocation>(Env.getStorageLocation(*FooDecl)); |
| EXPECT_NE(Env.getValue(*FooLoc.getChild(*BarDecl)), nullptr); |
| } |
| |
| TEST(TransferTest, DerivedBaseMemberStructDefault) { |
| std::string Code = R"( |
| struct A { |
| int Bar; |
| }; |
| struct B : public A { |
| }; |
| |
| void target() { |
| B Foo; |
| (void)Foo.Bar; |
| // [[p]] |
| } |
| )"; |
| runDataflow(Code, derivedBaseMemberExpectations); |
| } |
| |
| TEST(TransferTest, DerivedBaseMemberPrivateFriend) { |
| // Include an access to `Foo.Bar` to verify the analysis doesn't crash on that |
| // access. |
| std::string Code = R"( |
| struct A { |
| private: |
| friend void target(); |
| int Bar; |
| }; |
| struct B : public A { |
| }; |
| |
| void target() { |
| B Foo; |
| (void)Foo.Bar; |
| // [[p]] |
| } |
| )"; |
| runDataflow(Code, derivedBaseMemberExpectations); |
| } |
| |
| TEST(TransferTest, ClassMember) { |
| std::string Code = R"( |
| class A { |
| public: |
| int Bar; |
| }; |
| |
| void target(A Foo) { |
| int Baz = Foo.Bar; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| 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<RecordStorageLocation>(Env.getStorageLocation(*FooDecl)); |
| const auto *BarVal = |
| cast<IntegerValue>(getFieldValue(FooLoc, *BarDecl, Env)); |
| |
| const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz"); |
| ASSERT_THAT(BazDecl, NotNull()); |
| |
| EXPECT_EQ(Env.getValue(*BazDecl), BarVal); |
| }); |
| } |
| |
| TEST(TransferTest, BaseClassInitializer) { |
| using ast_matchers::cxxConstructorDecl; |
| using ast_matchers::hasName; |
| using ast_matchers::ofClass; |
| |
| std::string Code = R"( |
| class A { |
| public: |
| A(int I) : Bar(I) {} |
| int Bar; |
| }; |
| |
| class B : public A { |
| public: |
| B(int I) : A(I) { |
| (void)0; |
| // [[p]] |
| } |
| }; |
| )"; |
| ASSERT_THAT_ERROR( |
| checkDataflow<NoopAnalysis>( |
| AnalysisInputs<NoopAnalysis>( |
| Code, cxxConstructorDecl(ofClass(hasName("B"))), |
| [](ASTContext &C, Environment &) { return NoopAnalysis(C); }) |
| .withASTBuildArgs( |
| {"-fsyntax-only", "-fno-delayed-template-parsing", |
| "-std=" + std::string(LangStandard::getLangStandardForKind( |
| LangStandard::lang_cxx17) |
| .getName())}), |
| /*VerifyResults=*/ |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| const AnalysisOutputs &) { |
| // Regression test to verify that base-class initializers do not |
| // trigger an assertion. If we add support for such initializers in |
| // the future, we can expand this test to check more specific |
| // properties. |
| EXPECT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| }), |
| llvm::Succeeded()); |
| } |
| |
| TEST(TransferTest, FieldsDontHaveValuesInConstructor) { |
| // In a constructor, unlike in regular member functions, we don't want fields |
| // to be pre-initialized with values, because doing so is the job of the |
| // constructor. |
| std::string Code = R"( |
| struct target { |
| target() { |
| 0; |
| // [[p]] |
| // Mention the field so it is modeled; |
| Val; |
| } |
| |
| int Val; |
| }; |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| EXPECT_EQ(getFieldValue(Env.getThisPointeeStorageLocation(), "Val", |
| ASTCtx, Env), |
| nullptr); |
| }); |
| } |
| |
| TEST(TransferTest, FieldsDontHaveValuesInConstructorWithBaseClass) { |
| // See above, but for a class with a base class. |
| std::string Code = R"( |
| struct Base { |
| int BaseVal; |
| }; |
| |
| struct target : public Base { |
| target() { |
| 0; |
| // [[p]] |
| // Mention the fields so they are modeled. |
| BaseVal; |
| Val; |
| } |
| |
| int Val; |
| }; |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| // The field of the base class should already have been initialized with |
| // a value by the base constructor. |
| EXPECT_NE(getFieldValue(Env.getThisPointeeStorageLocation(), "BaseVal", |
| ASTCtx, Env), |
| nullptr); |
| EXPECT_EQ(getFieldValue(Env.getThisPointeeStorageLocation(), "Val", |
| ASTCtx, Env), |
| nullptr); |
| }); |
| } |
| |
| TEST(TransferTest, StructModeledFieldsWithAccessor) { |
| std::string Code = R"( |
| class S { |
| int *Ptr; |
| int *PtrNonConst; |
| int Int; |
| int IntWithInc; |
| int IntNotAccessed; |
| int IntRef; |
| public: |
| int *getPtr() const { return Ptr; } |
| int *getPtrNonConst() { return PtrNonConst; } |
| int getInt(int i) const { return Int; } |
| int getWithInc(int i) { IntWithInc += i; return IntWithInc; } |
| int getIntNotAccessed() const { return IntNotAccessed; } |
| int getIntNoDefinition() const; |
| int &getIntRef() { return IntRef; } |
| void returnVoid() const { return; } |
| }; |
| |
| void target() { |
| S s; |
| int *p1 = s.getPtr(); |
| int *p2 = s.getPtrNonConst(); |
| int i1 = s.getInt(1); |
| int i2 = s.getWithInc(1); |
| int i3 = s.getIntNoDefinition(); |
| int &iref = s.getIntRef(); |
| |
| // Regression test: Don't crash on an indirect call (which doesn't have |
| // an associated `CXXMethodDecl`). |
| auto ptr_to_member_fn = &S::getPtr; |
| p1 = (s.*ptr_to_member_fn)(); |
| |
| // Regression test: Don't crash on a return statement without a value. |
| s.returnVoid(); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = |
| getEnvironmentAtAnnotation(Results, "p"); |
| auto &SLoc = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "s"); |
| std::vector<const ValueDecl*> Fields; |
| for (auto [Field, _] : SLoc.children()) |
| Fields.push_back(Field); |
| // Only the fields that have simple accessor methods (that have a |
| // single statement body that returns the member variable) should be |
| // modeled. |
| ASSERT_THAT(Fields, UnorderedElementsAre( |
| findValueDecl(ASTCtx, "Ptr"), findValueDecl(ASTCtx, "PtrNonConst"), |
| findValueDecl(ASTCtx, "Int"), findValueDecl(ASTCtx, "IntRef"))); |
| }); |
| } |
| |
| TEST(TransferTest, StructModeledFieldsWithComplicatedInheritance) { |
| std::string Code = R"( |
| struct Base1 { |
| int base1_1; |
| int base1_2; |
| }; |
| struct Intermediate : Base1 { |
| int intermediate_1; |
| int intermediate_2; |
| }; |
| struct Base2 { |
| int base2_1; |
| int base2_2; |
| }; |
| struct MostDerived : public Intermediate, Base2 { |
| int most_derived_1; |
| int most_derived_2; |
| }; |
| |
| void target() { |
| MostDerived MD; |
| MD.base1_2 = 1; |
| MD.intermediate_2 = 1; |
| MD.base2_2 = 1; |
| MD.most_derived_2 = 1; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = |
| getEnvironmentAtAnnotation(Results, "p"); |
| |
| // Only the accessed fields should exist in the model. |
| auto &MDLoc = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "MD"); |
| std::vector<const ValueDecl*> Fields; |
| for (auto [Field, _] : MDLoc.children()) |
| Fields.push_back(Field); |
| ASSERT_THAT(Fields, UnorderedElementsAre( |
| findValueDecl(ASTCtx, "base1_2"), |
| findValueDecl(ASTCtx, "intermediate_2"), |
| findValueDecl(ASTCtx, "base2_2"), |
| findValueDecl(ASTCtx, "most_derived_2"))); |
| }); |
| } |
| |
| TEST(TransferTest, StructInitializerListWithComplicatedInheritance) { |
| std::string Code = R"( |
| struct Base1 { |
| int base1; |
| }; |
| struct Intermediate : Base1 { |
| int intermediate; |
| }; |
| struct Base2 { |
| int base2; |
| }; |
| struct MostDerived : public Intermediate, Base2 { |
| int most_derived; |
| }; |
| |
| void target() { |
| MostDerived MD = {}; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = |
| getEnvironmentAtAnnotation(Results, "p"); |
| |
| // When a struct is initialized with a initializer list, all the |
| // fields are considered "accessed", and therefore do exist. |
| auto &MD = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "MD"); |
| ASSERT_THAT(cast<IntegerValue>( |
| getFieldValue(&MD, *findValueDecl(ASTCtx, "base1"), Env)), |
| NotNull()); |
| ASSERT_THAT(cast<IntegerValue>( |
| getFieldValue(&MD, *findValueDecl(ASTCtx, "intermediate"), Env)), |
| NotNull()); |
| ASSERT_THAT(cast<IntegerValue>( |
| getFieldValue(&MD, *findValueDecl(ASTCtx, "base2"), Env)), |
| NotNull()); |
| ASSERT_THAT(cast<IntegerValue>( |
| getFieldValue(&MD, *findValueDecl(ASTCtx, "most_derived"), Env)), |
| NotNull()); |
| }); |
| } |
| |
| TEST(TransferTest, ReferenceMember) { |
| std::string Code = R"( |
| struct A { |
| int &Bar; |
| }; |
| |
| void target(A Foo) { |
| int Baz = Foo.Bar; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| 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<RecordStorageLocation>(Env.getStorageLocation(*FooDecl)); |
| const auto *BarReferentVal = |
| cast<IntegerValue>(getFieldValue(FooLoc, *BarDecl, Env)); |
| |
| const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz"); |
| ASSERT_THAT(BazDecl, NotNull()); |
| |
| EXPECT_EQ(Env.getValue(*BazDecl), BarReferentVal); |
| }); |
| } |
| |
| TEST(TransferTest, StructThisMember) { |
| std::string Code = R"( |
| struct A { |
| int Bar; |
| |
| struct B { |
| int Baz; |
| }; |
| |
| B Qux; |
| |
| void target() { |
| int Foo = Bar; |
| int Quux = Qux.Baz; |
| // [[p]] |
| } |
| }; |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const auto *ThisLoc = Env.getThisPointeeStorageLocation(); |
| ASSERT_THAT(ThisLoc, NotNull()); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const auto *BarLoc = |
| cast<ScalarStorageLocation>(ThisLoc->getChild(*BarDecl)); |
| ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(BarLoc)); |
| |
| const Value *BarVal = Env.getValue(*BarLoc); |
| ASSERT_TRUE(isa_and_nonnull<IntegerValue>(BarVal)); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| EXPECT_EQ(Env.getValue(*FooDecl), BarVal); |
| |
| const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "Qux"); |
| ASSERT_THAT(QuxDecl, NotNull()); |
| |
| ASSERT_TRUE(QuxDecl->getType()->isStructureType()); |
| auto QuxFields = QuxDecl->getType()->getAsRecordDecl()->fields(); |
| |
| FieldDecl *BazDecl = nullptr; |
| for (FieldDecl *Field : QuxFields) { |
| if (Field->getNameAsString() == "Baz") { |
| BazDecl = Field; |
| } else { |
| FAIL() << "Unexpected field: " << Field->getNameAsString(); |
| } |
| } |
| ASSERT_THAT(BazDecl, NotNull()); |
| |
| const auto *QuxLoc = |
| cast<RecordStorageLocation>(ThisLoc->getChild(*QuxDecl)); |
| |
| const auto *BazVal = |
| cast<IntegerValue>(getFieldValue(QuxLoc, *BazDecl, Env)); |
| |
| const ValueDecl *QuuxDecl = findValueDecl(ASTCtx, "Quux"); |
| ASSERT_THAT(QuuxDecl, NotNull()); |
| EXPECT_EQ(Env.getValue(*QuuxDecl), BazVal); |
| }); |
| } |
| |
| TEST(TransferTest, ClassThisMember) { |
| std::string Code = R"( |
| class A { |
| int Bar; |
| |
| class B { |
| public: |
| int Baz; |
| }; |
| |
| B Qux; |
| |
| void target() { |
| int Foo = Bar; |
| int Quux = Qux.Baz; |
| // [[p]] |
| } |
| }; |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const auto *ThisLoc = Env.getThisPointeeStorageLocation(); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const auto *BarLoc = |
| cast<ScalarStorageLocation>(ThisLoc->getChild(*BarDecl)); |
| ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(BarLoc)); |
| |
| const Value *BarVal = Env.getValue(*BarLoc); |
| ASSERT_TRUE(isa_and_nonnull<IntegerValue>(BarVal)); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| EXPECT_EQ(Env.getValue(*FooDecl), BarVal); |
| |
| const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "Qux"); |
| ASSERT_THAT(QuxDecl, NotNull()); |
| |
| ASSERT_TRUE(QuxDecl->getType()->isClassType()); |
| auto QuxFields = QuxDecl->getType()->getAsRecordDecl()->fields(); |
| |
| FieldDecl *BazDecl = nullptr; |
| for (FieldDecl *Field : QuxFields) { |
| if (Field->getNameAsString() == "Baz") { |
| BazDecl = Field; |
| } else { |
| FAIL() << "Unexpected field: " << Field->getNameAsString(); |
| } |
| } |
| ASSERT_THAT(BazDecl, NotNull()); |
| |
| const auto *QuxLoc = |
| cast<RecordStorageLocation>(ThisLoc->getChild(*QuxDecl)); |
| |
| const auto *BazVal = |
| cast<IntegerValue>(getFieldValue(QuxLoc, *BazDecl, Env)); |
| |
| const ValueDecl *QuuxDecl = findValueDecl(ASTCtx, "Quux"); |
| ASSERT_THAT(QuuxDecl, NotNull()); |
| EXPECT_EQ(Env.getValue(*QuuxDecl), BazVal); |
| }); |
| } |
| |
| TEST(TransferTest, UnionThisMember) { |
| std::string Code = R"( |
| union A { |
| int Foo; |
| int Bar; |
| |
| void target() { |
| A a; |
| // Mention the fields to ensure they're included in the analysis. |
| (void)a.Foo; |
| (void)a.Bar; |
| // [[p]] |
| } |
| }; |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const auto *ThisLoc = Env.getThisPointeeStorageLocation(); |
| ASSERT_THAT(ThisLoc, NotNull()); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const auto *FooLoc = |
| cast<ScalarStorageLocation>(ThisLoc->getChild(*FooDecl)); |
| ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(FooLoc)); |
| |
| const Value *FooVal = Env.getValue(*FooLoc); |
| ASSERT_TRUE(isa_and_nonnull<IntegerValue>(FooVal)); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const auto *BarLoc = |
| cast<ScalarStorageLocation>(ThisLoc->getChild(*BarDecl)); |
| ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(BarLoc)); |
| |
| const Value *BarVal = Env.getValue(*BarLoc); |
| ASSERT_TRUE(isa_and_nonnull<IntegerValue>(BarVal)); |
| }); |
| } |
| |
| TEST(TransferTest, StructThisInLambda) { |
| std::string ThisCaptureCode = R"( |
| struct A { |
| void frob() { |
| [this]() { |
| int Foo = Bar; |
| // [[p1]] |
| }(); |
| } |
| |
| int Bar; |
| }; |
| )"; |
| runDataflow( |
| ThisCaptureCode, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p1")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p1"); |
| |
| const auto *ThisLoc = Env.getThisPointeeStorageLocation(); |
| ASSERT_THAT(ThisLoc, NotNull()); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const auto *BarLoc = |
| cast<ScalarStorageLocation>(ThisLoc->getChild(*BarDecl)); |
| ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(BarLoc)); |
| |
| const Value *BarVal = Env.getValue(*BarLoc); |
| ASSERT_TRUE(isa_and_nonnull<IntegerValue>(BarVal)); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| EXPECT_EQ(Env.getValue(*FooDecl), BarVal); |
| }, |
| LangStandard::lang_cxx17, /*ApplyBuiltinTransfer=*/true, "operator()"); |
| |
| std::string RefCaptureDefaultCode = R"( |
| struct A { |
| void frob() { |
| [&]() { |
| int Foo = Bar; |
| // [[p2]] |
| }(); |
| } |
| |
| int Bar; |
| }; |
| )"; |
| runDataflow( |
| RefCaptureDefaultCode, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p2")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p2"); |
| |
| const auto *ThisLoc = Env.getThisPointeeStorageLocation(); |
| ASSERT_THAT(ThisLoc, NotNull()); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const auto *BarLoc = |
| cast<ScalarStorageLocation>(ThisLoc->getChild(*BarDecl)); |
| ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(BarLoc)); |
| |
| const Value *BarVal = Env.getValue(*BarLoc); |
| ASSERT_TRUE(isa_and_nonnull<IntegerValue>(BarVal)); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| EXPECT_EQ(Env.getValue(*FooDecl), BarVal); |
| }, |
| LangStandard::lang_cxx17, /*ApplyBuiltinTransfer=*/true, "operator()"); |
| |
| std::string FreeFunctionLambdaCode = R"( |
| void foo() { |
| int Bar; |
| [&]() { |
| int Foo = Bar; |
| // [[p3]] |
| }(); |
| } |
| )"; |
| runDataflow( |
| FreeFunctionLambdaCode, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p3")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p3"); |
| |
| EXPECT_THAT(Env.getThisPointeeStorageLocation(), IsNull()); |
| }, |
| LangStandard::lang_cxx17, /*ApplyBuiltinTransfer=*/true, "operator()"); |
| } |
| |
| TEST(TransferTest, ConstructorInitializer) { |
| std::string Code = R"( |
| struct target { |
| int Bar; |
| |
| target(int Foo) : Bar(Foo) { |
| int Qux = Bar; |
| // [[p]] |
| } |
| }; |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const auto *ThisLoc = Env.getThisPointeeStorageLocation(); |
| ASSERT_THAT(ThisLoc, NotNull()); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const auto *FooVal = cast<IntegerValue>(Env.getValue(*FooDecl)); |
| |
| const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "Qux"); |
| ASSERT_THAT(QuxDecl, NotNull()); |
| EXPECT_EQ(Env.getValue(*QuxDecl), FooVal); |
| }); |
| } |
| |
| TEST(TransferTest, DefaultInitializer) { |
| std::string Code = R"( |
| struct target { |
| int Bar; |
| int Baz = Bar; |
| |
| target(int Foo) : Bar(Foo) { |
| int Qux = Baz; |
| // [[p]] |
| } |
| }; |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const auto *ThisLoc = Env.getThisPointeeStorageLocation(); |
| ASSERT_THAT(ThisLoc, NotNull()); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const auto *FooVal = cast<IntegerValue>(Env.getValue(*FooDecl)); |
| |
| const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "Qux"); |
| ASSERT_THAT(QuxDecl, NotNull()); |
| EXPECT_EQ(Env.getValue(*QuxDecl), FooVal); |
| }); |
| } |
| |
| TEST(TransferTest, DefaultInitializerReference) { |
| std::string Code = R"( |
| struct target { |
| int &Bar; |
| int &Baz = Bar; |
| |
| target(int &Foo) : Bar(Foo) { |
| int &Qux = Baz; |
| // [[p]] |
| } |
| }; |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const auto *ThisLoc = Env.getThisPointeeStorageLocation(); |
| ASSERT_THAT(ThisLoc, NotNull()); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const auto *FooLoc = Env.getStorageLocation(*FooDecl); |
| |
| const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "Qux"); |
| ASSERT_THAT(QuxDecl, NotNull()); |
| |
| const auto *QuxLoc = Env.getStorageLocation(*QuxDecl); |
| EXPECT_EQ(QuxLoc, FooLoc); |
| }); |
| } |
| |
| TEST(TransferTest, TemporaryObject) { |
| std::string Code = R"( |
| struct A { |
| int Bar; |
| }; |
| |
| void target() { |
| A Foo = A(); |
| (void)Foo.Bar; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const auto *FooLoc = |
| cast<RecordStorageLocation>(Env.getStorageLocation(*FooDecl)); |
| EXPECT_TRUE(isa<IntegerValue>(getFieldValue(FooLoc, *BarDecl, Env))); |
| }); |
| } |
| |
| TEST(TransferTest, ElidableConstructor) { |
| // This test is effectively the same as TransferTest.TemporaryObject, but |
| // the code is compiled as C++14. |
| std::string Code = R"( |
| struct A { |
| int Bar; |
| }; |
| |
| void target() { |
| A Foo = A(); |
| (void)Foo.Bar; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const auto *FooLoc = |
| cast<RecordStorageLocation>(Env.getStorageLocation(*FooDecl)); |
| EXPECT_TRUE(isa<IntegerValue>(getFieldValue(FooLoc, *BarDecl, Env))); |
| }, |
| LangStandard::lang_cxx14); |
| } |
| |
| TEST(TransferTest, AssignmentOperator) { |
| std::string Code = R"( |
| struct A { |
| int Baz; |
| }; |
| |
| void target() { |
| A Foo = { 1 }; |
| A Bar = { 2 }; |
| // [[p1]] |
| A &Rval = (Foo = Bar); |
| // [[p2]] |
| Foo.Baz = 3; |
| // [[p3]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| 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()); |
| |
| // Before copy assignment. |
| { |
| const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1"); |
| |
| const auto *FooLoc1 = |
| cast<RecordStorageLocation>(Env1.getStorageLocation(*FooDecl)); |
| const auto *BarLoc1 = |
| cast<RecordStorageLocation>(Env1.getStorageLocation(*BarDecl)); |
| EXPECT_FALSE(recordsEqual(*FooLoc1, *BarLoc1, Env1)); |
| |
| const auto *FooBazVal1 = |
| cast<IntegerValue>(getFieldValue(FooLoc1, *BazDecl, Env1)); |
| const auto *BarBazVal1 = |
| cast<IntegerValue>(getFieldValue(BarLoc1, *BazDecl, Env1)); |
| EXPECT_NE(FooBazVal1, BarBazVal1); |
| } |
| |
| // After copy assignment. |
| { |
| const Environment &Env2 = getEnvironmentAtAnnotation(Results, "p2"); |
| |
| const auto *FooLoc2 = |
| cast<RecordStorageLocation>(Env2.getStorageLocation(*FooDecl)); |
| const auto *BarLoc2 = |
| cast<RecordStorageLocation>(Env2.getStorageLocation(*BarDecl)); |
| |
| EXPECT_TRUE(recordsEqual(*FooLoc2, *BarLoc2, Env2)); |
| EXPECT_EQ(&getLocForDecl(ASTCtx, Env2, "Rval"), FooLoc2); |
| |
| const auto *FooBazVal2 = |
| cast<IntegerValue>(getFieldValue(FooLoc2, *BazDecl, Env2)); |
| const auto *BarBazVal2 = |
| cast<IntegerValue>(getFieldValue(BarLoc2, *BazDecl, Env2)); |
| EXPECT_EQ(FooBazVal2, BarBazVal2); |
| } |
| |
| // After value update. |
| { |
| const Environment &Env3 = getEnvironmentAtAnnotation(Results, "p3"); |
| |
| const auto *FooLoc3 = |
| cast<RecordStorageLocation>(Env3.getStorageLocation(*FooDecl)); |
| const auto *BarLoc3 = |
| cast<RecordStorageLocation>(Env3.getStorageLocation(*BarDecl)); |
| EXPECT_FALSE(recordsEqual(*FooLoc3, *BarLoc3, Env3)); |
| |
| const auto *FooBazVal3 = |
| cast<IntegerValue>(getFieldValue(FooLoc3, *BazDecl, Env3)); |
| const auto *BarBazVal3 = |
| cast<IntegerValue>(getFieldValue(BarLoc3, *BazDecl, Env3)); |
| EXPECT_NE(FooBazVal3, BarBazVal3); |
| } |
| }); |
| } |
| |
| // It's legal for the assignment operator to take its source parameter by value. |
| // Check that we handle this correctly. (This is a repro -- we used to |
| // assert-fail on this.) |
| TEST(TransferTest, AssignmentOperator_ArgByValue) { |
| std::string Code = R"( |
| struct A { |
| int Baz; |
| A &operator=(A); |
| }; |
| |
| void target() { |
| A Foo = { 1 }; |
| A Bar = { 2 }; |
| Foo = Bar; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz"); |
| |
| const auto &FooLoc = |
| getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "Foo"); |
| const auto &BarLoc = |
| getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "Bar"); |
| |
| const auto *FooBazVal = |
| cast<IntegerValue>(getFieldValue(&FooLoc, *BazDecl, Env)); |
| const auto *BarBazVal = |
| cast<IntegerValue>(getFieldValue(&BarLoc, *BazDecl, Env)); |
| EXPECT_EQ(FooBazVal, BarBazVal); |
| }); |
| } |
| |
| TEST(TransferTest, AssignmentOperatorFromBase) { |
| std::string Code = R"( |
| struct Base { |
| int base; |
| }; |
| struct Derived : public Base { |
| using Base::operator=; |
| int derived; |
| }; |
| void target(Base B, Derived D) { |
| D.base = 1; |
| D.derived = 1; |
| // [[before]] |
| D = B; |
| // [[after]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &EnvBefore = |
| getEnvironmentAtAnnotation(Results, "before"); |
| const Environment &EnvAfter = |
| getEnvironmentAtAnnotation(Results, "after"); |
| |
| auto &BLoc = |
| getLocForDecl<RecordStorageLocation>(ASTCtx, EnvBefore, "B"); |
| auto &DLoc = |
| getLocForDecl<RecordStorageLocation>(ASTCtx, EnvBefore, "D"); |
| |
| EXPECT_NE(getFieldValue(&BLoc, "base", ASTCtx, EnvBefore), |
| getFieldValue(&DLoc, "base", ASTCtx, EnvBefore)); |
| EXPECT_EQ(getFieldValue(&BLoc, "base", ASTCtx, EnvAfter), |
| getFieldValue(&DLoc, "base", ASTCtx, EnvAfter)); |
| |
| EXPECT_EQ(getFieldValue(&DLoc, "derived", ASTCtx, EnvBefore), |
| getFieldValue(&DLoc, "derived", ASTCtx, EnvAfter)); |
| }); |
| } |
| |
| TEST(TransferTest, AssignmentOperatorFromCallResult) { |
| std::string Code = R"( |
| struct A {}; |
| A ReturnA(); |
| |
| void target() { |
| A MyA; |
| MyA = ReturnA(); |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| // As of this writing, we don't produce a `Value` for the call |
| // `ReturnA()`. The only condition we're testing for is that the |
| // analysis should not crash in this case. |
| }); |
| } |
| |
| TEST(TransferTest, AssignmentOperatorWithInitAndInheritance) { |
| // This is a crash repro. |
| std::string Code = R"( |
| struct B { int Foo; }; |
| struct S : public B {}; |
| void target() { |
| S S1 = { 1 }; |
| S S2; |
| S S3; |
| S1 = S2; // Only Dst has InitListExpr. |
| S3 = S1; // Only Src has InitListExpr. |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) {}); |
| } |
| |
| TEST(TransferTest, AssignmentOperatorReturnsVoid) { |
| // This is a crash repro. |
| std::string Code = R"( |
| struct S { |
| void operator=(S&& other); |
| }; |
| void target() { |
| S s; |
| s = S(); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) {}); |
| } |
| |
| TEST(TransferTest, AssignmentOperatorReturnsByValue) { |
| // This is a crash repro. |
| std::string Code = R"( |
| struct S { |
| S operator=(const S&); |
| int i; |
| }; |
| void target() { |
| S S1 = { 1 }; |
| S S2 = { 2 }; |
| S S3 = { 3 }; |
| // [[before]] |
| // Test that the returned value is modeled by assigning to another value. |
| S1 = (S2 = S3); |
| (void)0; |
| // [[after]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const ValueDecl *S1Decl = findValueDecl(ASTCtx, "S1"); |
| const ValueDecl *S2Decl = findValueDecl(ASTCtx, "S2"); |
| const ValueDecl *S3Decl = findValueDecl(ASTCtx, "S3"); |
| |
| const Environment &EnvBefore = |
| getEnvironmentAtAnnotation(Results, "before"); |
| |
| EXPECT_FALSE(recordsEqual( |
| *EnvBefore.get<RecordStorageLocation>(*S1Decl), |
| *EnvBefore.get<RecordStorageLocation>(*S2Decl), EnvBefore)); |
| EXPECT_FALSE(recordsEqual( |
| *EnvBefore.get<RecordStorageLocation>(*S2Decl), |
| *EnvBefore.get<RecordStorageLocation>(*S3Decl), EnvBefore)); |
| |
| const Environment &EnvAfter = |
| getEnvironmentAtAnnotation(Results, "after"); |
| |
| EXPECT_TRUE(recordsEqual(*EnvAfter.get<RecordStorageLocation>(*S1Decl), |
| *EnvAfter.get<RecordStorageLocation>(*S2Decl), |
| EnvAfter)); |
| EXPECT_TRUE(recordsEqual(*EnvAfter.get<RecordStorageLocation>(*S2Decl), |
| *EnvAfter.get<RecordStorageLocation>(*S3Decl), |
| EnvAfter)); |
| }); |
| } |
| |
| TEST(TransferTest, AssignmentOperatorReturnsDifferentTypeByRef) { |
| // This is a crash repro. |
| std::string Code = R"( |
| struct DifferentType {}; |
| struct S { |
| DifferentType& operator=(const S&); |
| }; |
| void target() { |
| S s; |
| s = S(); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) {}); |
| } |
| |
| TEST(TransferTest, AssignmentOperatorReturnsDifferentTypeByValue) { |
| // This is a crash repro. |
| std::string Code = R"( |
| struct DifferentType {}; |
| struct S { |
| DifferentType operator=(const S&); |
| }; |
| void target() { |
| S s; |
| s = S(); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) {}); |
| } |
| |
| TEST(TransferTest, InitListExprAsXValue) { |
| // This is a crash repro. |
| std::string Code = R"( |
| void target() { |
| bool&& Foo{false}; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| const auto &FooVal = getValueForDecl<BoolValue>(ASTCtx, Env, "Foo"); |
| ASSERT_TRUE(FooVal.formula().isLiteral(false)); |
| }); |
| } |
| |
| TEST(TransferTest, ArrayInitListExprOneRecordElement) { |
| // This is a crash repro. |
| std::string Code = R"cc( |
| struct S {}; |
| |
| void target() { S foo[] = {S()}; } |
| )cc"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| // Just verify that it doesn't crash. |
| }); |
| } |
| |
| TEST(TransferTest, InitListExprAsUnion) { |
| // This is a crash repro. |
| std::string Code = R"cc( |
| class target { |
| union { |
| int *a; |
| bool *b; |
| } F; |
| |
| public: |
| constexpr target() : F{nullptr} { |
| int *null = nullptr; |
| F.b; // Make sure we reference 'b' so it is modeled. |
| // [[p]] |
| } |
| }; |
| )cc"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| auto &FLoc = getFieldLoc<RecordStorageLocation>( |
| *Env.getThisPointeeStorageLocation(), "F", ASTCtx); |
| auto *AVal = cast<PointerValue>(getFieldValue(&FLoc, "a", ASTCtx, Env)); |
| EXPECT_EQ(AVal, &getValueForDecl<PointerValue>(ASTCtx, Env, "null")); |
| EXPECT_EQ(getFieldValue(&FLoc, "b", ASTCtx, Env), nullptr); |
| }); |
| } |
| |
| TEST(TransferTest, EmptyInitListExprForUnion) { |
| // This is a crash repro. |
| std::string Code = R"cc( |
| class target { |
| union { |
| int *a; |
| bool *b; |
| } F; |
| |
| public: |
| // Empty initializer list means that `F` is aggregate-initialized. |
| // For a union, this has the effect that the first member of the union |
| // is copy-initialized from an empty initializer list; in this specific |
| // case, this has the effect of initializing `a` with null. |
| constexpr target() : F{} { |
| int *null = nullptr; |
| F.b; // Make sure we reference 'b' so it is modeled. |
| // [[p]] |
| } |
| }; |
| )cc"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| auto &FLoc = getFieldLoc<RecordStorageLocation>( |
| *Env.getThisPointeeStorageLocation(), "F", ASTCtx); |
| auto *AVal = cast<PointerValue>(getFieldValue(&FLoc, "a", ASTCtx, Env)); |
| EXPECT_EQ(AVal, &getValueForDecl<PointerValue>(ASTCtx, Env, "null")); |
| EXPECT_EQ(getFieldValue(&FLoc, "b", ASTCtx, Env), nullptr); |
| }); |
| } |
| |
| TEST(TransferTest, EmptyInitListExprForStruct) { |
| std::string Code = R"cc( |
| class target { |
| struct { |
| int *a; |
| bool *b; |
| } F; |
| |
| public: |
| constexpr target() : F{} { |
| int *NullIntPtr = nullptr; |
| bool *NullBoolPtr = nullptr; |
| // [[p]] |
| } |
| }; |
| )cc"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| auto &FLoc = getFieldLoc<RecordStorageLocation>( |
| *Env.getThisPointeeStorageLocation(), "F", ASTCtx); |
| auto *AVal = cast<PointerValue>(getFieldValue(&FLoc, "a", ASTCtx, Env)); |
| EXPECT_EQ(AVal, |
| &getValueForDecl<PointerValue>(ASTCtx, Env, "NullIntPtr")); |
| auto *BVal = cast<PointerValue>(getFieldValue(&FLoc, "b", ASTCtx, Env)); |
| EXPECT_EQ(BVal, |
| &getValueForDecl<PointerValue>(ASTCtx, Env, "NullBoolPtr")); |
| }); |
| } |
| |
| TEST(TransferTest, CopyConstructor) { |
| std::string Code = R"( |
| struct A { |
| int Baz; |
| }; |
| |
| void target() { |
| A Foo = { 1 }; |
| A Bar = Foo; |
| // [[after_copy]] |
| Foo.Baz = 2; |
| // [[after_update]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| 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()); |
| |
| // after_copy |
| { |
| const Environment &Env = |
| getEnvironmentAtAnnotation(Results, "after_copy"); |
| |
| const auto *FooLoc = |
| cast<RecordStorageLocation>(Env.getStorageLocation(*FooDecl)); |
| const auto *BarLoc = |
| cast<RecordStorageLocation>(Env.getStorageLocation(*BarDecl)); |
| |
| // The records compare equal. |
| EXPECT_TRUE(recordsEqual(*FooLoc, *BarLoc, Env)); |
| |
| // In particular, the value of `Baz` in both records is the same. |
| const auto *FooBazVal = |
| cast<IntegerValue>(getFieldValue(FooLoc, *BazDecl, Env)); |
| const auto *BarBazVal = |
| cast<IntegerValue>(getFieldValue(BarLoc, *BazDecl, Env)); |
| EXPECT_EQ(FooBazVal, BarBazVal); |
| } |
| |
| // after_update |
| { |
| const Environment &Env = |
| getEnvironmentAtAnnotation(Results, "after_update"); |
| |
| const auto *FooLoc = |
| cast<RecordStorageLocation>(Env.getStorageLocation(*FooDecl)); |
| const auto *BarLoc = |
| cast<RecordStorageLocation>(Env.getStorageLocation(*BarDecl)); |
| |
| EXPECT_FALSE(recordsEqual(*FooLoc, *BarLoc, Env)); |
| |
| const auto *FooBazVal = |
| cast<IntegerValue>(getFieldValue(FooLoc, *BazDecl, Env)); |
| const auto *BarBazVal = |
| cast<IntegerValue>(getFieldValue(BarLoc, *BazDecl, Env)); |
| EXPECT_NE(FooBazVal, BarBazVal); |
| } |
| }); |
| } |
| |
| TEST(TransferTest, CopyConstructorWithDefaultArgument) { |
| std::string Code = R"( |
| struct A { |
| int Baz; |
| A() = default; |
| A(const A& a, bool def = true) { Baz = a.Baz; } |
| }; |
| |
| void target() { |
| A Foo; |
| (void)Foo.Baz; |
| A Bar = Foo; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| 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 auto *FooLoc = |
| cast<RecordStorageLocation>(Env.getStorageLocation(*FooDecl)); |
| const auto *BarLoc = |
| cast<RecordStorageLocation>(Env.getStorageLocation(*BarDecl)); |
| EXPECT_TRUE(recordsEqual(*FooLoc, *BarLoc, Env)); |
| |
| const auto *FooBazVal = |
| cast<IntegerValue>(getFieldValue(FooLoc, *BazDecl, Env)); |
| const auto *BarBazVal = |
| cast<IntegerValue>(getFieldValue(BarLoc, *BazDecl, Env)); |
| EXPECT_EQ(FooBazVal, BarBazVal); |
| }); |
| } |
| |
| TEST(TransferTest, CopyConstructorWithParens) { |
| std::string Code = R"( |
| struct A { |
| int Baz; |
| }; |
| |
| void target() { |
| A Foo; |
| (void)Foo.Baz; |
| A Bar((A(Foo))); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| 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 auto *FooLoc = |
| cast<RecordStorageLocation>(Env.getStorageLocation(*FooDecl)); |
| const auto *BarLoc = |
| cast<RecordStorageLocation>(Env.getStorageLocation(*BarDecl)); |
| EXPECT_TRUE(recordsEqual(*FooLoc, *BarLoc, Env)); |
| |
| const auto *FooBazVal = |
| cast<IntegerValue>(getFieldValue(FooLoc, *BazDecl, Env)); |
| const auto *BarBazVal = |
| cast<IntegerValue>(getFieldValue(BarLoc, *BazDecl, Env)); |
| EXPECT_EQ(FooBazVal, BarBazVal); |
| }); |
| } |
| |
| TEST(TransferTest, CopyConstructorWithInitializerListAsSyntacticSugar) { |
| std::string Code = R"( |
| struct A { |
| int Baz; |
| }; |
| void target() { |
| A Foo = {3}; |
| (void)Foo.Baz; |
| A Bar = {A(Foo)}; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz"); |
| |
| const auto &FooLoc = |
| getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "Foo"); |
| const auto &BarLoc = |
| getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "Bar"); |
| |
| const auto *FooBazVal = |
| cast<IntegerValue>(getFieldValue(&FooLoc, *BazDecl, Env)); |
| const auto *BarBazVal = |
| cast<IntegerValue>(getFieldValue(&BarLoc, *BazDecl, Env)); |
| EXPECT_EQ(FooBazVal, BarBazVal); |
| }); |
| } |
| |
| TEST(TransferTest, CopyConstructorArgIsRefReturnedByFunction) { |
| // This is a crash repro. |
| std::string Code = R"( |
| struct S {}; |
| const S &returnsSRef(); |
| void target() { |
| S s(returnsSRef()); |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) {}); |
| } |
| |
| TEST(TransferTest, MoveConstructor) { |
| std::string Code = R"( |
| namespace std { |
| |
| template <typename T> struct remove_reference { using type = T; }; |
| template <typename T> struct remove_reference<T&> { using type = T; }; |
| template <typename T> struct remove_reference<T&&> { using type = T; }; |
| |
| template <typename T> |
| using remove_reference_t = typename remove_reference<T>::type; |
| |
| template <typename T> |
| std::remove_reference_t<T>&& move(T&& x); |
| |
| } // namespace std |
| |
| struct A { |
| int Baz; |
| }; |
| |
| void target() { |
| A Foo; |
| A Bar; |
| (void)Foo.Baz; |
| // [[p1]] |
| Foo = std::move(Bar); |
| // [[p2]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p1", "p2")); |
| const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1"); |
| const Environment &Env2 = getEnvironmentAtAnnotation(Results, "p2"); |
| |
| 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 auto *FooLoc1 = |
| cast<RecordStorageLocation>(Env1.getStorageLocation(*FooDecl)); |
| const auto *BarLoc1 = |
| cast<RecordStorageLocation>(Env1.getStorageLocation(*BarDecl)); |
| |
| EXPECT_FALSE(recordsEqual(*FooLoc1, *BarLoc1, Env1)); |
| |
| const auto *FooBazVal1 = |
| cast<IntegerValue>(getFieldValue(FooLoc1, *BazDecl, Env1)); |
| const auto *BarBazVal1 = |
| cast<IntegerValue>(getFieldValue(BarLoc1, *BazDecl, Env1)); |
| EXPECT_NE(FooBazVal1, BarBazVal1); |
| |
| const auto *FooLoc2 = |
| cast<RecordStorageLocation>(Env2.getStorageLocation(*FooDecl)); |
| EXPECT_TRUE(recordsEqual(*FooLoc2, Env2, *BarLoc1, Env1)); |
| |
| const auto *FooBazVal2 = |
| cast<IntegerValue>(getFieldValue(FooLoc1, *BazDecl, Env2)); |
| EXPECT_EQ(FooBazVal2, BarBazVal1); |
| }); |
| } |
| |
| TEST(TransferTest, BindTemporary) { |
| std::string Code = R"( |
| struct A { |
| virtual ~A() = default; |
| |
| int Baz; |
| }; |
| |
| void target(A Foo) { |
| int Bar = A(Foo).Baz; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| 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 auto &FooLoc = |
| *cast<RecordStorageLocation>(Env.getStorageLocation(*FooDecl)); |
| const auto *BarVal = cast<IntegerValue>(Env.getValue(*BarDecl)); |
| EXPECT_EQ(BarVal, getFieldValue(&FooLoc, *BazDecl, Env)); |
| }); |
| } |
| |
| TEST(TransferTest, ResultObjectLocation) { |
| std::string Code = R"( |
| struct A { |
| virtual ~A() = default; |
| }; |
| |
| void target() { |
| 0, A(); |
| (void)0; // [[p]] |
| } |
| )"; |
| using ast_matchers::binaryOperator; |
| using ast_matchers::cxxBindTemporaryExpr; |
| using ast_matchers::cxxTemporaryObjectExpr; |
| using ast_matchers::exprWithCleanups; |
| using ast_matchers::has; |
| using ast_matchers::hasOperatorName; |
| using ast_matchers::hasRHS; |
| using ast_matchers::match; |
| using ast_matchers::selectFirst; |
| using ast_matchers::traverse; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| // The expression `0, A()` in the code above produces the following |
| // structure, consisting of four prvalues of record type. |
| // `Env.getResultObjectLocation()` should return the same location for |
| // all of these. |
| auto MatchResult = match( |
| traverse(TK_AsIs, |
| exprWithCleanups( |
| has(binaryOperator( |
| hasOperatorName(","), |
| hasRHS(cxxBindTemporaryExpr( |
| has(cxxTemporaryObjectExpr().bind( |
| "toe"))) |
| .bind("bte"))) |
| .bind("comma"))) |
| .bind("ewc")), |
| ASTCtx); |
| auto *TOE = selectFirst<CXXTemporaryObjectExpr>("toe", MatchResult); |
| ASSERT_NE(TOE, nullptr); |
| auto *Comma = selectFirst<BinaryOperator>("comma", MatchResult); |
| ASSERT_NE(Comma, nullptr); |
| auto *EWC = selectFirst<ExprWithCleanups>("ewc", MatchResult); |
| ASSERT_NE(EWC, nullptr); |
| auto *BTE = selectFirst<CXXBindTemporaryExpr>("bte", MatchResult); |
| ASSERT_NE(BTE, nullptr); |
| |
| RecordStorageLocation &Loc = Env.getResultObjectLocation(*TOE); |
| EXPECT_EQ(&Loc, &Env.getResultObjectLocation(*Comma)); |
| EXPECT_EQ(&Loc, &Env.getResultObjectLocation(*EWC)); |
| EXPECT_EQ(&Loc, &Env.getResultObjectLocation(*BTE)); |
| }); |
| } |
| |
| TEST(TransferTest, ResultObjectLocationForDefaultArgExpr) { |
| std::string Code = R"( |
| struct Inner {}; |
| struct Outer { |
| Inner I = {}; |
| }; |
| |
| void funcWithDefaultArg(Outer O = {}); |
| void target() { |
| funcWithDefaultArg(); |
| // [[p]] |
| } |
| )"; |
| |
| using ast_matchers::cxxDefaultArgExpr; |
| using ast_matchers::match; |
| using ast_matchers::selectFirst; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| auto *DefaultArg = selectFirst<CXXDefaultArgExpr>( |
| "default_arg", |
| match(cxxDefaultArgExpr().bind("default_arg"), ASTCtx)); |
| ASSERT_NE(DefaultArg, nullptr); |
| |
| // The values for default arguments aren't modeled; we merely verify |
| // that we can get a result object location for a default arg. |
| Env.getResultObjectLocation(*DefaultArg); |
| }); |
| } |
| |
| TEST(TransferTest, ResultObjectLocationForDefaultInitExpr) { |
| std::string Code = R"( |
| struct S {}; |
| struct target { |
| target () { |
| (void)0; |
| // [[p]] |
| } |
| S s = {}; |
| }; |
| )"; |
| |
| using ast_matchers::cxxCtorInitializer; |
| using ast_matchers::match; |
| using ast_matchers::selectFirst; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *SField = findValueDecl(ASTCtx, "s"); |
| |
| auto *CtorInit = selectFirst<CXXCtorInitializer>( |
| "ctor_initializer", |
| match(cxxCtorInitializer().bind("ctor_initializer"), ASTCtx)); |
| ASSERT_NE(CtorInit, nullptr); |
| |
| auto *DefaultInit = cast<CXXDefaultInitExpr>(CtorInit->getInit()); |
| |
| RecordStorageLocation &Loc = Env.getResultObjectLocation(*DefaultInit); |
| |
| EXPECT_EQ(&Loc, Env.getThisPointeeStorageLocation()->getChild(*SField)); |
| }); |
| } |
| |
| // This test ensures that CXXOperatorCallExpr returning prvalues are correctly |
| // handled by the transfer functions, especially that `getResultObjectLocation` |
| // correctly returns a storage location for those. |
| TEST(TransferTest, ResultObjectLocationForCXXOperatorCallExpr) { |
| std::string Code = R"( |
| struct A { |
| A operator+(int); |
| }; |
| |
| void target() { |
| A a; |
| a + 3; |
| (void)0; // [[p]] |
| } |
| )"; |
| using ast_matchers::cxxOperatorCallExpr; |
| using ast_matchers::match; |
| using ast_matchers::selectFirst; |
| using ast_matchers::traverse; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| auto *CallExpr = selectFirst<CXXOperatorCallExpr>( |
| "call_expr", |
| match(cxxOperatorCallExpr().bind("call_expr"), ASTCtx)); |
| |
| EXPECT_NE(&Env.getResultObjectLocation(*CallExpr), nullptr); |
| }); |
| } |
| |
| TEST(TransferTest, ResultObjectLocationForInitListExpr) { |
| std::string Code = R"cc( |
| struct Inner {}; |
| |
| struct Outer { Inner I; }; |
| |
| void target() { |
| Outer O = { Inner() }; |
| // [[p]] |
| } |
| )cc"; |
| using ast_matchers::asString; |
| using ast_matchers::cxxConstructExpr; |
| using ast_matchers::hasType; |
| using ast_matchers::match; |
| using ast_matchers::selectFirst; |
| using ast_matchers::traverse; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| auto *Construct = selectFirst<CXXConstructExpr>( |
| "construct", |
| match( |
| cxxConstructExpr(hasType(asString("Inner"))).bind("construct"), |
| ASTCtx)); |
| |
| EXPECT_EQ( |
| &Env.getResultObjectLocation(*Construct), |
| &getFieldLoc(getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "O"), |
| "I", ASTCtx)); |
| }); |
| } |
| |
| TEST(TransferTest, ResultObjectLocationForParenInitListExpr) { |
| std::string Code = R"cc( |
| struct Inner {}; |
| |
| struct Outer { Inner I; }; |
| |
| void target() { |
| Outer O((Inner())); |
| // [[p]] |
| } |
| )cc"; |
| using ast_matchers::asString; |
| using ast_matchers::cxxConstructExpr; |
| using ast_matchers::hasType; |
| using ast_matchers::match; |
| using ast_matchers::selectFirst; |
| using ast_matchers::traverse; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| auto *Construct = selectFirst<CXXConstructExpr>( |
| "construct", |
| match( |
| cxxConstructExpr(hasType(asString("Inner"))).bind("construct"), |
| ASTCtx)); |
| |
| EXPECT_EQ( |
| &Env.getResultObjectLocation(*Construct), |
| &getFieldLoc(getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "O"), |
| "I", ASTCtx)); |
| }, |
| LangStandard::lang_cxx20); |
| } |
| |
| // Check that the `std::strong_ordering` object returned by builtin `<=>` has a |
| // correctly modeled result object location. |
| TEST(TransferTest, ResultObjectLocationForBuiltinSpaceshipOperator) { |
| std::string Code = R"( |
| namespace std { |
| // This is the minimal definition required to get |
| // `Sema::CheckComparisonCategoryType()` to accept this fake. |
| struct strong_ordering { |
| enum class ordering { less, equal, greater }; |
| ordering o; |
| static const strong_ordering less; |
| static const strong_ordering equivalent; |
| static const strong_ordering equal; |
| static const strong_ordering greater; |
| }; |
| |
| inline constexpr strong_ordering strong_ordering::less = |
| { strong_ordering::ordering::less }; |
| inline constexpr strong_ordering strong_ordering::equal = |
| { strong_ordering::ordering::equal }; |
| inline constexpr strong_ordering strong_ordering::equivalent = |
| { strong_ordering::ordering::equal }; |
| inline constexpr strong_ordering strong_ordering::greater = |
| { strong_ordering::ordering::greater }; |
| } |
| void target(int i, int j) { |
| auto ordering = i <=> j; |
| // [[p]] |
| } |
| )"; |
| using ast_matchers::binaryOperator; |
| using ast_matchers::hasOperatorName; |
| using ast_matchers::match; |
| using ast_matchers::selectFirst; |
| using ast_matchers::traverse; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| auto *Spaceship = selectFirst<BinaryOperator>( |
| "op", |
| match(binaryOperator(hasOperatorName("<=>")).bind("op"), ASTCtx)); |
| |
| EXPECT_EQ( |
| &Env.getResultObjectLocation(*Spaceship), |
| &getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "ordering")); |
| }, |
| LangStandard::lang_cxx20); |
| } |
| |
| TEST(TransferTest, ResultObjectLocationForStdInitializerListExpr) { |
| std::string Code = R"( |
| namespace std { |
| template <typename T> |
| struct initializer_list { const T *a, *b; }; |
| } // namespace std |
| |
| void target() { |
| std::initializer_list<int> list = {1}; |
| // [[p]] |
| } |
| )"; |
| |
| using ast_matchers::cxxStdInitializerListExpr; |
| using ast_matchers::match; |
| using ast_matchers::selectFirst; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| auto *StdInitList = selectFirst<CXXStdInitializerListExpr>( |
| "std_init_list", |
| match(cxxStdInitializerListExpr().bind("std_init_list"), ASTCtx)); |
| ASSERT_NE(StdInitList, nullptr); |
| |
| EXPECT_EQ(&Env.getResultObjectLocation(*StdInitList), |
| &getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "list")); |
| }); |
| } |
| |
| TEST(TransferTest, ResultObjectLocationForStmtExpr) { |
| std::string Code = R"( |
| struct S {}; |
| void target() { |
| S s = ({ S(); }); |
| // [[p]] |
| } |
| )"; |
| using ast_matchers::cxxConstructExpr; |
| using ast_matchers::match; |
| using ast_matchers::selectFirst; |
| using ast_matchers::traverse; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| auto *Construct = selectFirst<CXXConstructExpr>( |
| "construct", match(cxxConstructExpr().bind("construct"), ASTCtx)); |
| |
| EXPECT_EQ(&Env.getResultObjectLocation(*Construct), |
| &getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "s")); |
| }); |
| } |
| |
| TEST(TransferTest, ResultObjectLocationForBuiltinBitCastExpr) { |
| std::string Code = R"( |
| struct S { int i; }; |
| void target(int i) { |
| S s = __builtin_bit_cast(S, i); |
| // [[p]] |
| } |
| )"; |
| using ast_matchers::explicitCastExpr; |
| using ast_matchers::match; |
| using ast_matchers::selectFirst; |
| using ast_matchers::traverse; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| auto *BuiltinBitCast = selectFirst<BuiltinBitCastExpr>( |
| "cast", match(explicitCastExpr().bind("cast"), ASTCtx)); |
| |
| EXPECT_EQ(&Env.getResultObjectLocation(*BuiltinBitCast), |
| &getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "s")); |
| }); |
| } |
| |
| TEST(TransferTest, ResultObjectLocationForAtomicExpr) { |
| std::string Code = R"( |
| struct S {}; |
| void target(_Atomic(S) *ptr) { |
| S s = __c11_atomic_load(ptr, __ATOMIC_SEQ_CST); |
| // [[p]] |
| } |
| )"; |
| using ast_matchers::atomicExpr; |
| using ast_matchers::match; |
| using ast_matchers::selectFirst; |
| using ast_matchers::traverse; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| auto *Atomic = selectFirst<AtomicExpr>( |
| "atomic", match(atomicExpr().bind("atomic"), ASTCtx)); |
| |
| EXPECT_EQ(&Env.getResultObjectLocation(*Atomic), |
| &getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "s")); |
| }); |
| } |
| |
| TEST(TransferTest, ResultObjectLocationPropagatesThroughConditionalOperator) { |
| std::string Code = R"( |
| struct A { |
| A(int); |
| }; |
| |
| void target(bool b) { |
| A a = b ? A(0) : A(1); |
| (void)0; // [[p]] |
| } |
| )"; |
| using ast_matchers::cxxConstructExpr; |
| using ast_matchers::equals; |
| using ast_matchers::hasArgument; |
| using ast_matchers::integerLiteral; |
| using ast_matchers::match; |
| using ast_matchers::selectFirst; |
| using ast_matchers::traverse; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| auto *ConstructExpr0 = selectFirst<CXXConstructExpr>( |
| "construct", |
| match(cxxConstructExpr(hasArgument(0, integerLiteral(equals(0)))) |
| .bind("construct"), |
| ASTCtx)); |
| auto *ConstructExpr1 = selectFirst<CXXConstructExpr>( |
| "construct", |
| match(cxxConstructExpr(hasArgument(0, integerLiteral(equals(1)))) |
| .bind("construct"), |
| ASTCtx)); |
| |
| auto &ALoc = getLocForDecl<StorageLocation>(ASTCtx, Env, "a"); |
| EXPECT_EQ(&Env.getResultObjectLocation(*ConstructExpr0), &ALoc); |
| EXPECT_EQ(&Env.getResultObjectLocation(*ConstructExpr1), &ALoc); |
| }); |
| } |
| |
| TEST(TransferTest, ResultObjectLocationDontVisitNestedRecordDecl) { |
| // This is a crash repro. |
| // We used to crash because when propagating result objects, we would visit |
| // nested record and function declarations, but we don't model fields used |
| // only in these. |
| std::string Code = R"( |
| struct S1 {}; |
| struct S2 { S1 s1; }; |
| void target() { |
| struct Nested { |
| void f() { |
| S2 s2 = { S1() }; |
| } |
| }; |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) {}); |
| } |
| |
| TEST(TransferTest, ResultObjectLocationDontVisitUnevaluatedContexts) { |
| // This is a crash repro. |
| // We used to crash because when propagating result objects, we would visit |
| // unevaluated contexts, but we don't model fields used only in these. |
| |
| auto testFunction = [](llvm::StringRef Code, llvm::StringRef TargetFun) { |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) {}, |
| LangStandard::lang_gnucxx17, |
| /* ApplyBuiltinTransfer= */ true, TargetFun); |
| }; |
| |
| std::string Code = R"cc( |
| // Definitions needed for `typeid`. |
| namespace std { |
| class type_info {}; |
| class bad_typeid {}; |
| } // namespace std |
| |
| struct S1 {}; |
| struct S2 { S1 s1; }; |
| |
| // We test each type of unevaluated context from a different target |
| // function. Some types of unevaluated contexts may actually cause the |
| // field `s1` to be modeled, and we don't want this to "pollute" the tests |
| // for the other unevaluated contexts. |
| void decltypeTarget() { |
| decltype(S2{}) Dummy; |
| } |
| void typeofTarget() { |
| typeof(S2{}) Dummy; |
| } |
| void typeidTarget() { |
| #if __has_feature(cxx_rtti) |
| typeid(S2{}); |
| #endif |
| } |
| void sizeofTarget() { |
| sizeof(S2{}); |
| } |
| void noexceptTarget() { |
| noexcept(S2{}); |
| } |
| )cc"; |
| |
| testFunction(Code, "decltypeTarget"); |
| testFunction(Code, "typeofTarget"); |
| testFunction(Code, "typeidTarget"); |
| testFunction(Code, "sizeofTarget"); |
| testFunction(Code, "noexceptTarget"); |
| } |
| |
| TEST(TransferTest, StaticCast) { |
| std::string Code = R"( |
| void target(int Foo) { |
| int Bar = static_cast<int>(Foo); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const auto *FooVal = Env.getValue(*FooDecl); |
| const auto *BarVal = Env.getValue(*BarDecl); |
| EXPECT_TRUE(isa<IntegerValue>(FooVal)); |
| EXPECT_TRUE(isa<IntegerValue>(BarVal)); |
| EXPECT_EQ(FooVal, BarVal); |
| }); |
| } |
| |
| TEST(TransferTest, IntegralCast) { |
| std::string Code = R"( |
| void target(int Foo) { |
| long Bar = Foo; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const auto &FooVal = getValueForDecl<IntegerValue>(ASTCtx, Env, "Foo"); |
| const auto &BarVal = getValueForDecl<IntegerValue>(ASTCtx, Env, "Bar"); |
| EXPECT_EQ(&FooVal, &BarVal); |
| }); |
| } |
| |
| TEST(TransferTest, IntegraltoBooleanCast) { |
| std::string Code = R"( |
| void target(int Foo) { |
| bool Bar = Foo; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const auto &FooVal = getValueForDecl(ASTCtx, Env, "Foo"); |
| const auto &BarVal = getValueForDecl(ASTCtx, Env, "Bar"); |
| EXPECT_TRUE(isa<IntegerValue>(FooVal)); |
| EXPECT_TRUE(isa<BoolValue>(BarVal)); |
| }); |
| } |
| |
| TEST(TransferTest, IntegralToBooleanCastFromBool) { |
| std::string Code = R"( |
| void target(bool Foo) { |
| int Zab = Foo; |
| bool Bar = Zab; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const auto &FooVal = getValueForDecl<BoolValue>(ASTCtx, Env, "Foo"); |
| const auto &BarVal = getValueForDecl<BoolValue>(ASTCtx, Env, "Bar"); |
| EXPECT_EQ(&FooVal, &BarVal); |
| }); |
| } |
| |
| TEST(TransferTest, WidenBoolValueInIntegerVariable) { |
| // This is a crash repro. |
| // This test sets up a case where we perform widening on an integer variable |
| // that contains a `BoolValue` for the previous iteration and an |
| // `IntegerValue` for the current iteration. We used to crash on this because |
| // `widenDistinctValues()` assumed that if the previous iteration had a |
| // `BoolValue`, the current iteration would too. |
| // FIXME: The real fix here is to make sure we never store `BoolValue`s in |
| // integer variables; see also the comment in `widenDistinctValues()`. |
| std::string Code = R"cc( |
| struct S { |
| int i; |
| S *next; |
| }; |
| void target(S *s) { |
| for (; s; s = s->next) |
| s->i = false; |
| } |
| )cc"; |
| runDataflow(Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &, |
| ASTContext &) {}); |
| } |
| |
| TEST(TransferTest, NullToPointerCast) { |
| std::string Code = R"( |
| using my_nullptr_t = decltype(nullptr); |
| struct Baz {}; |
| void target() { |
| int *FooX = nullptr; |
| int *FooY = nullptr; |
| bool **Bar = nullptr; |
| Baz *Baz = nullptr; |
| my_nullptr_t Null = 0; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooXDecl = findValueDecl(ASTCtx, "FooX"); |
| ASSERT_THAT(FooXDecl, NotNull()); |
| |
| const ValueDecl *FooYDecl = findValueDecl(ASTCtx, "FooY"); |
| ASSERT_THAT(FooYDecl, NotNull()); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz"); |
| ASSERT_THAT(BazDecl, NotNull()); |
| |
| const ValueDecl *NullDecl = findValueDecl(ASTCtx, "Null"); |
| ASSERT_THAT(NullDecl, NotNull()); |
| |
| const auto *FooXVal = cast<PointerValue>(Env.getValue(*FooXDecl)); |
| const auto *FooYVal = cast<PointerValue>(Env.getValue(*FooYDecl)); |
| const auto *BarVal = cast<PointerValue>(Env.getValue(*BarDecl)); |
| const auto *BazVal = cast<PointerValue>(Env.getValue(*BazDecl)); |
| const auto *NullVal = cast<PointerValue>(Env.getValue(*NullDecl)); |
| |
| EXPECT_EQ(FooXVal, FooYVal); |
| EXPECT_NE(FooXVal, BarVal); |
| EXPECT_NE(FooXVal, BazVal); |
| EXPECT_NE(BarVal, BazVal); |
| |
| const StorageLocation &FooPointeeLoc = FooXVal->getPointeeLoc(); |
| EXPECT_TRUE(isa<ScalarStorageLocation>(FooPointeeLoc)); |
| EXPECT_THAT(Env.getValue(FooPointeeLoc), IsNull()); |
| |
| const StorageLocation &BarPointeeLoc = BarVal->getPointeeLoc(); |
| EXPECT_TRUE(isa<ScalarStorageLocation>(BarPointeeLoc)); |
| EXPECT_THAT(Env.getValue(BarPointeeLoc), IsNull()); |
| |
| const StorageLocation &BazPointeeLoc = BazVal->getPointeeLoc(); |
| EXPECT_TRUE(isa<RecordStorageLocation>(BazPointeeLoc)); |
| EXPECT_EQ(BazVal, &Env.fork().getOrCreateNullPointerValue( |
| BazPointeeLoc.getType())); |
| |
| const StorageLocation &NullPointeeLoc = NullVal->getPointeeLoc(); |
| EXPECT_TRUE(isa<ScalarStorageLocation>(NullPointeeLoc)); |
| EXPECT_THAT(Env.getValue(NullPointeeLoc), IsNull()); |
| }); |
| } |
| |
| TEST(TransferTest, PointerToMemberVariable) { |
| std::string Code = R"( |
| struct S { |
| int i; |
| }; |
| void target() { |
| int S::*MemberPointer = &S::i; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *MemberPointerDecl = |
| findValueDecl(ASTCtx, "MemberPointer"); |
| ASSERT_THAT(MemberPointerDecl, NotNull()); |
| ASSERT_THAT(Env.getValue(*MemberPointerDecl), IsNull()); |
| }); |
| } |
| |
| TEST(TransferTest, PointerToMemberFunction) { |
| std::string Code = R"( |
| struct S { |
| void Method(); |
| }; |
| void target() { |
| void (S::*MemberPointer)() = &S::Method; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *MemberPointerDecl = |
| findValueDecl(ASTCtx, "MemberPointer"); |
| ASSERT_THAT(MemberPointerDecl, NotNull()); |
| ASSERT_THAT(Env.getValue(*MemberPointerDecl), IsNull()); |
| }); |
| } |
| |
| TEST(TransferTest, NullToMemberPointerCast) { |
| std::string Code = R"( |
| struct Foo {}; |
| void target() { |
| int Foo::*MemberPointer = nullptr; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *MemberPointerDecl = |
| findValueDecl(ASTCtx, "MemberPointer"); |
| ASSERT_THAT(MemberPointerDecl, NotNull()); |
| ASSERT_THAT(Env.getValue(*MemberPointerDecl), IsNull()); |
| }); |
| } |
| |
| TEST(TransferTest, AddrOfValue) { |
| std::string Code = R"( |
| void target() { |
| int Foo; |
| int *Bar = &Foo; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const auto *FooLoc = |
| cast<ScalarStorageLocation>(Env.getStorageLocation(*FooDecl)); |
| const auto *BarVal = cast<PointerValue>(Env.getValue(*BarDecl)); |
| EXPECT_EQ(&BarVal->getPointeeLoc(), FooLoc); |
| }); |
| } |
| |
| TEST(TransferTest, AddrOfReference) { |
| std::string Code = R"( |
| void target(int *Foo) { |
| int *Bar = &(*Foo); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const auto *FooVal = cast<PointerValue>(Env.getValue(*FooDecl)); |
| const auto *BarVal = cast<PointerValue>(Env.getValue(*BarDecl)); |
| EXPECT_EQ(&BarVal->getPointeeLoc(), &FooVal->getPointeeLoc()); |
| }); |
| } |
| |
| TEST(TransferTest, Preincrement) { |
| std::string Code = R"( |
| void target(int I) { |
| int &IRef = ++I; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| EXPECT_EQ(&getLocForDecl(ASTCtx, Env, "IRef"), |
| &getLocForDecl(ASTCtx, Env, "I")); |
| }); |
| } |
| |
| TEST(TransferTest, Postincrement) { |
| std::string Code = R"( |
| void target(int I) { |
| int OldVal = I++; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| EXPECT_EQ(&getValueForDecl(ASTCtx, Env, "OldVal"), |
| &getValueForDecl(ASTCtx, Env, "I")); |
| }); |
| } |
| |
| // We test just one of the compound assignment operators because we know the |
| // code for propagating the storage location is shared among all of them. |
| TEST(TransferTest, AddAssign) { |
| std::string Code = R"( |
| void target(int I) { |
| int &IRef = (I += 1); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| EXPECT_EQ(&getLocForDecl(ASTCtx, Env, "IRef"), |
| &getLocForDecl(ASTCtx, Env, "I")); |
| }); |
| } |
| |
| TEST(TransferTest, CannotAnalyzeFunctionTemplate) { |
| std::string Code = R"( |
| template <typename T> |
| void target() {} |
| )"; |
| ASSERT_THAT_ERROR( |
| checkDataflowWithNoopAnalysis(Code), |
| llvm::FailedWithMessage("Cannot analyze templated declarations")); |
| } |
| |
| TEST(TransferTest, CannotAnalyzeMethodOfClassTemplate) { |
| std::string Code = R"( |
| template <typename T> |
| struct A { |
| void target() {} |
| }; |
| )"; |
| ASSERT_THAT_ERROR( |
| checkDataflowWithNoopAnalysis(Code), |
| llvm::FailedWithMessage("Cannot analyze templated declarations")); |
| } |
| |
| TEST(TransferTest, VarDeclInitAssignConditionalOperator) { |
| std::string Code = R"( |
| struct A { |
| int i; |
| }; |
| |
| void target(A Foo, A Bar, bool Cond) { |
| A Baz = Cond ? A(Foo) : A(Bar); |
| // Make sure A::i is modeled. |
| Baz.i; |
| /*[[p]]*/ |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| auto *FooIVal = cast<IntegerValue>(getFieldValue( |
| &getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "Foo"), "i", |
| ASTCtx, Env)); |
| auto *BarIVal = cast<IntegerValue>(getFieldValue( |
| &getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "Bar"), "i", |
| ASTCtx, Env)); |
| auto *BazIVal = cast<IntegerValue>(getFieldValue( |
| &getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "Baz"), "i", |
| ASTCtx, Env)); |
| |
| EXPECT_NE(BazIVal, FooIVal); |
| EXPECT_NE(BazIVal, BarIVal); |
| }); |
| } |
| |
| TEST(TransferTest, VarDeclInDoWhile) { |
| std::string Code = R"( |
| void target(int *Foo) { |
| do { |
| int Bar = *Foo; |
| // [[in_loop]] |
| } while (false); |
| (void)0; |
| // [[after_loop]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &EnvInLoop = |
| getEnvironmentAtAnnotation(Results, "in_loop"); |
| const Environment &EnvAfterLoop = |
| getEnvironmentAtAnnotation(Results, "after_loop"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const auto *FooVal = |
| cast<PointerValue>(EnvAfterLoop.getValue(*FooDecl)); |
| const auto *FooPointeeVal = |
| cast<IntegerValue>(EnvAfterLoop.getValue(FooVal->getPointeeLoc())); |
| |
| const auto *BarVal = cast<IntegerValue>(EnvInLoop.getValue(*BarDecl)); |
| EXPECT_EQ(BarVal, FooPointeeVal); |
| |
| ASSERT_THAT(EnvAfterLoop.getValue(*BarDecl), IsNull()); |
| }); |
| } |
| |
| TEST(TransferTest, UnreachableAfterWhileTrue) { |
| std::string Code = R"( |
| void target() { |
| while (true) {} |
| (void)0; |
| /*[[p]]*/ |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| // The node after the while-true is pruned because it is trivially |
| // known to be unreachable. |
| ASSERT_TRUE(Results.empty()); |
| }); |
| } |
| |
| TEST(TransferTest, AggregateInitialization) { |
| std::string BracesCode = R"( |
| struct A { |
| int Foo; |
| }; |
| |
| struct B { |
| int Bar; |
| A Baz; |
| int Qux; |
| }; |
| |
| void target(int BarArg, int FooArg, int QuxArg) { |
| B Quux{BarArg, {FooArg}, QuxArg}; |
| B OtherB; |
| /*[[p]]*/ |
| } |
| )"; |
| std::string BraceElisionCode = R"( |
| struct A { |
| int Foo; |
| }; |
| |
| struct B { |
| int Bar; |
| A Baz; |
| int Qux; |
| }; |
| |
| void target(int BarArg, int FooArg, int QuxArg) { |
| B Quux = {BarArg, FooArg, QuxArg}; |
| B OtherB; |
| /*[[p]]*/ |
| } |
| )"; |
| for (const std::string &Code : {BracesCode, BraceElisionCode}) { |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| 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 ValueDecl *QuxDecl = findValueDecl(ASTCtx, "Qux"); |
| ASSERT_THAT(QuxDecl, NotNull()); |
| |
| const ValueDecl *FooArgDecl = findValueDecl(ASTCtx, "FooArg"); |
| ASSERT_THAT(FooArgDecl, NotNull()); |
| |
| const ValueDecl *BarArgDecl = findValueDecl(ASTCtx, "BarArg"); |
| ASSERT_THAT(BarArgDecl, NotNull()); |
| |
| const ValueDecl *QuxArgDecl = findValueDecl(ASTCtx, "QuxArg"); |
| ASSERT_THAT(QuxArgDecl, NotNull()); |
| |
| const ValueDecl *QuuxDecl = findValueDecl(ASTCtx, "Quux"); |
| ASSERT_THAT(QuuxDecl, NotNull()); |
| |
| const auto *FooArgVal = cast<IntegerValue>(Env.getValue(*FooArgDecl)); |
| const auto *BarArgVal = cast<IntegerValue>(Env.getValue(*BarArgDecl)); |
| const auto *QuxArgVal = cast<IntegerValue>(Env.getValue(*QuxArgDecl)); |
| |
| const auto &QuuxLoc = |
| *cast<RecordStorageLocation>(Env.getStorageLocation(*QuuxDecl)); |
| const auto &BazLoc = |
| *cast<RecordStorageLocation>(QuuxLoc.getChild(*BazDecl)); |
| |
| EXPECT_EQ(getFieldValue(&QuuxLoc, *BarDecl, Env), BarArgVal); |
| EXPECT_EQ(getFieldValue(&BazLoc, *FooDecl, Env), FooArgVal); |
| EXPECT_EQ(getFieldValue(&QuuxLoc, *QuxDecl, Env), QuxArgVal); |
| |
| // Check that fields initialized in an initializer list are always |
| // modeled in other instances of the same type. |
| const auto &OtherBLoc = |
| getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "OtherB"); |
| EXPECT_THAT(OtherBLoc.getChild(*BarDecl), NotNull()); |
| EXPECT_THAT(OtherBLoc.getChild(*BazDecl), NotNull()); |
| EXPECT_THAT(OtherBLoc.getChild(*QuxDecl), NotNull()); |
| }); |
| } |
| } |
| |
| TEST(TransferTest, AggregateInitializationReferenceField) { |
| std::string Code = R"( |
| struct S { |
| int &RefField; |
| }; |
| |
| void target(int i) { |
| S s = { i }; |
| /*[[p]]*/ |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *RefFieldDecl = findValueDecl(ASTCtx, "RefField"); |
| |
| auto &ILoc = getLocForDecl<StorageLocation>(ASTCtx, Env, "i"); |
| auto &SLoc = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "s"); |
| |
| EXPECT_EQ(SLoc.getChild(*RefFieldDecl), &ILoc); |
| }); |
| } |
| |
| TEST(TransferTest, AggregateInitialization_NotExplicitlyInitializedField) { |
| std::string Code = R"( |
| struct S { |
| int i1; |
| int i2; |
| }; |
| |
| void target(int i) { |
| S s = { i }; |
| /*[[p]]*/ |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *I1FieldDecl = findValueDecl(ASTCtx, "i1"); |
| const ValueDecl *I2FieldDecl = findValueDecl(ASTCtx, "i2"); |
| |
| auto &SLoc = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "s"); |
| |
| auto &IValue = getValueForDecl<IntegerValue>(ASTCtx, Env, "i"); |
| auto &I1Value = |
| *cast<IntegerValue>(getFieldValue(&SLoc, *I1FieldDecl, Env)); |
| EXPECT_EQ(&I1Value, &IValue); |
| auto &I2Value = |
| *cast<IntegerValue>(getFieldValue(&SLoc, *I2FieldDecl, Env)); |
| EXPECT_NE(&I2Value, &IValue); |
| }); |
| } |
| |
| TEST(TransferTest, AggregateInitializationFunctionPointer) { |
| // This is a repro for an assertion failure. |
| // nullptr takes on the type of a const function pointer, but its type was |
| // asserted to be equal to the *unqualified* type of Field, which no longer |
| // included the const. |
| std::string Code = R"( |
| struct S { |
| void (*const Field)(); |
| }; |
| |
| void target() { |
| S s{nullptr}; |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) {}); |
| } |
| |
| TEST(TransferTest, AssignToUnionMember) { |
| std::string Code = R"( |
| union A { |
| int Foo; |
| }; |
| |
| void target(int Bar) { |
| A Baz; |
| Baz.Foo = Bar; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz"); |
| ASSERT_THAT(BazDecl, NotNull()); |
| ASSERT_TRUE(BazDecl->getType()->isUnionType()); |
| |
| auto BazFields = BazDecl->getType()->getAsRecordDecl()->fields(); |
| FieldDecl *FooDecl = nullptr; |
| for (FieldDecl *Field : BazFields) { |
| if (Field->getNameAsString() == "Foo") { |
| FooDecl = Field; |
| } else { |
| FAIL() << "Unexpected field: " << Field->getNameAsString(); |
| } |
| } |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const auto *BazLoc = dyn_cast_or_null<RecordStorageLocation>( |
| Env.getStorageLocation(*BazDecl)); |
| ASSERT_THAT(BazLoc, NotNull()); |
| |
| const auto *FooVal = |
| cast<IntegerValue>(getFieldValue(BazLoc, *FooDecl, Env)); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| const auto *BarLoc = Env.getStorageLocation(*BarDecl); |
| ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(BarLoc)); |
| |
| EXPECT_EQ(Env.getValue(*BarLoc), FooVal); |
| }); |
| } |
| |
| TEST(TransferTest, AssignFromBoolLiteral) { |
| std::string Code = R"( |
| void target() { |
| bool Foo = true; |
| bool Bar = false; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const auto *FooVal = |
| dyn_cast_or_null<BoolValue>(Env.getValue(*FooDecl)); |
| ASSERT_THAT(FooVal, NotNull()); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const auto *BarVal = |
| dyn_cast_or_null<BoolValue>(Env.getValue(*BarDecl)); |
| ASSERT_THAT(BarVal, NotNull()); |
| |
| EXPECT_EQ(FooVal, &Env.getBoolLiteralValue(true)); |
| EXPECT_EQ(BarVal, &Env.getBoolLiteralValue(false)); |
| }); |
| } |
| |
| TEST(TransferTest, AssignFromCompositeBoolExpression) { |
| { |
| std::string Code = R"( |
| void target(bool Foo, bool Bar, bool Qux) { |
| bool Baz = (Foo) && (Bar || Qux); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const auto *FooVal = |
| dyn_cast_or_null<BoolValue>(Env.getValue(*FooDecl)); |
| ASSERT_THAT(FooVal, NotNull()); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const auto *BarVal = |
| dyn_cast_or_null<BoolValue>(Env.getValue(*BarDecl)); |
| ASSERT_THAT(BarVal, NotNull()); |
| |
| const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "Qux"); |
| ASSERT_THAT(QuxDecl, NotNull()); |
| |
| const auto *QuxVal = |
| dyn_cast_or_null<BoolValue>(Env.getValue(*QuxDecl)); |
| ASSERT_THAT(QuxVal, NotNull()); |
| |
| const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz"); |
| ASSERT_THAT(BazDecl, NotNull()); |
| |
| const auto *BazVal = |
| dyn_cast_or_null<BoolValue>(Env.getValue(*BazDecl)); |
| ASSERT_THAT(BazVal, NotNull()); |
| auto &A = Env.arena(); |
| EXPECT_EQ(&BazVal->formula(), |
| &A.makeAnd(FooVal->formula(), |
| A.makeOr(BarVal->formula(), QuxVal->formula()))); |
| }); |
| } |
| |
| { |
| std::string Code = R"( |
| void target(bool Foo, bool Bar, bool Qux) { |
| bool Baz = (Foo && Qux) || (Bar); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const auto *FooVal = |
| dyn_cast_or_null<BoolValue>(Env.getValue(*FooDecl)); |
| ASSERT_THAT(FooVal, NotNull()); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const auto *BarVal = |
| dyn_cast_or_null<BoolValue>(Env.getValue(*BarDecl)); |
| ASSERT_THAT(BarVal, NotNull()); |
| |
| const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "Qux"); |
| ASSERT_THAT(QuxDecl, NotNull()); |
| |
| const auto *QuxVal = |
| dyn_cast_or_null<BoolValue>(Env.getValue(*QuxDecl)); |
| ASSERT_THAT(QuxVal, NotNull()); |
| |
| const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz"); |
| ASSERT_THAT(BazDecl, NotNull()); |
| |
| const auto *BazVal = |
| dyn_cast_or_null<BoolValue>(Env.getValue(*BazDecl)); |
| ASSERT_THAT(BazVal, NotNull()); |
| auto &A = Env.arena(); |
| EXPECT_EQ(&BazVal->formula(), |
| &A.makeOr(A.makeAnd(FooVal->formula(), QuxVal->formula()), |
| BarVal->formula())); |
| }); |
| } |
| |
| { |
| std::string Code = R"( |
| void target(bool A, bool B, bool C, bool D) { |
| bool Foo = ((A && B) && C) && D; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *ADecl = findValueDecl(ASTCtx, "A"); |
| ASSERT_THAT(ADecl, NotNull()); |
| |
| const auto *AVal = dyn_cast_or_null<BoolValue>(Env.getValue(*ADecl)); |
| ASSERT_THAT(AVal, NotNull()); |
| |
| const ValueDecl *BDecl = findValueDecl(ASTCtx, "B"); |
| ASSERT_THAT(BDecl, NotNull()); |
| |
| const auto *BVal = dyn_cast_or_null<BoolValue>(Env.getValue(*BDecl)); |
| ASSERT_THAT(BVal, NotNull()); |
| |
| const ValueDecl *CDecl = findValueDecl(ASTCtx, "C"); |
| ASSERT_THAT(CDecl, NotNull()); |
| |
| const auto *CVal = dyn_cast_or_null<BoolValue>(Env.getValue(*CDecl)); |
| ASSERT_THAT(CVal, NotNull()); |
| |
| const ValueDecl *DDecl = findValueDecl(ASTCtx, "D"); |
| ASSERT_THAT(DDecl, NotNull()); |
| |
| const auto *DVal = dyn_cast_or_null<BoolValue>(Env.getValue(*DDecl)); |
| ASSERT_THAT(DVal, NotNull()); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const auto *FooVal = |
| dyn_cast_or_null<BoolValue>(Env.getValue(*FooDecl)); |
| ASSERT_THAT(FooVal, NotNull()); |
| auto &A = Env.arena(); |
| EXPECT_EQ( |
| &FooVal->formula(), |
| &A.makeAnd(A.makeAnd(A.makeAnd(AVal->formula(), BVal->formula()), |
| CVal->formula()), |
| DVal->formula())); |
| }); |
| } |
| } |
| |
| TEST(TransferTest, AssignFromBoolNegation) { |
| std::string Code = R"( |
| void target() { |
| bool Foo = true; |
| bool Bar = !(Foo); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const auto *FooVal = |
| dyn_cast_or_null<BoolValue>(Env.getValue(*FooDecl)); |
| ASSERT_THAT(FooVal, NotNull()); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const auto *BarVal = |
| dyn_cast_or_null<BoolValue>(Env.getValue(*BarDecl)); |
| ASSERT_THAT(BarVal, NotNull()); |
| auto &A = Env.arena(); |
| EXPECT_EQ(&BarVal->formula(), &A.makeNot(FooVal->formula())); |
| }); |
| } |
| |
| TEST(TransferTest, BuiltinExpect) { |
| std::string Code = R"( |
| void target(long Foo) { |
| long Bar = __builtin_expect(Foo, true); |
| /*[[p]]*/ |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| EXPECT_EQ(Env.getValue(*FooDecl), Env.getValue(*BarDecl)); |
| }); |
| } |
| |
| // `__builtin_expect` takes and returns a `long` argument, so other types |
| // involve casts. This verifies that we identify the input and output in that |
| // case. |
| TEST(TransferTest, BuiltinExpectBoolArg) { |
| std::string Code = R"( |
| void target(bool Foo) { |
| bool Bar = __builtin_expect(Foo, true); |
| /*[[p]]*/ |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| EXPECT_EQ(Env.getValue(*FooDecl), Env.getValue(*BarDecl)); |
| }); |
| } |
| |
| TEST(TransferTest, BuiltinUnreachable) { |
| std::string Code = R"( |
| void target(bool Foo) { |
| bool Bar = false; |
| if (Foo) |
| Bar = Foo; |
| else |
| __builtin_unreachable(); |
| (void)0; |
| /*[[p]]*/ |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| // `__builtin_unreachable` promises that the code is |
| // unreachable, so the compiler treats the "then" branch as the |
| // only possible predecessor of this statement. |
| EXPECT_EQ(Env.getValue(*FooDecl), Env.getValue(*BarDecl)); |
| }); |
| } |
| |
| TEST(TransferTest, BuiltinTrap) { |
| std::string Code = R"( |
| void target(bool Foo) { |
| bool Bar = false; |
| if (Foo) |
| Bar = Foo; |
| else |
| __builtin_trap(); |
| (void)0; |
| /*[[p]]*/ |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| // `__builtin_trap` ensures program termination, so only the |
| // "then" branch is a predecessor of this statement. |
| EXPECT_EQ(Env.getValue(*FooDecl), Env.getValue(*BarDecl)); |
| }); |
| } |
| |
| TEST(TransferTest, BuiltinDebugTrap) { |
| std::string Code = R"( |
| void target(bool Foo) { |
| bool Bar = false; |
| if (Foo) |
| Bar = Foo; |
| else |
| __builtin_debugtrap(); |
| (void)0; |
| /*[[p]]*/ |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| // `__builtin_debugtrap` doesn't ensure program termination. |
| EXPECT_NE(Env.getValue(*FooDecl), Env.getValue(*BarDecl)); |
| }); |
| } |
| |
| TEST(TransferTest, StaticIntSingleVarDecl) { |
| std::string Code = R"( |
| void target() { |
| static int Foo; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| 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); |
| EXPECT_TRUE(isa_and_nonnull<IntegerValue>(FooVal)); |
| }); |
| } |
| |
| TEST(TransferTest, StaticIntGroupVarDecl) { |
| std::string Code = R"( |
| void target() { |
| static int Foo, Bar; |
| (void)0; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const StorageLocation *FooLoc = Env.getStorageLocation(*FooDecl); |
| ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(FooLoc)); |
| |
| const StorageLocation *BarLoc = Env.getStorageLocation(*BarDecl); |
| ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(BarLoc)); |
| |
| const Value *FooVal = Env.getValue(*FooLoc); |
| EXPECT_TRUE(isa_and_nonnull<IntegerValue>(FooVal)); |
| |
| const Value *BarVal = Env.getValue(*BarLoc); |
| EXPECT_TRUE(isa_and_nonnull<IntegerValue>(BarVal)); |
| |
| EXPECT_NE(FooVal, BarVal); |
| }); |
| } |
| |
| TEST(TransferTest, GlobalIntVarDecl) { |
| std::string Code = R"( |
| static int Foo; |
| |
| void target() { |
| int Bar = Foo; |
| int Baz = Foo; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz"); |
| ASSERT_THAT(BazDecl, NotNull()); |
| |
| const Value *BarVal = cast<IntegerValue>(Env.getValue(*BarDecl)); |
| const Value *BazVal = cast<IntegerValue>(Env.getValue(*BazDecl)); |
| EXPECT_EQ(BarVal, BazVal); |
| }); |
| } |
| |
| TEST(TransferTest, StaticMemberIntVarDecl) { |
| std::string Code = R"( |
| struct A { |
| static int Foo; |
| }; |
| |
| void target(A a) { |
| int Bar = a.Foo; |
| int Baz = a.Foo; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz"); |
| ASSERT_THAT(BazDecl, NotNull()); |
| |
| const Value *BarVal = cast<IntegerValue>(Env.getValue(*BarDecl)); |
| const Value *BazVal = cast<IntegerValue>(Env.getValue(*BazDecl)); |
| EXPECT_EQ(BarVal, BazVal); |
| }); |
| } |
| |
| TEST(TransferTest, StaticMemberRefVarDecl) { |
| std::string Code = R"( |
| struct A { |
| static int &Foo; |
| }; |
| |
| void target(A a) { |
| int Bar = a.Foo; |
| int Baz = a.Foo; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz"); |
| ASSERT_THAT(BazDecl, NotNull()); |
| |
| const Value *BarVal = cast<IntegerValue>(Env.getValue(*BarDecl)); |
| const Value *BazVal = cast<IntegerValue>(Env.getValue(*BazDecl)); |
| EXPECT_EQ(BarVal, BazVal); |
| }); |
| } |
| |
| TEST(TransferTest, AssignMemberBeforeCopy) { |
| std::string Code = R"( |
| struct A { |
| int Foo; |
| }; |
| |
| void target() { |
| A A1; |
| A A2; |
| int Bar; |
| A1.Foo = Bar; |
| A2 = A1; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const ValueDecl *A1Decl = findValueDecl(ASTCtx, "A1"); |
| ASSERT_THAT(A1Decl, NotNull()); |
| |
| const ValueDecl *A2Decl = findValueDecl(ASTCtx, "A2"); |
| ASSERT_THAT(A2Decl, NotNull()); |
| |
| const auto *BarVal = cast<IntegerValue>(Env.getValue(*BarDecl)); |
| |
| const auto &A2Loc = |
| *cast<RecordStorageLocation>(Env.getStorageLocation(*A2Decl)); |
| EXPECT_EQ(getFieldValue(&A2Loc, *FooDecl, Env), BarVal); |
| }); |
| } |
| |
| TEST(TransferTest, BooleanEquality) { |
| std::string Code = R"( |
| void target(bool Bar) { |
| bool Foo = true; |
| if (Bar == Foo) { |
| (void)0; |
| /*[[p-then]]*/ |
| } else { |
| (void)0; |
| /*[[p-else]]*/ |
| } |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p-then", "p-else")); |
| const Environment &EnvThen = |
| getEnvironmentAtAnnotation(Results, "p-then"); |
| const Environment &EnvElse = |
| getEnvironmentAtAnnotation(Results, "p-else"); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| auto &BarValThen = getFormula(*BarDecl, EnvThen); |
| EXPECT_TRUE(EnvThen.proves(BarValThen)); |
| |
| auto &BarValElse = getFormula(*BarDecl, EnvElse); |
| EXPECT_TRUE(EnvElse.proves(EnvElse.arena().makeNot(BarValElse))); |
| }); |
| } |
| |
| TEST(TransferTest, BooleanInequality) { |
| std::string Code = R"( |
| void target(bool Bar) { |
| bool Foo = true; |
| if (Bar != Foo) { |
| (void)0; |
| /*[[p-then]]*/ |
| } else { |
| (void)0; |
| /*[[p-else]]*/ |
| } |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p-then", "p-else")); |
| const Environment &EnvThen = |
| getEnvironmentAtAnnotation(Results, "p-then"); |
| const Environment &EnvElse = |
| getEnvironmentAtAnnotation(Results, "p-else"); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| auto &BarValThen = getFormula(*BarDecl, EnvThen); |
| EXPECT_TRUE(EnvThen.proves(EnvThen.arena().makeNot(BarValThen))); |
| |
| auto &BarValElse = getFormula(*BarDecl, EnvElse); |
| EXPECT_TRUE(EnvElse.proves(BarValElse)); |
| }); |
| } |
| |
| TEST(TransferTest, PointerEquality) { |
| std::string Code = R"cc( |
| void target() { |
| int i = 0; |
| int i_other = 0; |
| int *p1 = &i; |
| int *p2 = &i; |
| int *p_other = &i_other; |
| int *null = nullptr; |
| |
| bool p1_eq_p1 = (p1 == p1); |
| bool p1_eq_p2 = (p1 == p2); |
| bool p1_eq_p_other = (p1 == p_other); |
| |
| bool p1_eq_null = (p1 == null); |
| bool p1_eq_nullptr = (p1 == nullptr); |
| bool null_eq_nullptr = (null == nullptr); |
| bool nullptr_eq_nullptr = (nullptr == nullptr); |
| |
| // We won't duplicate all of the tests above with `!=`, as we know that |
| // the implementation simply negates the result of the `==` comparison. |
| // Instaed, just spot-check one case. |
| bool p1_ne_p1 = (p1 != p1); |
| |
| (void)0; // [[p]] |
| } |
| )cc"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| // Check the we have indeed set things up so that `p1` and `p2` have |
| // different pointer values. |
| EXPECT_NE(&getValueForDecl<PointerValue>(ASTCtx, Env, "p1"), |
| &getValueForDecl<PointerValue>(ASTCtx, Env, "p2")); |
| |
| EXPECT_EQ(&getValueForDecl<BoolValue>(ASTCtx, Env, "p1_eq_p1"), |
| &Env.getBoolLiteralValue(true)); |
| EXPECT_EQ(&getValueForDecl<BoolValue>(ASTCtx, Env, "p1_eq_p2"), |
| &Env.getBoolLiteralValue(true)); |
| EXPECT_TRUE(isa<AtomicBoolValue>( |
| getValueForDecl<BoolValue>(ASTCtx, Env, "p1_eq_p_other"))); |
| |
| EXPECT_TRUE(isa<AtomicBoolValue>( |
| getValueForDecl<BoolValue>(ASTCtx, Env, "p1_eq_null"))); |
| EXPECT_TRUE(isa<AtomicBoolValue>( |
| getValueForDecl<BoolValue>(ASTCtx, Env, "p1_eq_nullptr"))); |
| EXPECT_EQ(&getValueForDecl<BoolValue>(ASTCtx, Env, "null_eq_nullptr"), |
| &Env.getBoolLiteralValue(true)); |
| EXPECT_EQ( |
| &getValueForDecl<BoolValue>(ASTCtx, Env, "nullptr_eq_nullptr"), |
| &Env.getBoolLiteralValue(true)); |
| |
| EXPECT_EQ(&getValueForDecl<BoolValue>(ASTCtx, Env, "p1_ne_p1"), |
| &Env.getBoolLiteralValue(false)); |
| }); |
| } |
| |
| TEST(TransferTest, PointerEqualityUnionMembers) { |
| std::string Code = R"cc( |
| union U { |
| int i1; |
| int i2; |
| }; |
| void target() { |
| U u; |
| bool i1_eq_i2 = (&u.i1 == &u.i2); |
| |
| (void)0; // [[p]] |
| } |
| )cc"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| // FIXME: By the standard, `u.i1` and `u.i2` should have the same |
| // address, but we don't yet model this property of union members |
| // correctly. The result is therefore weaker than it could be (just an |
| // atom rather than a true literal), though not wrong. |
| EXPECT_TRUE(isa<AtomicBoolValue>( |
| getValueForDecl<BoolValue>(ASTCtx, Env, "i1_eq_i2"))); |
| }); |
| } |
| |
| TEST(TransferTest, IntegerLiteralEquality) { |
| std::string Code = R"( |
| void target() { |
| bool equal = (42 == 42); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| auto &Equal = |
| getValueForDecl<BoolValue>(ASTCtx, Env, "equal").formula(); |
| EXPECT_TRUE(Env.proves(Equal)); |
| }); |
| } |
| |
| TEST(TransferTest, CorrelatedBranches) { |
| std::string Code = R"( |
| void target(bool B, bool C) { |
| if (B) { |
| return; |
| } |
| (void)0; |
| /*[[p0]]*/ |
| if (C) { |
| B = true; |
| /*[[p1]]*/ |
| } |
| if (B) { |
| (void)0; |
| /*[[p2]]*/ |
| } |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p0", "p1", "p2")); |
| |
| const ValueDecl *CDecl = findValueDecl(ASTCtx, "C"); |
| ASSERT_THAT(CDecl, NotNull()); |
| |
| { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p0"); |
| const ValueDecl *BDecl = findValueDecl(ASTCtx, "B"); |
| ASSERT_THAT(BDecl, NotNull()); |
| auto &BVal = getFormula(*BDecl, Env); |
| |
| EXPECT_TRUE(Env.proves(Env.arena().makeNot(BVal))); |
| } |
| |
| { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p1"); |
| auto &CVal = getFormula(*CDecl, Env); |
| EXPECT_TRUE(Env.proves(CVal)); |
| } |
| |
| { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p2"); |
| auto &CVal = getFormula(*CDecl, Env); |
| EXPECT_TRUE(Env.proves(CVal)); |
| } |
| }); |
| } |
| |
| TEST(TransferTest, LoopWithAssignmentConverges) { |
| std::string Code = R"( |
| bool foo(); |
| |
| void target() { |
| do { |
| bool Bar = foo(); |
| if (Bar) break; |
| (void)Bar; |
| /*[[p]]*/ |
| } while (true); |
| } |
| )"; |
| // The key property that we are verifying is implicit in `runDataflow` -- |
| // namely, that the analysis succeeds, rather than hitting the maximum number |
| // of iterations. |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| auto &BarVal = getFormula(*BarDecl, Env); |
| EXPECT_TRUE(Env.proves(Env.arena().makeNot(BarVal))); |
| }); |
| } |
| |
| TEST(TransferTest, LoopWithStagedAssignments) { |
| std::string Code = R"( |
| bool foo(); |
| |
| void target() { |
| bool Bar = false; |
| bool Err = false; |
| while (foo()) { |
| if (Bar) |
| Err = true; |
| Bar = true; |
| /*[[p]]*/ |
| } |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| const ValueDecl *ErrDecl = findValueDecl(ASTCtx, "Err"); |
| ASSERT_THAT(ErrDecl, NotNull()); |
| |
| auto &BarVal = getFormula(*BarDecl, Env); |
| auto &ErrVal = getFormula(*ErrDecl, Env); |
| EXPECT_TRUE(Env.proves(BarVal)); |
| // An unsound analysis, for example only evaluating the loop once, can |
| // conclude that `Err` is false. So, we test that this conclusion is not |
| // reached. |
| EXPECT_FALSE(Env.proves(Env.arena().makeNot(ErrVal))); |
| }); |
| } |
| |
| TEST(TransferTest, LoopWithReferenceAssignmentConverges) { |
| std::string Code = R"( |
| bool &foo(); |
| |
| void target() { |
| do { |
| bool& Bar = foo(); |
| if (Bar) break; |
| (void)Bar; |
| /*[[p]]*/ |
| } while (true); |
| } |
| )"; |
| // The key property that we are verifying is that the analysis succeeds, |
| // rather than hitting the maximum number of iterations. |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| auto &BarVal = getFormula(*BarDecl, Env); |
| EXPECT_TRUE(Env.proves(Env.arena().makeNot(BarVal))); |
| }); |
| } |
| |
| TEST(TransferTest, LoopWithStructReferenceAssignmentConverges) { |
| std::string Code = R"( |
| struct Lookup { |
| int x; |
| }; |
| |
| void target(Lookup val, bool b) { |
| const Lookup* l = nullptr; |
| while (b) { |
| l = &val; |
| /*[[p-inner]]*/ |
| } |
| (void)0; |
| /*[[p-outer]]*/ |
| } |
| )"; |
| // The key property that we are verifying is implicit in `runDataflow` -- |
| // namely, that the analysis succeeds, rather than hitting the maximum number |
| // of iterations. |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p-inner", "p-outer")); |
| const Environment &InnerEnv = |
| getEnvironmentAtAnnotation(Results, "p-inner"); |
| const Environment &OuterEnv = |
| getEnvironmentAtAnnotation(Results, "p-outer"); |
| |
| const ValueDecl *ValDecl = findValueDecl(ASTCtx, "val"); |
| ASSERT_THAT(ValDecl, NotNull()); |
| |
| const ValueDecl *LDecl = findValueDecl(ASTCtx, "l"); |
| ASSERT_THAT(LDecl, NotNull()); |
| |
| // Inner. |
| auto *LVal = dyn_cast<PointerValue>(InnerEnv.getValue(*LDecl)); |
| ASSERT_THAT(LVal, NotNull()); |
| |
| EXPECT_EQ(&LVal->getPointeeLoc(), |
| InnerEnv.getStorageLocation(*ValDecl)); |
| |
| // Outer. |
| LVal = dyn_cast<PointerValue>(OuterEnv.getValue(*LDecl)); |
| ASSERT_THAT(LVal, NotNull()); |
| |
| // The loop body may not have been executed, so we should not conclude |
| // that `l` points to `val`. |
| EXPECT_NE(&LVal->getPointeeLoc(), |
| OuterEnv.getStorageLocation(*ValDecl)); |
| }); |
| } |
| |
| TEST(TransferTest, LoopDereferencingChangingPointerConverges) { |
| std::string Code = R"cc( |
| bool some_condition(); |
| |
| void target(int i1, int i2) { |
| int *p = &i1; |
| while (true) { |
| (void)*p; |
| if (some_condition()) |
| p = &i1; |
| else |
| p = &i2; |
| } |
| } |
| )cc"; |
| ASSERT_THAT_ERROR(checkDataflowWithNoopAnalysis(Code), llvm::Succeeded()); |
| } |
| |
| TEST(TransferTest, LoopDereferencingChangingRecordPointerConverges) { |
| std::string Code = R"cc( |
| struct Lookup { |
| int x; |
| }; |
| |
| bool some_condition(); |
| |
| void target(Lookup l1, Lookup l2) { |
| Lookup *l = &l1; |
| while (true) { |
| (void)l->x; |
| if (some_condition()) |
| l = &l1; |
| else |
| l = &l2; |
| } |
| } |
| )cc"; |
| ASSERT_THAT_ERROR(checkDataflowWithNoopAnalysis(Code), llvm::Succeeded()); |
| } |
| |
| TEST(TransferTest, LoopWithShortCircuitedConditionConverges) { |
| std::string Code = R"cc( |
| bool foo(); |
| |
| void target() { |
| bool c = false; |
| while (foo() || foo()) { |
| c = true; |
| } |
| } |
| )cc"; |
| ASSERT_THAT_ERROR(checkDataflowWithNoopAnalysis(Code), llvm::Succeeded()); |
| } |
| |
| TEST(TransferTest, LoopCanProveInvariantForBoolean) { |
| // Check that we can prove `b` is always false in the loop. |
| // This test exercises the logic in `widenDistinctValues()` that preserves |
| // information if the boolean can be proved to be either true or false in both |
| // the previous and current iteration. |
| std::string Code = R"cc( |
| int return_int(); |
| void target() { |
| bool b = return_int() == 0; |
| if (b) return; |
| while (true) { |
| b; |
| // [[p]] |
| b = return_int() == 0; |
| if (b) return; |
| } |
| } |
| )cc"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| auto &BVal = getValueForDecl<BoolValue>(ASTCtx, Env, "b"); |
| EXPECT_TRUE(Env.proves(Env.arena().makeNot(BVal.formula()))); |
| }); |
| } |
| |
| TEST(TransferTest, DoesNotCrashOnUnionThisExpr) { |
| std::string Code = R"cc( |
| union Union { |
| int A; |
| float B; |
| }; |
| |
| void foo() { |
| Union A; |
| Union B; |
| A = B; |
| } |
| )cc"; |
| // This is a crash regression test when calling the transfer function on a |
| // `CXXThisExpr` that refers to a union. |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &, |
| ASTContext &) {}, |
| LangStandard::lang_cxx17, /*ApplyBuiltinTransfer=*/true, "operator="); |
| } |
| |
| TEST(TransferTest, DoesNotCrashOnNullChildren) { |
| std::string Code = (CoroutineLibrary + R"cc( |
| task target() noexcept { |
| co_return; |
| } |
| )cc") |
| .str(); |
| // This is a crash regression test when calling `AdornedCFG::build` on a |
| // statement (in this case, the `CoroutineBodyStmt`) with null children. |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &, |
| ASTContext &) {}, |
| LangStandard::lang_cxx20, /*ApplyBuiltinTransfer=*/true); |
| } |
| |
| TEST(TransferTest, StructuredBindingAssignFromStructIntMembersToRefs) { |
| std::string Code = R"( |
| struct A { |
| int Foo; |
| int Bar; |
| }; |
| |
| void target() { |
| int Qux; |
| A Baz; |
| Baz.Foo = Qux; |
| auto &FooRef = Baz.Foo; |
| auto &BarRef = Baz.Bar; |
| auto &[BoundFooRef, BoundBarRef] = Baz; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooRefDecl = findValueDecl(ASTCtx, "FooRef"); |
| ASSERT_THAT(FooRefDecl, NotNull()); |
| |
| const ValueDecl *BarRefDecl = findValueDecl(ASTCtx, "BarRef"); |
| ASSERT_THAT(BarRefDecl, NotNull()); |
| |
| const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "Qux"); |
| ASSERT_THAT(QuxDecl, NotNull()); |
| |
| const ValueDecl *BoundFooRefDecl = findValueDecl(ASTCtx, "BoundFooRef"); |
| ASSERT_THAT(BoundFooRefDecl, NotNull()); |
| |
| const ValueDecl *BoundBarRefDecl = findValueDecl(ASTCtx, "BoundBarRef"); |
| ASSERT_THAT(BoundBarRefDecl, NotNull()); |
| |
| const StorageLocation *FooRefLoc = Env.getStorageLocation(*FooRefDecl); |
| ASSERT_THAT(FooRefLoc, NotNull()); |
| |
| const StorageLocation *BarRefLoc = Env.getStorageLocation(*BarRefDecl); |
| ASSERT_THAT(BarRefLoc, NotNull()); |
| |
| const Value *QuxVal = Env.getValue(*QuxDecl); |
| ASSERT_THAT(QuxVal, NotNull()); |
| |
| const StorageLocation *BoundFooRefLoc = |
| Env.getStorageLocation(*BoundFooRefDecl); |
| EXPECT_EQ(BoundFooRefLoc, FooRefLoc); |
| |
| const StorageLocation *BoundBarRefLoc = |
| Env.getStorageLocation(*BoundBarRefDecl); |
| EXPECT_EQ(BoundBarRefLoc, BarRefLoc); |
| |
| EXPECT_EQ(Env.getValue(*BoundFooRefDecl), QuxVal); |
| }); |
| } |
| |
| TEST(TransferTest, StructuredBindingAssignFromStructRefMembersToRefs) { |
| std::string Code = R"( |
| struct A { |
| int &Foo; |
| int &Bar; |
| }; |
| |
| void target(A Baz) { |
| int Qux; |
| Baz.Foo = Qux; |
| auto &FooRef = Baz.Foo; |
| auto &BarRef = Baz.Bar; |
| auto &[BoundFooRef, BoundBarRef] = Baz; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooRefDecl = findValueDecl(ASTCtx, "FooRef"); |
| ASSERT_THAT(FooRefDecl, NotNull()); |
| |
| const ValueDecl *BarRefDecl = findValueDecl(ASTCtx, "BarRef"); |
| ASSERT_THAT(BarRefDecl, NotNull()); |
| |
| const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "Qux"); |
| ASSERT_THAT(QuxDecl, NotNull()); |
| |
| const ValueDecl *BoundFooRefDecl = findValueDecl(ASTCtx, "BoundFooRef"); |
| ASSERT_THAT(BoundFooRefDecl, NotNull()); |
| |
| const ValueDecl *BoundBarRefDecl = findValueDecl(ASTCtx, "BoundBarRef"); |
| ASSERT_THAT(BoundBarRefDecl, NotNull()); |
| |
| const StorageLocation *FooRefLoc = Env.getStorageLocation(*FooRefDecl); |
| ASSERT_THAT(FooRefLoc, NotNull()); |
| |
| const StorageLocation *BarRefLoc = Env.getStorageLocation(*BarRefDecl); |
| ASSERT_THAT(BarRefLoc, NotNull()); |
| |
| const Value *QuxVal = Env.getValue(*QuxDecl); |
| ASSERT_THAT(QuxVal, NotNull()); |
| |
| const StorageLocation *BoundFooRefLoc = |
| Env.getStorageLocation(*BoundFooRefDecl); |
| EXPECT_EQ(BoundFooRefLoc, FooRefLoc); |
| |
| const StorageLocation *BoundBarRefLoc = |
| Env.getStorageLocation(*BoundBarRefDecl); |
| EXPECT_EQ(BoundBarRefLoc, BarRefLoc); |
| |
| EXPECT_EQ(Env.getValue(*BoundFooRefDecl), QuxVal); |
| }); |
| } |
| |
| TEST(TransferTest, StructuredBindingAssignFromStructIntMembersToInts) { |
| std::string Code = R"( |
| struct A { |
| int Foo; |
| int Bar; |
| }; |
| |
| void target() { |
| int Qux; |
| A Baz; |
| Baz.Foo = Qux; |
| auto &FooRef = Baz.Foo; |
| auto &BarRef = Baz.Bar; |
| auto [BoundFoo, BoundBar] = Baz; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooRefDecl = findValueDecl(ASTCtx, "FooRef"); |
| ASSERT_THAT(FooRefDecl, NotNull()); |
| |
| const ValueDecl *BarRefDecl = findValueDecl(ASTCtx, "BarRef"); |
| ASSERT_THAT(BarRefDecl, NotNull()); |
| |
| const ValueDecl *BoundFooDecl = findValueDecl(ASTCtx, "BoundFoo"); |
| ASSERT_THAT(BoundFooDecl, NotNull()); |
| |
| const ValueDecl *BoundBarDecl = findValueDecl(ASTCtx, "BoundBar"); |
| ASSERT_THAT(BoundBarDecl, NotNull()); |
| |
| const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "Qux"); |
| ASSERT_THAT(QuxDecl, NotNull()); |
| |
| const StorageLocation *FooRefLoc = Env.getStorageLocation(*FooRefDecl); |
| ASSERT_THAT(FooRefLoc, NotNull()); |
| |
| const StorageLocation *BarRefLoc = Env.getStorageLocation(*BarRefDecl); |
| ASSERT_THAT(BarRefLoc, NotNull()); |
| |
| const Value *QuxVal = Env.getValue(*QuxDecl); |
| ASSERT_THAT(QuxVal, NotNull()); |
| |
| const StorageLocation *BoundFooLoc = |
| Env.getStorageLocation(*BoundFooDecl); |
| EXPECT_NE(BoundFooLoc, FooRefLoc); |
| |
| const StorageLocation *BoundBarLoc = |
| Env.getStorageLocation(*BoundBarDecl); |
| EXPECT_NE(BoundBarLoc, BarRefLoc); |
| |
| EXPECT_EQ(Env.getValue(*BoundFooDecl), QuxVal); |
| }); |
| } |
| |
| TEST(TransferTest, StructuredBindingAssignFromTupleLikeType) { |
| std::string Code = R"( |
| namespace std { |
| using size_t = int; |
| template <class> struct tuple_size; |
| template <std::size_t, class> struct tuple_element; |
| template <class...> class tuple; |
| |
| namespace { |
| template <class T, T v> |
| struct size_helper { static const T value = v; }; |
| } // namespace |
| |
| template <class... T> |
| struct tuple_size<tuple<T...>> : size_helper<std::size_t, sizeof...(T)> {}; |
| |
| template <std::size_t I, class... T> |
| struct tuple_element<I, tuple<T...>> { |
| using type = __type_pack_element<I, T...>; |
| }; |
| |
| template <class...> class tuple {}; |
| |
| template <std::size_t I, class... T> |
| typename tuple_element<I, tuple<T...>>::type get(tuple<T...>); |
| } // namespace std |
| |
| std::tuple<bool, int> makeTuple(); |
| |
| void target(bool B) { |
| auto [BoundFoo, BoundBar] = makeTuple(); |
| bool Baz; |
| // Include if-then-else to test interaction of `BindingDecl` with join. |
| if (B) { |
| Baz = BoundFoo; |
| (void)BoundBar; |
| // [[p1]] |
| } else { |
| Baz = BoundFoo; |
| } |
| (void)0; |
| // [[p2]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p1", "p2")); |
| const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1"); |
| |
| const ValueDecl *BoundFooDecl = findValueDecl(ASTCtx, "BoundFoo"); |
| ASSERT_THAT(BoundFooDecl, NotNull()); |
| |
| const ValueDecl *BoundBarDecl = findValueDecl(ASTCtx, "BoundBar"); |
| ASSERT_THAT(BoundBarDecl, NotNull()); |
| |
| const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz"); |
| ASSERT_THAT(BazDecl, NotNull()); |
| |
| // BindingDecls always map to references -- either lvalue or rvalue, so |
| // we still need to skip here. |
| const Value *BoundFooValue = Env1.getValue(*BoundFooDecl); |
| ASSERT_THAT(BoundFooValue, NotNull()); |
| EXPECT_TRUE(isa<BoolValue>(BoundFooValue)); |
| |
| const Value *BoundBarValue = Env1.getValue(*BoundBarDecl); |
| ASSERT_THAT(BoundBarValue, NotNull()); |
| EXPECT_TRUE(isa<IntegerValue>(BoundBarValue)); |
| |
| // Test that a `DeclRefExpr` to a `BindingDecl` works as expected. |
| EXPECT_EQ(Env1.getValue(*BazDecl), BoundFooValue); |
| |
| const Environment &Env2 = getEnvironmentAtAnnotation(Results, "p2"); |
| |
| // Test that `BoundFooDecl` retains the value we expect, after the join. |
| BoundFooValue = Env2.getValue(*BoundFooDecl); |
| EXPECT_EQ(Env2.getValue(*BazDecl), BoundFooValue); |
| }); |
| } |
| |
| TEST(TransferTest, StructuredBindingAssignRefFromTupleLikeType) { |
| std::string Code = R"( |
| namespace std { |
| using size_t = int; |
| template <class> struct tuple_size; |
| template <std::size_t, class> struct tuple_element; |
| template <class...> class tuple; |
| |
| namespace { |
| template <class T, T v> |
| struct size_helper { static const T value = v; }; |
| } // namespace |
| |
| template <class... T> |
| struct tuple_size<tuple<T...>> : size_helper<std::size_t, sizeof...(T)> {}; |
| |
| template <std::size_t I, class... T> |
| struct tuple_element<I, tuple<T...>> { |
| using type = __type_pack_element<I, T...>; |
| }; |
| |
| template <class...> class tuple {}; |
| |
| template <std::size_t I, class... T> |
| typename tuple_element<I, tuple<T...>>::type get(tuple<T...>); |
| } // namespace std |
| |
| std::tuple<bool, int> &getTuple(); |
| |
| void target(bool B) { |
| auto &[BoundFoo, BoundBar] = getTuple(); |
| bool Baz; |
| // Include if-then-else to test interaction of `BindingDecl` with join. |
| if (B) { |
| Baz = BoundFoo; |
| (void)BoundBar; |
| // [[p1]] |
| } else { |
| Baz = BoundFoo; |
| } |
| (void)0; |
| // [[p2]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p1", "p2")); |
| const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1"); |
| |
| const ValueDecl *BoundFooDecl = findValueDecl(ASTCtx, "BoundFoo"); |
| ASSERT_THAT(BoundFooDecl, NotNull()); |
| |
| const ValueDecl *BoundBarDecl = findValueDecl(ASTCtx, "BoundBar"); |
| ASSERT_THAT(BoundBarDecl, NotNull()); |
| |
| const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz"); |
| ASSERT_THAT(BazDecl, NotNull()); |
| |
| const Value *BoundFooValue = Env1.getValue(*BoundFooDecl); |
| ASSERT_THAT(BoundFooValue, NotNull()); |
| EXPECT_TRUE(isa<BoolValue>(BoundFooValue)); |
| |
| const Value *BoundBarValue = Env1.getValue(*BoundBarDecl); |
| ASSERT_THAT(BoundBarValue, NotNull()); |
| EXPECT_TRUE(isa<IntegerValue>(BoundBarValue)); |
| |
| // Test that a `DeclRefExpr` to a `BindingDecl` (with reference type) |
| // works as expected. We don't test aliasing properties of the |
| // reference, because we don't model `std::get` and so have no way to |
| // equate separate references into the tuple. |
| EXPECT_EQ(Env1.getValue(*BazDecl), BoundFooValue); |
| |
| const Environment &Env2 = getEnvironmentAtAnnotation(Results, "p2"); |
| |
| // Test that `BoundFooDecl` retains the value we expect, after the join. |
| BoundFooValue = Env2.getValue(*BoundFooDecl); |
| EXPECT_EQ(Env2.getValue(*BazDecl), BoundFooValue); |
| }); |
| } |
| |
| TEST(TransferTest, BinaryOperatorComma) { |
| std::string Code = R"( |
| void target(int Foo, int Bar) { |
| int &Baz = (Foo, Bar); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz"); |
| ASSERT_THAT(BazDecl, NotNull()); |
| |
| const StorageLocation *BarLoc = Env.getStorageLocation(*BarDecl); |
| ASSERT_THAT(BarLoc, NotNull()); |
| |
| const StorageLocation *BazLoc = Env.getStorageLocation(*BazDecl); |
| EXPECT_EQ(BazLoc, BarLoc); |
| }); |
| } |
| |
| TEST(TransferTest, ConditionalOperatorValue) { |
| std::string Code = R"( |
| void target(bool Cond, bool B1, bool B2) { |
| bool JoinSame = Cond ? B1 : B1; |
| bool JoinDifferent = Cond ? B1 : B2; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| Environment Env = getEnvironmentAtAnnotation(Results, "p").fork(); |
| |
| auto &B1 = getValueForDecl<BoolValue>(ASTCtx, Env, "B1"); |
| auto &B2 = getValueForDecl<BoolValue>(ASTCtx, Env, "B2"); |
| auto &JoinSame = getValueForDecl<BoolValue>(ASTCtx, Env, "JoinSame"); |
| auto &JoinDifferent = |
| getValueForDecl<BoolValue>(ASTCtx, Env, "JoinDifferent"); |
| |
| EXPECT_EQ(&JoinSame, &B1); |
| |
| const Formula &JoinDifferentEqB1 = |
| Env.arena().makeEquals(JoinDifferent.formula(), B1.formula()); |
| EXPECT_TRUE(Env.allows(JoinDifferentEqB1)); |
| EXPECT_FALSE(Env.proves(JoinDifferentEqB1)); |
| |
| const Formula &JoinDifferentEqB2 = |
| Env.arena().makeEquals(JoinDifferent.formula(), B2.formula()); |
| EXPECT_TRUE(Env.allows(JoinDifferentEqB2)); |
| EXPECT_FALSE(Env.proves(JoinDifferentEqB1)); |
| }); |
| } |
| |
| TEST(TransferTest, ConditionalOperatorLocation) { |
| std::string Code = R"( |
| void target(bool Cond, int I1, int I2) { |
| int &JoinSame = Cond ? I1 : I1; |
| int &JoinDifferent = Cond ? I1 : I2; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| Environment Env = getEnvironmentAtAnnotation(Results, "p").fork(); |
| |
| StorageLocation &I1 = getLocForDecl(ASTCtx, Env, "I1"); |
| StorageLocation &I2 = getLocForDecl(ASTCtx, Env, "I2"); |
| StorageLocation &JoinSame = getLocForDecl(ASTCtx, Env, "JoinSame"); |
| StorageLocation &JoinDifferent = |
| getLocForDecl(ASTCtx, Env, "JoinDifferent"); |
| |
| EXPECT_EQ(&JoinSame, &I1); |
| |
| EXPECT_NE(&JoinDifferent, &I1); |
| EXPECT_NE(&JoinDifferent, &I2); |
| }); |
| } |
| |
| TEST(TransferTest, ConditionalOperatorOnConstantExpr) { |
| // This is a regression test: We used to crash when a `ConstantExpr` was used |
| // in the branches of a conditional operator. |
| std::string Code = R"cc( |
| consteval bool identity(bool B) { return B; } |
| void target(bool Cond) { |
| bool JoinTrueTrue = Cond ? identity(true) : identity(true); |
| bool JoinTrueFalse = Cond ? identity(true) : identity(false); |
| // [[p]] |
| } |
| )cc"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| Environment Env = getEnvironmentAtAnnotation(Results, "p").fork(); |
| |
| auto &JoinTrueTrue = |
| getValueForDecl<BoolValue>(ASTCtx, Env, "JoinTrueTrue"); |
| // FIXME: This test documents the current behavior, namely that we |
| // don't actually use the constant result of the `ConstantExpr` and |
| // instead treat it like a normal function call. |
| EXPECT_EQ(JoinTrueTrue.formula().kind(), Formula::Kind::AtomRef); |
| // EXPECT_TRUE(JoinTrueTrue.formula().literal()); |
| |
| auto &JoinTrueFalse = |
| getValueForDecl<BoolValue>(ASTCtx, Env, "JoinTrueFalse"); |
| EXPECT_EQ(JoinTrueFalse.formula().kind(), Formula::Kind::AtomRef); |
| }, |
| LangStandard::lang_cxx20); |
| } |
| |
| TEST(TransferTest, IfStmtBranchExtendsFlowCondition) { |
| std::string Code = R"( |
| void target(bool Foo) { |
| if (Foo) { |
| (void)0; |
| // [[if_then]] |
| } else { |
| (void)0; |
| // [[if_else]] |
| } |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("if_then", "if_else")); |
| const Environment &ThenEnv = |
| getEnvironmentAtAnnotation(Results, "if_then"); |
| const Environment &ElseEnv = |
| getEnvironmentAtAnnotation(Results, "if_else"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| auto &ThenFooVal= getFormula(*FooDecl, ThenEnv); |
| EXPECT_TRUE(ThenEnv.proves(ThenFooVal)); |
| |
| auto &ElseFooVal = getFormula(*FooDecl, ElseEnv); |
| EXPECT_TRUE(ElseEnv.proves(ElseEnv.arena().makeNot(ElseFooVal))); |
| }); |
| } |
| |
| TEST(TransferTest, WhileStmtBranchExtendsFlowCondition) { |
| std::string Code = R"( |
| void target(bool Foo) { |
| while (Foo) { |
| (void)0; |
| // [[loop_body]] |
| } |
| (void)0; |
| // [[after_loop]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), |
| UnorderedElementsAre("loop_body", "after_loop")); |
| const Environment &LoopBodyEnv = |
| getEnvironmentAtAnnotation(Results, "loop_body"); |
| const Environment &AfterLoopEnv = |
| getEnvironmentAtAnnotation(Results, "after_loop"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| auto &LoopBodyFooVal = getFormula(*FooDecl, LoopBodyEnv); |
| EXPECT_TRUE(LoopBodyEnv.proves(LoopBodyFooVal)); |
| |
| auto &AfterLoopFooVal = getFormula(*FooDecl, AfterLoopEnv); |
| EXPECT_TRUE( |
| AfterLoopEnv.proves(AfterLoopEnv.arena().makeNot(AfterLoopFooVal))); |
| }); |
| } |
| |
| TEST(TransferTest, DoWhileStmtBranchExtendsFlowCondition) { |
| std::string Code = R"( |
| void target(bool Foo) { |
| bool Bar = true; |
| do { |
| (void)0; |
| // [[loop_body]] |
| Bar = false; |
| } while (Foo); |
| (void)0; |
| // [[after_loop]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), |
| UnorderedElementsAre("loop_body", "after_loop")); |
| const Environment &LoopBodyEnv = |
| getEnvironmentAtAnnotation(Results, "loop_body"); |
| const Environment &AfterLoopEnv = |
| getEnvironmentAtAnnotation(Results, "after_loop"); |
| auto &A = AfterLoopEnv.arena(); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| auto &LoopBodyFooVal= getFormula(*FooDecl, LoopBodyEnv); |
| auto &LoopBodyBarVal = getFormula(*BarDecl, LoopBodyEnv); |
| EXPECT_TRUE( |
| LoopBodyEnv.proves(A.makeOr(LoopBodyBarVal, LoopBodyFooVal))); |
| |
| auto &AfterLoopFooVal = getFormula(*FooDecl, AfterLoopEnv); |
| auto &AfterLoopBarVal = getFormula(*BarDecl, AfterLoopEnv); |
| EXPECT_TRUE(AfterLoopEnv.proves(A.makeNot(AfterLoopFooVal))); |
| EXPECT_TRUE(AfterLoopEnv.proves(A.makeNot(AfterLoopBarVal))); |
| }); |
| } |
| |
| TEST(TransferTest, ForStmtBranchExtendsFlowCondition) { |
| std::string Code = R"( |
| void target(bool Foo) { |
| for (; Foo;) { |
| (void)0; |
| // [[loop_body]] |
| } |
| (void)0; |
| // [[after_loop]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), |
| UnorderedElementsAre("loop_body", "after_loop")); |
| const Environment &LoopBodyEnv = |
| getEnvironmentAtAnnotation(Results, "loop_body"); |
| const Environment &AfterLoopEnv = |
| getEnvironmentAtAnnotation(Results, "after_loop"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| auto &LoopBodyFooVal= getFormula(*FooDecl, LoopBodyEnv); |
| EXPECT_TRUE(LoopBodyEnv.proves(LoopBodyFooVal)); |
| |
| auto &AfterLoopFooVal = getFormula(*FooDecl, AfterLoopEnv); |
| EXPECT_TRUE( |
| AfterLoopEnv.proves(AfterLoopEnv.arena().makeNot(AfterLoopFooVal))); |
| }); |
| } |
| |
| TEST(TransferTest, ForStmtBranchWithoutConditionDoesNotExtendFlowCondition) { |
| std::string Code = R"( |
| void target(bool Foo) { |
| for (;;) { |
| (void)0; |
| // [[loop_body]] |
| } |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("loop_body")); |
| const Environment &LoopBodyEnv = |
| getEnvironmentAtAnnotation(Results, "loop_body"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| auto &LoopBodyFooVal= getFormula(*FooDecl, LoopBodyEnv); |
| EXPECT_FALSE(LoopBodyEnv.proves(LoopBodyFooVal)); |
| }); |
| } |
| |
| TEST(TransferTest, ContextSensitiveOptionDisabled) { |
| std::string Code = R"( |
| bool GiveBool(); |
| void SetBool(bool &Var) { Var = true; } |
| |
| void target() { |
| bool Foo = GiveBool(); |
| SetBool(Foo); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| auto &FooVal = getFormula(*FooDecl, Env); |
| EXPECT_FALSE(Env.proves(FooVal)); |
| EXPECT_FALSE(Env.proves(Env.arena().makeNot(FooVal))); |
| }, |
| {BuiltinOptions{/*.ContextSensitiveOpts=*/std::nullopt}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveReturnReference) { |
| std::string Code = R"( |
| class S {}; |
| S& target(bool b, S &s) { |
| return s; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *SDecl = findValueDecl(ASTCtx, "s"); |
| ASSERT_THAT(SDecl, NotNull()); |
| |
| auto *SLoc = Env.getStorageLocation(*SDecl); |
| ASSERT_THAT(SLoc, NotNull()); |
| |
| ASSERT_THAT(Env.getReturnStorageLocation(), Eq(SLoc)); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{}}}); |
| } |
| |
| // This test is a regression test, based on a real crash. |
| TEST(TransferTest, ContextSensitiveReturnReferenceWithConditionalOperator) { |
| std::string Code = R"( |
| class S {}; |
| S& target(bool b, S &s) { |
| return b ? s : s; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *SDecl = findValueDecl(ASTCtx, "s"); |
| ASSERT_THAT(SDecl, NotNull()); |
| |
| auto *SLoc = Env.getStorageLocation(*SDecl); |
| EXPECT_THAT(SLoc, NotNull()); |
| |
| auto *Loc = Env.getReturnStorageLocation(); |
| EXPECT_THAT(Loc, NotNull()); |
| |
| EXPECT_EQ(Loc, SLoc); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{}}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveReturnOneOfTwoReferences) { |
| std::string Code = R"( |
| class S {}; |
| S &callee(bool b, S &s1_parm, S &s2_parm) { |
| if (b) |
| return s1_parm; |
| else |
| return s2_parm; |
| } |
| void target(bool b) { |
| S s1; |
| S s2; |
| S &return_s1 = s1; |
| S &return_s2 = s2; |
| S &return_dont_know = callee(b, s1, s2); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *S1 = findValueDecl(ASTCtx, "s1"); |
| ASSERT_THAT(S1, NotNull()); |
| const ValueDecl *S2 = findValueDecl(ASTCtx, "s2"); |
| ASSERT_THAT(S2, NotNull()); |
| const ValueDecl *ReturnS1 = findValueDecl(ASTCtx, "return_s1"); |
| ASSERT_THAT(ReturnS1, NotNull()); |
| const ValueDecl *ReturnS2 = findValueDecl(ASTCtx, "return_s2"); |
| ASSERT_THAT(ReturnS2, NotNull()); |
| const ValueDecl *ReturnDontKnow = |
| findValueDecl(ASTCtx, "return_dont_know"); |
| ASSERT_THAT(ReturnDontKnow, NotNull()); |
| |
| StorageLocation *S1Loc = Env.getStorageLocation(*S1); |
| StorageLocation *S2Loc = Env.getStorageLocation(*S2); |
| |
| EXPECT_THAT(Env.getStorageLocation(*ReturnS1), Eq(S1Loc)); |
| EXPECT_THAT(Env.getStorageLocation(*ReturnS2), Eq(S2Loc)); |
| |
| // In the case where we don't have a consistent storage location for |
| // the return value, the framework creates a new storage location, which |
| // should be different from the storage locations of `s1` and `s2`. |
| EXPECT_THAT(Env.getStorageLocation(*ReturnDontKnow), Ne(S1Loc)); |
| EXPECT_THAT(Env.getStorageLocation(*ReturnDontKnow), Ne(S2Loc)); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{}}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveDepthZero) { |
| std::string Code = R"( |
| bool GiveBool(); |
| void SetBool(bool &Var) { Var = true; } |
| |
| void target() { |
| bool Foo = GiveBool(); |
| SetBool(Foo); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| auto &FooVal = getFormula(*FooDecl, Env); |
| EXPECT_FALSE(Env.proves(FooVal)); |
| EXPECT_FALSE(Env.proves(Env.arena().makeNot(FooVal))); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{/*.Depth=*/0}}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveSetTrue) { |
| std::string Code = R"( |
| bool GiveBool(); |
| void SetBool(bool &Var) { Var = true; } |
| |
| void target() { |
| bool Foo = GiveBool(); |
| SetBool(Foo); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| auto &FooVal = getFormula(*FooDecl, Env); |
| EXPECT_TRUE(Env.proves(FooVal)); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{}}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveSetFalse) { |
| std::string Code = R"( |
| bool GiveBool(); |
| void SetBool(bool &Var) { Var = false; } |
| |
| void target() { |
| bool Foo = GiveBool(); |
| SetBool(Foo); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| auto &FooVal = getFormula(*FooDecl, Env); |
| EXPECT_TRUE(Env.proves(Env.arena().makeNot(FooVal))); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{}}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveSetBothTrueAndFalse) { |
| std::string Code = R"( |
| bool GiveBool(); |
| void SetBool(bool &Var, bool Val) { Var = Val; } |
| |
| void target() { |
| bool Foo = GiveBool(); |
| bool Bar = GiveBool(); |
| SetBool(Foo, true); |
| SetBool(Bar, false); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| auto &A = Env.arena(); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| auto &FooVal = getFormula(*FooDecl, Env); |
| EXPECT_TRUE(Env.proves(FooVal)); |
| EXPECT_FALSE(Env.proves(A.makeNot(FooVal))); |
| |
| auto &BarVal = getFormula(*BarDecl, Env); |
| EXPECT_FALSE(Env.proves(BarVal)); |
| EXPECT_TRUE(Env.proves(A.makeNot(BarVal))); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{}}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveSetTwoLayersDepthOne) { |
| std::string Code = R"( |
| bool GiveBool(); |
| void SetBool1(bool &Var) { Var = true; } |
| void SetBool2(bool &Var) { SetBool1(Var); } |
| |
| void target() { |
| bool Foo = GiveBool(); |
| SetBool2(Foo); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| auto &FooVal = getFormula(*FooDecl, Env); |
| EXPECT_FALSE(Env.proves(FooVal)); |
| EXPECT_FALSE(Env.proves(Env.arena().makeNot(FooVal))); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{/*.Depth=*/1}}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveSetTwoLayersDepthTwo) { |
| std::string Code = R"( |
| bool GiveBool(); |
| void SetBool1(bool &Var) { Var = true; } |
| void SetBool2(bool &Var) { SetBool1(Var); } |
| |
| void target() { |
| bool Foo = GiveBool(); |
| SetBool2(Foo); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| auto &FooVal = getFormula(*FooDecl, Env); |
| EXPECT_TRUE(Env.proves(FooVal)); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{/*.Depth=*/2}}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveSetThreeLayersDepthTwo) { |
| std::string Code = R"( |
| bool GiveBool(); |
| void SetBool1(bool &Var) { Var = true; } |
| void SetBool2(bool &Var) { SetBool1(Var); } |
| void SetBool3(bool &Var) { SetBool2(Var); } |
| |
| void target() { |
| bool Foo = GiveBool(); |
| SetBool3(Foo); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| auto &FooVal = getFormula(*FooDecl, Env); |
| EXPECT_FALSE(Env.proves(FooVal)); |
| EXPECT_FALSE(Env.proves(Env.arena().makeNot(FooVal))); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{/*.Depth=*/2}}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveSetThreeLayersDepthThree) { |
| std::string Code = R"( |
| bool GiveBool(); |
| void SetBool1(bool &Var) { Var = true; } |
| void SetBool2(bool &Var) { SetBool1(Var); } |
| void SetBool3(bool &Var) { SetBool2(Var); } |
| |
| void target() { |
| bool Foo = GiveBool(); |
| SetBool3(Foo); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| auto &FooVal = getFormula(*FooDecl, Env); |
| EXPECT_TRUE(Env.proves(FooVal)); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{/*.Depth=*/3}}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveMutualRecursion) { |
| std::string Code = R"( |
| bool Pong(bool X, bool Y); |
| |
| bool Ping(bool X, bool Y) { |
| if (X) { |
| return Y; |
| } else { |
| return Pong(!X, Y); |
| } |
| } |
| |
| bool Pong(bool X, bool Y) { |
| if (Y) { |
| return X; |
| } else { |
| return Ping(X, !Y); |
| } |
| } |
| |
| void target() { |
| bool Foo = Ping(false, false); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| // The analysis doesn't crash... |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| auto &FooVal = getFormula(*FooDecl, Env); |
| // ... but it also can't prove anything here. |
| EXPECT_FALSE(Env.proves(FooVal)); |
| EXPECT_FALSE(Env.proves(Env.arena().makeNot(FooVal))); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{/*.Depth=*/4}}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveSetMultipleLines) { |
| std::string Code = R"( |
| void SetBools(bool &Var1, bool &Var2) { |
| Var1 = true; |
| Var2 = false; |
| } |
| |
| void target() { |
| bool Foo = false; |
| bool Bar = true; |
| SetBools(Foo, Bar); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| auto &FooVal = getFormula(*FooDecl, Env); |
| EXPECT_TRUE(Env.proves(FooVal)); |
| EXPECT_FALSE(Env.proves(Env.arena().makeNot(FooVal))); |
| |
| auto &BarVal = getFormula(*BarDecl, Env); |
| EXPECT_FALSE(Env.proves(BarVal)); |
| EXPECT_TRUE(Env.proves(Env.arena().makeNot(BarVal))); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{}}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveSetMultipleBlocks) { |
| std::string Code = R"( |
| void IfCond(bool Cond, bool &Then, bool &Else) { |
| if (Cond) { |
| Then = true; |
| } else { |
| Else = true; |
| } |
| } |
| |
| void target() { |
| bool Foo = false; |
| bool Bar = false; |
| bool Baz = false; |
| IfCond(Foo, Bar, Baz); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz"); |
| ASSERT_THAT(BazDecl, NotNull()); |
| |
| auto &BarVal = getFormula(*BarDecl, Env); |
| EXPECT_FALSE(Env.proves(BarVal)); |
| EXPECT_TRUE(Env.proves(Env.arena().makeNot(BarVal))); |
| |
| auto &BazVal = getFormula(*BazDecl, Env); |
| EXPECT_TRUE(Env.proves(BazVal)); |
| EXPECT_FALSE(Env.proves(Env.arena().makeNot(BazVal))); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{}}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveReturnVoid) { |
| std::string Code = R"( |
| void Noop() { return; } |
| |
| void target() { |
| Noop(); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| // This just tests that the analysis doesn't crash. |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{}}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveReturnTrue) { |
| std::string Code = R"( |
| bool GiveBool() { return true; } |
| |
| void target() { |
| bool Foo = GiveBool(); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| auto &FooVal = getFormula(*FooDecl, Env); |
| EXPECT_TRUE(Env.proves(FooVal)); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{}}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveReturnFalse) { |
| std::string Code = R"( |
| bool GiveBool() { return false; } |
| |
| void target() { |
| bool Foo = GiveBool(); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| auto &FooVal = getFormula(*FooDecl, Env); |
| EXPECT_TRUE(Env.proves(Env.arena().makeNot(FooVal))); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{}}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveReturnArg) { |
| std::string Code = R"( |
| bool GiveBool(); |
| bool GiveBack(bool Arg) { return Arg; } |
| |
| void target() { |
| bool Foo = GiveBool(); |
| bool Bar = GiveBack(Foo); |
| bool Baz = Foo == Bar; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz"); |
| ASSERT_THAT(BazDecl, NotNull()); |
| |
| auto &BazVal = getFormula(*BazDecl, Env); |
| EXPECT_TRUE(Env.proves(BazVal)); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{}}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveReturnInt) { |
| std::string Code = R"( |
| int identity(int x) { return x; } |
| |
| void target() { |
| int y = identity(42); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| // This just tests that the analysis doesn't crash. |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{}}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveReturnRecord) { |
| std::string Code = R"( |
| struct S { |
| bool B; |
| }; |
| |
| S makeS(bool BVal) { return {BVal}; } |
| |
| void target() { |
| S FalseS = makeS(false); |
| S TrueS = makeS(true); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| auto &FalseSLoc = |
| getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "FalseS"); |
| auto &TrueSLoc = |
| getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "TrueS"); |
| |
| EXPECT_EQ(getFieldValue(&FalseSLoc, "B", ASTCtx, Env), |
| &Env.getBoolLiteralValue(false)); |
| EXPECT_EQ(getFieldValue(&TrueSLoc, "B", ASTCtx, Env), |
| &Env.getBoolLiteralValue(true)); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{}}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveReturnSelfReferentialRecord) { |
| std::string Code = R"( |
| struct S { |
| S() { self = this; } |
| S *self; |
| }; |
| |
| S makeS() { |
| // RVO guarantees that this will be constructed directly into `MyS`. |
| return S(); |
| } |
| |
| void target() { |
| S MyS = makeS(); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| auto &MySLoc = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "MyS"); |
| |
| auto *SelfVal = |
| cast<PointerValue>(getFieldValue(&MySLoc, "self", ASTCtx, Env)); |
| EXPECT_EQ(&SelfVal->getPointeeLoc(), &MySLoc); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{}}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveMethodLiteral) { |
| std::string Code = R"( |
| class MyClass { |
| public: |
| bool giveBool() { return true; } |
| }; |
| |
| void target() { |
| MyClass MyObj; |
| bool Foo = MyObj.giveBool(); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| auto &FooVal = getFormula(*FooDecl, Env); |
| EXPECT_TRUE(Env.proves(FooVal)); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{}}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveMethodGetter) { |
| std::string Code = R"( |
| class MyClass { |
| public: |
| bool getField() { return Field; } |
| |
| bool Field; |
| }; |
| |
| void target() { |
| MyClass MyObj; |
| MyObj.Field = true; |
| bool Foo = MyObj.getField(); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| auto &FooVal = getFormula(*FooDecl, Env); |
| EXPECT_TRUE(Env.proves(FooVal)); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{}}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveMethodSetter) { |
| std::string Code = R"( |
| class MyClass { |
| public: |
| void setField(bool Val) { Field = Val; } |
| |
| bool Field; |
| }; |
| |
| void target() { |
| MyClass MyObj; |
| MyObj.setField(true); |
| bool Foo = MyObj.Field; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| auto &FooVal = getFormula(*FooDecl, Env); |
| EXPECT_TRUE(Env.proves(FooVal)); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{}}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveMethodGetterAndSetter) { |
| std::string Code = R"( |
| class MyClass { |
| public: |
| bool getField() { return Field; } |
| void setField(bool Val) { Field = Val; } |
| |
| private: |
| bool Field; |
| }; |
| |
| void target() { |
| MyClass MyObj; |
| MyObj.setField(true); |
| bool Foo = MyObj.getField(); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| auto &FooVal = getFormula(*FooDecl, Env); |
| EXPECT_TRUE(Env.proves(FooVal)); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{}}}); |
| } |
| |
| |
| TEST(TransferTest, ContextSensitiveMethodTwoLayersVoid) { |
| std::string Code = R"( |
| class MyClass { |
| public: |
| void Inner() { MyField = true; } |
| void Outer() { Inner(); } |
| |
| bool MyField; |
| }; |
| |
| void target() { |
| MyClass MyObj; |
| MyObj.Outer(); |
| bool Foo = MyObj.MyField; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| ; |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| auto &FooVal = getFormula(*FooDecl, Env); |
| EXPECT_TRUE(Env.proves(FooVal)); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{}}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveMethodTwoLayersReturn) { |
| std::string Code = R"( |
| class MyClass { |
| public: |
| bool Inner() { return MyField; } |
| bool Outer() { return Inner(); } |
| |
| bool MyField; |
| }; |
| |
| void target() { |
| MyClass MyObj; |
| MyObj.MyField = true; |
| bool Foo = MyObj.Outer(); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| ; |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| auto &FooVal = getFormula(*FooDecl, Env); |
| EXPECT_TRUE(Env.proves(FooVal)); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{}}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveConstructorBody) { |
| std::string Code = R"( |
| class MyClass { |
| public: |
| MyClass() { MyField = true; } |
| |
| bool MyField; |
| }; |
| |
| void target() { |
| MyClass MyObj; |
| bool Foo = MyObj.MyField; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| auto &FooVal = getFormula(*FooDecl, Env); |
| EXPECT_TRUE(Env.proves(FooVal)); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{}}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveConstructorInitializer) { |
| std::string Code = R"( |
| class MyClass { |
| public: |
| MyClass() : MyField(true) {} |
| |
| bool MyField; |
| }; |
| |
| void target() { |
| MyClass MyObj; |
| bool Foo = MyObj.MyField; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| auto &FooVal = getFormula(*FooDecl, Env); |
| EXPECT_TRUE(Env.proves(FooVal)); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{}}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveConstructorDefault) { |
| std::string Code = R"( |
| class MyClass { |
| public: |
| MyClass() = default; |
| |
| bool MyField = true; |
| }; |
| |
| void target() { |
| MyClass MyObj; |
| bool Foo = MyObj.MyField; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| auto &FooVal = getFormula(*FooDecl, Env); |
| EXPECT_TRUE(Env.proves(FooVal)); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{}}}); |
| } |
| |
| TEST(TransferTest, ContextSensitiveSelfReferentialClass) { |
| // Test that the `this` pointer seen in the constructor has the same value |
| // as the address of the variable the object is constructed into. |
| std::string Code = R"( |
| class MyClass { |
| public: |
| MyClass() : Self(this) {} |
| MyClass *Self; |
| }; |
| |
| void target() { |
| MyClass MyObj; |
| MyClass *SelfPtr = MyObj.Self; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| |
| const ValueDecl *MyObjDecl = findValueDecl(ASTCtx, "MyObj"); |
| ASSERT_THAT(MyObjDecl, NotNull()); |
| |
| const ValueDecl *SelfDecl = findValueDecl(ASTCtx, "SelfPtr"); |
| ASSERT_THAT(SelfDecl, NotNull()); |
| |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| auto &SelfVal = *cast<PointerValue>(Env.getValue(*SelfDecl)); |
| EXPECT_EQ(Env.getStorageLocation(*MyObjDecl), &SelfVal.getPointeeLoc()); |
| }, |
| {BuiltinOptions{ContextSensitiveOptions{}}}); |
| } |
| |
| TEST(TransferTest, UnnamedBitfieldInitializer) { |
| std::string Code = R"( |
| struct B {}; |
| struct A { |
| unsigned a; |
| unsigned : 4; |
| unsigned c; |
| B b; |
| }; |
| void target() { |
| A a = {}; |
| A test = a; |
| (void)test.c; |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| // This doesn't need a body because this test was crashing the framework |
| // before handling correctly Unnamed bitfields in `InitListExpr`. |
| }); |
| } |
| |
| // Repro for a crash that used to occur with chained short-circuiting logical |
| // operators. |
| TEST(TransferTest, ChainedLogicalOps) { |
| std::string Code = R"( |
| bool target() { |
| bool b = true || false || false || false; |
| // [[p]] |
| return b; |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| auto &B = getValueForDecl<BoolValue>(ASTCtx, Env, "b").formula(); |
| EXPECT_TRUE(Env.proves(B)); |
| }); |
| } |
| |
| // Repro for a crash that used to occur when we call a `noreturn` function |
| // within one of the operands of a `&&` or `||` operator. |
| TEST(TransferTest, NoReturnFunctionInsideShortCircuitedBooleanOp) { |
| std::string Code = R"( |
| __attribute__((noreturn)) int doesnt_return(); |
| bool some_condition(); |
| void target(bool b1, bool b2) { |
| // Neither of these should crash. In addition, if we don't terminate the |
| // program, we know that the operators need to trigger the short-circuit |
| // logic, so `NoreturnOnRhsOfAnd` will be false and `NoreturnOnRhsOfOr` |
| // will be true. |
| bool NoreturnOnRhsOfAnd = b1 && doesnt_return() > 0; |
| bool NoreturnOnRhsOfOr = b2 || doesnt_return() > 0; |
| |
| // Calling a `noreturn` function on the LHS of an `&&` or `||` makes the |
| // entire expression unreachable. So we know that in both of the following |
| // cases, if `target()` terminates, the `else` branch was taken. |
| bool NoreturnOnLhsMakesAndUnreachable = false; |
| if (some_condition()) |
| doesnt_return() > 0 && some_condition(); |
| else |
| NoreturnOnLhsMakesAndUnreachable = true; |
| |
| bool NoreturnOnLhsMakesOrUnreachable = false; |
| if (some_condition()) |
| doesnt_return() > 0 || some_condition(); |
| else |
| NoreturnOnLhsMakesOrUnreachable = true; |
| |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| auto &A = Env.arena(); |
| |
| // Check that [[p]] is reachable with a non-false flow condition. |
| EXPECT_FALSE(Env.proves(A.makeLiteral(false))); |
| |
| auto &B1 = getValueForDecl<BoolValue>(ASTCtx, Env, "b1").formula(); |
| EXPECT_TRUE(Env.proves(A.makeNot(B1))); |
| |
| auto &NoreturnOnRhsOfAnd = |
| getValueForDecl<BoolValue>(ASTCtx, Env, "NoreturnOnRhsOfAnd").formula(); |
| EXPECT_TRUE(Env.proves(A.makeNot(NoreturnOnRhsOfAnd))); |
| |
| auto &B2 = getValueForDecl<BoolValue>(ASTCtx, Env, "b2").formula(); |
| EXPECT_TRUE(Env.proves(B2)); |
| |
| auto &NoreturnOnRhsOfOr = |
| getValueForDecl<BoolValue>(ASTCtx, Env, "NoreturnOnRhsOfOr") |
| .formula(); |
| EXPECT_TRUE(Env.proves(NoreturnOnRhsOfOr)); |
| |
| auto &NoreturnOnLhsMakesAndUnreachable = getValueForDecl<BoolValue>( |
| ASTCtx, Env, "NoreturnOnLhsMakesAndUnreachable").formula(); |
| EXPECT_TRUE(Env.proves(NoreturnOnLhsMakesAndUnreachable)); |
| |
| auto &NoreturnOnLhsMakesOrUnreachable = getValueForDecl<BoolValue>( |
| ASTCtx, Env, "NoreturnOnLhsMakesOrUnreachable").formula(); |
| EXPECT_TRUE(Env.proves(NoreturnOnLhsMakesOrUnreachable)); |
| }); |
| } |
| |
| TEST(TransferTest, NewExpressions) { |
| std::string Code = R"( |
| void target() { |
| int *p = new int(42); |
| // [[after_new]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = |
| getEnvironmentAtAnnotation(Results, "after_new"); |
| |
| auto &P = getValueForDecl<PointerValue>(ASTCtx, Env, "p"); |
| |
| EXPECT_THAT(Env.getValue(P.getPointeeLoc()), NotNull()); |
| }); |
| } |
| |
| TEST(TransferTest, NewExpressions_Structs) { |
| std::string Code = R"( |
| struct Inner { |
| int InnerField; |
| }; |
| |
| struct Outer { |
| Inner OuterField; |
| }; |
| |
| void target() { |
| Outer *p = new Outer; |
| // Access the fields to make sure the analysis actually generates children |
| // for them in the `RecordStorageLocation`. |
| p->OuterField.InnerField; |
| // [[after_new]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = |
| getEnvironmentAtAnnotation(Results, "after_new"); |
| |
| const ValueDecl *OuterField = findValueDecl(ASTCtx, "OuterField"); |
| const ValueDecl *InnerField = findValueDecl(ASTCtx, "InnerField"); |
| |
| auto &P = getValueForDecl<PointerValue>(ASTCtx, Env, "p"); |
| |
| auto &OuterLoc = cast<RecordStorageLocation>(P.getPointeeLoc()); |
| auto &OuterFieldLoc = |
| *cast<RecordStorageLocation>(OuterLoc.getChild(*OuterField)); |
| auto &InnerFieldLoc = *OuterFieldLoc.getChild(*InnerField); |
| |
| EXPECT_THAT(Env.getValue(InnerFieldLoc), NotNull()); |
| }); |
| } |
| |
| TEST(TransferTest, FunctionToPointerDecayHasValue) { |
| std::string Code = R"( |
| struct A { static void static_member_func(); }; |
| void target() { |
| // To check that we're treating function-to-pointer decay correctly, |
| // create two pointers, then verify they refer to the same storage |
| // location. |
| // We need to do the test this way because even if an initializer (in this |
| // case, the function-to-pointer decay) does not create a value, we still |
| // create a value for the variable. |
| void (*non_member_p1)() = target; |
| void (*non_member_p2)() = target; |
| |
| // Do the same thing but for a static member function. |
| void (*member_p1)() = A::static_member_func; |
| void (*member_p2)() = A::static_member_func; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| auto &NonMemberP1 = |
| getValueForDecl<PointerValue>(ASTCtx, Env, "non_member_p1"); |
| auto &NonMemberP2 = |
| getValueForDecl<PointerValue>(ASTCtx, Env, "non_member_p2"); |
| EXPECT_EQ(&NonMemberP1.getPointeeLoc(), &NonMemberP2.getPointeeLoc()); |
| |
| auto &MemberP1 = |
| getValueForDecl<PointerValue>(ASTCtx, Env, "member_p1"); |
| auto &MemberP2 = |
| getValueForDecl<PointerValue>(ASTCtx, Env, "member_p2"); |
| EXPECT_EQ(&MemberP1.getPointeeLoc(), &MemberP2.getPointeeLoc()); |
| }); |
| } |
| |
| // Check that a builtin function is not associated with a value. (It's only |
| // possible to call builtin functions directly, not take their address.) |
| TEST(TransferTest, BuiltinFunctionModeled) { |
| std::string Code = R"( |
| void target() { |
| __builtin_expect(0, 0); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| using ast_matchers::selectFirst; |
| using ast_matchers::match; |
| using ast_matchers::traverse; |
| using ast_matchers::implicitCastExpr; |
| using ast_matchers::hasCastKind; |
| |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| auto *ImplicitCast = selectFirst<ImplicitCastExpr>( |
| "implicit_cast", |
| match(traverse(TK_AsIs, |
| implicitCastExpr(hasCastKind(CK_BuiltinFnToFnPtr)) |
| .bind("implicit_cast")), |
| ASTCtx)); |
| |
| ASSERT_THAT(ImplicitCast, NotNull()); |
| EXPECT_THAT(Env.getValue(*ImplicitCast), IsNull()); |
| }); |
| } |
| |
| // Check that a callee of a member operator call is modeled as a `PointerValue`. |
| // Member operator calls are unusual in that their callee is a pointer that |
| // stems from a `FunctionToPointerDecay`. In calls to non-operator non-static |
| // member functions, the callee is a `MemberExpr` (which does not have pointer |
| // type). |
| // We want to make sure that we produce a pointer value for the callee in this |
| // specific scenario and that its storage location is durable (for convergence). |
| TEST(TransferTest, MemberOperatorCallModelsPointerForCallee) { |
| std::string Code = R"( |
| struct S { |
| bool operator!=(S s); |
| }; |
| void target() { |
| S s; |
| (void)(s != s); |
| (void)(s != s); |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| using ast_matchers::selectFirst; |
| using ast_matchers::match; |
| using ast_matchers::traverse; |
| using ast_matchers::cxxOperatorCallExpr; |
| |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| auto Matches = match( |
| traverse(TK_AsIs, cxxOperatorCallExpr().bind("call")), ASTCtx); |
| |
| ASSERT_EQ(Matches.size(), 2UL); |
| |
| auto *Call1 = Matches[0].getNodeAs<CXXOperatorCallExpr>("call"); |
| auto *Call2 = Matches[1].getNodeAs<CXXOperatorCallExpr>("call"); |
| |
| ASSERT_THAT(Call1, NotNull()); |
| ASSERT_THAT(Call2, NotNull()); |
| |
| EXPECT_EQ(cast<ImplicitCastExpr>(Call1->getCallee())->getCastKind(), |
| CK_FunctionToPointerDecay); |
| EXPECT_EQ(cast<ImplicitCastExpr>(Call2->getCallee())->getCastKind(), |
| CK_FunctionToPointerDecay); |
| |
| auto *Ptr1 = cast<PointerValue>(Env.getValue(*Call1->getCallee())); |
| auto *Ptr2 = cast<PointerValue>(Env.getValue(*Call2->getCallee())); |
| |
| ASSERT_EQ(&Ptr1->getPointeeLoc(), &Ptr2->getPointeeLoc()); |
| }); |
| } |
| |
| // Check that fields of anonymous records are modeled. |
| TEST(TransferTest, AnonymousStruct) { |
| std::string Code = R"( |
| struct S { |
| struct { |
| bool b; |
| }; |
| }; |
| void target() { |
| S s; |
| s.b = true; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| const ValueDecl *SDecl = findValueDecl(ASTCtx, "s"); |
| const ValueDecl *BDecl = findValueDecl(ASTCtx, "b"); |
| const IndirectFieldDecl *IndirectField = |
| findIndirectFieldDecl(ASTCtx, "b"); |
| |
| auto *S = cast<RecordStorageLocation>(Env.getStorageLocation(*SDecl)); |
| auto &AnonStruct = *cast<RecordStorageLocation>( |
| S->getChild(*cast<ValueDecl>(IndirectField->chain().front()))); |
| |
| auto *B = cast<BoolValue>(getFieldValue(&AnonStruct, *BDecl, Env)); |
| ASSERT_TRUE(Env.proves(B->formula())); |
| }); |
| } |
| |
| TEST(TransferTest, AnonymousStructWithInitializer) { |
| std::string Code = R"( |
| struct target { |
| target() { |
| (void)0; |
| // [[p]] |
| } |
| struct { |
| bool b = true; |
| }; |
| }; |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| const ValueDecl *BDecl = findValueDecl(ASTCtx, "b"); |
| const IndirectFieldDecl *IndirectField = |
| findIndirectFieldDecl(ASTCtx, "b"); |
| |
| auto *ThisLoc = |
| cast<RecordStorageLocation>(Env.getThisPointeeStorageLocation()); |
| auto &AnonStruct = *cast<RecordStorageLocation>(ThisLoc->getChild( |
| *cast<ValueDecl>(IndirectField->chain().front()))); |
| |
| auto *B = cast<BoolValue>(getFieldValue(&AnonStruct, *BDecl, Env)); |
| ASSERT_TRUE(Env.proves(B->formula())); |
| }); |
| } |
| |
| TEST(TransferTest, AnonymousStructWithReferenceField) { |
| std::string Code = R"( |
| int global_i = 0; |
| struct target { |
| target() { |
| (void)0; |
| // [[p]] |
| } |
| struct { |
| int &i = global_i; |
| }; |
| }; |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| const ValueDecl *GlobalIDecl = findValueDecl(ASTCtx, "global_i"); |
| const ValueDecl *IDecl = findValueDecl(ASTCtx, "i"); |
| const IndirectFieldDecl *IndirectField = |
| findIndirectFieldDecl(ASTCtx, "i"); |
| |
| auto *ThisLoc = |
| cast<RecordStorageLocation>(Env.getThisPointeeStorageLocation()); |
| auto &AnonStruct = *cast<RecordStorageLocation>(ThisLoc->getChild( |
| *cast<ValueDecl>(IndirectField->chain().front()))); |
| |
| ASSERT_EQ(AnonStruct.getChild(*IDecl), |
| Env.getStorageLocation(*GlobalIDecl)); |
| }); |
| } |
| |
| TEST(TransferTest, EvaluateBlockWithUnreachablePreds) { |
| // This is a crash repro. |
| // `false` block may not have been processed when we try to evaluate the `||` |
| // after visiting `true`, because it is not necessary (and therefore the edge |
| // is marked unreachable). Trying to get the analysis state via |
| // `getEnvironment` for the subexpression still should not crash. |
| std::string Code = R"( |
| int target(int i) { |
| if ((i < 0 && true) || false) { |
| return 0; |
| } |
| return 0; |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) {}); |
| } |
| |
| TEST(TransferTest, LambdaCaptureByCopy) { |
| std::string Code = R"( |
| void target(int Foo, int Bar) { |
| [Foo]() { |
| (void)0; |
| // [[p]] |
| }(); |
| } |
| )"; |
| runDataflowOnLambda( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| 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); |
| EXPECT_TRUE(isa_and_nonnull<IntegerValue>(FooVal)); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const StorageLocation *BarLoc = Env.getStorageLocation(*BarDecl); |
| EXPECT_THAT(BarLoc, IsNull()); |
| }); |
| } |
| |
| TEST(TransferTest, LambdaCaptureByReference) { |
| std::string Code = R"( |
| void target(int Foo, int Bar) { |
| [&Foo]() { |
| (void)0; |
| // [[p]] |
| }(); |
| } |
| )"; |
| runDataflowOnLambda( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| 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); |
| EXPECT_TRUE(isa_and_nonnull<IntegerValue>(FooVal)); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const StorageLocation *BarLoc = Env.getStorageLocation(*BarDecl); |
| EXPECT_THAT(BarLoc, IsNull()); |
| }); |
| } |
| |
| TEST(TransferTest, LambdaCaptureWithInitializer) { |
| std::string Code = R"( |
| void target(int Bar) { |
| [Foo=Bar]() { |
| (void)0; |
| // [[p]] |
| }(); |
| } |
| )"; |
| runDataflowOnLambda( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| 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); |
| EXPECT_TRUE(isa_and_nonnull<IntegerValue>(FooVal)); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| const StorageLocation *BarLoc = Env.getStorageLocation(*BarDecl); |
| EXPECT_THAT(BarLoc, IsNull()); |
| }); |
| } |
| |
| TEST(TransferTest, LambdaCaptureByCopyImplicit) { |
| std::string Code = R"( |
| void target(int Foo, int Bar) { |
| [=]() { |
| Foo; |
| // [[p]] |
| }(); |
| } |
| )"; |
| runDataflowOnLambda( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| 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); |
| EXPECT_TRUE(isa_and_nonnull<IntegerValue>(FooVal)); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| // There is no storage location for `Bar` because it isn't used in the |
| // body of the lambda. |
| const StorageLocation *BarLoc = Env.getStorageLocation(*BarDecl); |
| EXPECT_THAT(BarLoc, IsNull()); |
| }); |
| } |
| |
| TEST(TransferTest, LambdaCaptureByReferenceImplicit) { |
| std::string Code = R"( |
| void target(int Foo, int Bar) { |
| [&]() { |
| Foo; |
| // [[p]] |
| }(); |
| } |
| )"; |
| runDataflowOnLambda( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| 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); |
| EXPECT_TRUE(isa_and_nonnull<IntegerValue>(FooVal)); |
| |
| const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); |
| ASSERT_THAT(BarDecl, NotNull()); |
| |
| // There is no storage location for `Bar` because it isn't used in the |
| // body of the lambda. |
| const StorageLocation *BarLoc = Env.getStorageLocation(*BarDecl); |
| EXPECT_THAT(BarLoc, IsNull()); |
| }); |
| } |
| |
| TEST(TransferTest, LambdaCaptureThis) { |
| std::string Code = R"( |
| struct Bar { |
| int Foo; |
| |
| void target() { |
| [this]() { |
| Foo; |
| // [[p]] |
| }(); |
| } |
| }; |
| )"; |
| runDataflowOnLambda( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const RecordStorageLocation *ThisPointeeLoc = |
| Env.getThisPointeeStorageLocation(); |
| ASSERT_THAT(ThisPointeeLoc, NotNull()); |
| |
| const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo"); |
| ASSERT_THAT(FooDecl, NotNull()); |
| |
| const StorageLocation *FooLoc = ThisPointeeLoc->getChild(*FooDecl); |
| ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(FooLoc)); |
| |
| const Value *FooVal = Env.getValue(*FooLoc); |
| EXPECT_TRUE(isa_and_nonnull<IntegerValue>(FooVal)); |
| }); |
| } |
| |
| // This test verifies correct modeling of a relational dependency that goes |
| // through unmodeled functions (the simple `cond()` in this case). |
| TEST(TransferTest, ConditionalRelation) { |
| std::string Code = R"( |
| bool cond(); |
| void target() { |
| bool a = true; |
| bool b = true; |
| if (cond()) { |
| a = false; |
| if (cond()) { |
| b = false; |
| } |
| } |
| (void)0; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| auto &A = Env.arena(); |
| auto &VarA = getValueForDecl<BoolValue>(ASTCtx, Env, "a").formula(); |
| auto &VarB = getValueForDecl<BoolValue>(ASTCtx, Env, "b").formula(); |
| |
| EXPECT_FALSE(Env.allows(A.makeAnd(VarA, A.makeNot(VarB)))); |
| }); |
| } |
| |
| // This is a crash repro. |
| // We used to crash while transferring `S().i` because Clang contained a bug |
| // causing the AST to be malformed. |
| TEST(TransferTest, AnonymousUnionMemberExprInTemplate) { |
| using ast_matchers::functionDecl; |
| using ast_matchers::hasName; |
| using ast_matchers::unless; |
| |
| std::string Code = R"cc( |
| struct S { |
| struct { |
| int i; |
| }; |
| }; |
| |
| template <class> |
| void target() { |
| S().i; |
| } |
| |
| template void target<int>(); |
| )cc"; |
| auto Matcher = functionDecl(hasName("target"), unless(isTemplated())); |
| ASSERT_THAT_ERROR(checkDataflowWithNoopAnalysis(Code, Matcher), |
| llvm::Succeeded()); |
| } |
| |
| } // namespace |