//===- unittests/Analysis/IntervalPartitionTest.cpp -----------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "clang/Analysis/Analyses/IntervalPartition.h" #include "CFGBuildResult.h" #include "clang/Analysis/CFG.h" #include "llvm/Support/raw_ostream.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include #include namespace clang { llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const std::vector &Nodes) { OS << "Blocks{"; for (const auto *B : Nodes) OS << B->getBlockID() << ", "; OS << "}"; return OS; } void PrintTo(const std::vector &Nodes, std::ostream *OS) { std::string Result; llvm::raw_string_ostream StringOS(Result); StringOS << Nodes; *OS << Result; } namespace internal { llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const CFGIntervalNode &I) { OS << "Interval{ID = " << I.ID << ", "; OS << "Blocks{"; for (const auto *B : I.Nodes) OS << B->getBlockID() << ", "; OS << "}, Pre{"; for (const auto *P : I.Predecessors) OS << P->ID << ","; OS << "}, Succ{"; for (const auto *P : I.Successors) OS << P->ID << ","; OS << "}}"; return OS; } llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const CFGIntervalGraph &G) { OS << "Intervals{"; for (const auto &I : G) { OS << I << ", "; } OS << "}"; return OS; } void PrintTo(const CFGIntervalNode &I, std::ostream *OS) { std::string Result; llvm::raw_string_ostream StringOS(Result); StringOS << I; *OS << Result; } void PrintTo(const CFGIntervalGraph &G, std::ostream *OS) { *OS << "Intervals{"; for (const auto &I : G) { PrintTo(I, OS); *OS << ", "; } *OS << "}"; } } // namespace internal namespace { using ::clang::analysis::BuildCFG; using ::clang::analysis::BuildResult; using ::clang::internal::buildInterval; using ::clang::internal::partitionIntoIntervals; using ::testing::ElementsAre; using ::testing::IsEmpty; using ::testing::Optional; using ::testing::Property; using ::testing::UnorderedElementsAre; MATCHER_P(intervalID, ID, "") { return arg->ID == ID; } template auto blockIDs(T... IDs) { return UnorderedElementsAre(Property(&CFGBlock::getBlockID, IDs)...); } template auto blockOrder(T... IDs) { return ElementsAre(Property(&CFGBlock::getBlockID, IDs)...); } MATCHER_P3(isInterval, ID, Preds, Succs, "") { return testing::Matches(ID)(arg.ID) && testing::Matches(Preds)(arg.Predecessors) && testing::Matches(Succs)(arg.Successors); } MATCHER_P4(isInterval, ID, Nodes, Preds, Succs, "") { return testing::Matches(ID)(arg.ID) && testing::Matches(Nodes)(arg.Nodes) && testing::Matches(Preds)(arg.Predecessors) && testing::Matches(Succs)(arg.Successors); } TEST(BuildInterval, PartitionSimpleOneInterval) { const char *Code = R"(void f() { int x = 3; int y = 7; x = y + x; })"; BuildResult Result = BuildCFG(Code); EXPECT_EQ(BuildResult::BuiltCFG, Result.getStatus()); CFG *cfg = Result.getCFG(); // Basic correctness checks. ASSERT_EQ(cfg->size(), 3u); auto &EntryBlock = cfg->getEntry(); std::vector I = buildInterval(&EntryBlock); EXPECT_EQ(I.size(), 3u); } TEST(BuildInterval, PartitionIfThenOneInterval) { const char *Code = R"(void f() { int x = 3; if (x > 3) x = 2; else x = 7; x = x + x; })"; BuildResult Result = BuildCFG(Code); EXPECT_EQ(BuildResult::BuiltCFG, Result.getStatus()); CFG *cfg = Result.getCFG(); // Basic correctness checks. ASSERT_EQ(cfg->size(), 6u); auto &EntryBlock = cfg->getEntry(); std::vector I = buildInterval(&EntryBlock); EXPECT_EQ(I.size(), 6u); } TEST(BuildInterval, PartitionWhileMultipleIntervals) { const char *Code = R"(void f() { int x = 3; while (x >= 3) --x; x = x + x; })"; BuildResult Result = BuildCFG(Code); ASSERT_EQ(BuildResult::BuiltCFG, Result.getStatus()); CFG *cfg = Result.getCFG(); ASSERT_EQ(cfg->size(), 7u); auto *EntryBlock = &cfg->getEntry(); CFGBlock *InitXBlock = *EntryBlock->succ_begin(); CFGBlock *LoopHeadBlock = *InitXBlock->succ_begin(); std::vector I1 = buildInterval(EntryBlock); EXPECT_THAT(I1, ElementsAre(EntryBlock, InitXBlock)); std::vector I2 = buildInterval(LoopHeadBlock); EXPECT_EQ(I2.size(), 5u); } TEST(PartitionIntoIntervals, PartitionIfThenOneInterval) { const char *Code = R"(void f() { int x = 3; if (x > 3) x = 2; else x = 7; x = x + x; })"; BuildResult Result = BuildCFG(Code); ASSERT_EQ(BuildResult::BuiltCFG, Result.getStatus()); auto Graph = partitionIntoIntervals(*Result.getCFG()); EXPECT_EQ(Graph.size(), 1u); EXPECT_THAT(Graph, ElementsAre(isInterval(0, IsEmpty(), IsEmpty()))); } TEST(PartitionIntoIntervals, PartitionWhileTwoIntervals) { const char *Code = R"(void f() { int x = 3; while (x >= 3) --x; x = x + x; })"; BuildResult Result = BuildCFG(Code); ASSERT_EQ(BuildResult::BuiltCFG, Result.getStatus()); auto Graph = partitionIntoIntervals(*Result.getCFG()); EXPECT_THAT( Graph, ElementsAre( isInterval(0, IsEmpty(), UnorderedElementsAre(intervalID(1u))), isInterval(1, UnorderedElementsAre(intervalID(0u)), IsEmpty()))); } TEST(PartitionIntoIntervals, PartitionNestedWhileThreeIntervals) { const char *Code = R"(void f() { int x = 3; while (x >= 3) { --x; int y = x; while (y > 0) --y; } x = x + x; })"; BuildResult Result = BuildCFG(Code); ASSERT_EQ(BuildResult::BuiltCFG, Result.getStatus()); auto Graph = partitionIntoIntervals(*Result.getCFG()); EXPECT_THAT( Graph, ElementsAre( isInterval(0, IsEmpty(), UnorderedElementsAre(intervalID(1u))), isInterval(1, UnorderedElementsAre(intervalID(0u), intervalID(2u)), UnorderedElementsAre(intervalID(2u))), isInterval(2, UnorderedElementsAre(intervalID(1u)), UnorderedElementsAre(intervalID(1u))))); } TEST(PartitionIntoIntervals, PartitionSequentialWhileThreeIntervals) { const char *Code = R"(void f() { int x = 3; while (x >= 3) { --x; } x = x + x; int y = x; while (y > 0) --y; })"; BuildResult Result = BuildCFG(Code); ASSERT_EQ(BuildResult::BuiltCFG, Result.getStatus()); auto Graph = partitionIntoIntervals(*Result.getCFG()); EXPECT_THAT( Graph, ElementsAre( isInterval(0, IsEmpty(), UnorderedElementsAre(intervalID(1u))), isInterval(1, UnorderedElementsAre(intervalID(0u)), UnorderedElementsAre(intervalID(2u))), isInterval(2, UnorderedElementsAre(intervalID(1u)), IsEmpty()))); } TEST(PartitionIntoIntervals, LimitReducibleSequentialWhile) { const char *Code = R"(void f() { int x = 3; while (x >= 3) { --x; } x = x + x; int y = x; while (y > 0) --y; })"; BuildResult Result = BuildCFG(Code); ASSERT_EQ(BuildResult::BuiltCFG, Result.getStatus()); auto Graph = partitionIntoIntervals(*Result.getCFG()); ASSERT_THAT( Graph, ElementsAre(isInterval(0, blockOrder(9, 8), IsEmpty(), UnorderedElementsAre(intervalID(1u))), isInterval(1, blockOrder(7, 6, 4, 5), UnorderedElementsAre(intervalID(0u)), UnorderedElementsAre(intervalID(2u))), isInterval(2, blockOrder(3, 2, 0, 1), UnorderedElementsAre(intervalID(1u)), IsEmpty()))); auto Graph2 = partitionIntoIntervals(Graph); EXPECT_THAT(Graph2, ElementsAre(isInterval( 0, blockOrder(9, 8, 7, 6, 4, 5, 3, 2, 0, 1), IsEmpty(), IsEmpty()))); } TEST(PartitionIntoIntervals, LimitReducibleNestedWhile) { const char *Code = R"(void f() { int x = 3; while (x >= 3) { --x; int y = x; while (y > 0) --y; } x = x + x; })"; BuildResult Result = BuildCFG(Code); ASSERT_EQ(BuildResult::BuiltCFG, Result.getStatus()); auto Graph = partitionIntoIntervals(*Result.getCFG()); ASSERT_THAT(Graph, ElementsAre(isInterval(0, blockOrder(9, 8), IsEmpty(), UnorderedElementsAre(intervalID(1u))), isInterval(1, blockOrder(7, 6, 1, 0), UnorderedElementsAre(intervalID(0u), intervalID(2u)), UnorderedElementsAre(intervalID(2u))), isInterval(2, blockOrder(5, 4, 2, 3), UnorderedElementsAre(intervalID(1u)), UnorderedElementsAre(intervalID(1u))))); auto Graph2 = partitionIntoIntervals(Graph); EXPECT_THAT( Graph2, ElementsAre(isInterval(0, blockOrder(9, 8), IsEmpty(), UnorderedElementsAre(intervalID(1u))), isInterval(1, blockOrder(7, 6, 1, 0, 5, 4, 2, 3), UnorderedElementsAre(intervalID(0u)), IsEmpty()))); auto Graph3 = partitionIntoIntervals(Graph2); EXPECT_THAT(Graph3, ElementsAre(isInterval( 0, blockOrder(9, 8, 7, 6, 1, 0, 5, 4, 2, 3), IsEmpty(), IsEmpty()))); } TEST(GetIntervalWTO, SequentialWhile) { const char *Code = R"(void f() { int x = 3; while (x >= 3) { --x; } x = x + x; int y = x; while (y > 0) --y; })"; BuildResult Result = BuildCFG(Code); ASSERT_EQ(BuildResult::BuiltCFG, Result.getStatus()); EXPECT_THAT(getIntervalWTO(*Result.getCFG()), Optional(blockOrder(9, 8, 7, 6, 4, 5, 3, 2, 0, 1))); } TEST(GetIntervalWTO, NestedWhile) { const char *Code = R"(void f() { int x = 3; while (x >= 3) { --x; int y = x; while (y > 0) --y; } x = x + x; })"; BuildResult Result = BuildCFG(Code); ASSERT_EQ(BuildResult::BuiltCFG, Result.getStatus()); EXPECT_THAT(getIntervalWTO(*Result.getCFG()), Optional(blockOrder(9, 8, 7, 6, 1, 0, 5, 4, 2, 3))); } TEST(GetIntervalWTO, UnreachablePred) { const char *Code = R"( void target(bool Foo) { bool Bar = false; if (Foo) Bar = Foo; else __builtin_unreachable(); (void)0; })"; BuildResult Result = BuildCFG(Code); ASSERT_EQ(BuildResult::BuiltCFG, Result.getStatus()); EXPECT_THAT(getIntervalWTO(*Result.getCFG()), Optional(blockOrder(5, 4, 3, 2, 1, 0))); } TEST(WTOCompare, UnreachableBlock) { const char *Code = R"( void target() { while (true) {} (void)0; /*[[p]]*/ })"; BuildResult Result = BuildCFG(Code); ASSERT_EQ(BuildResult::BuiltCFG, Result.getStatus()); std::optional WTO = getIntervalWTO(*Result.getCFG()); ASSERT_THAT(WTO, Optional(blockOrder(4, 3, 2))); auto Cmp = WTOCompare(*WTO); const CFGBlock &Entry = Result.getCFG()->getEntry(); const CFGBlock &Exit = Result.getCFG()->getExit(); EXPECT_TRUE(Cmp(&Entry, &Exit)); } } // namespace } // namespace clang