blob: 845608504a3d0c6efc60e85f29d281ce7cc2df54 [file] [log] [blame]
//===- 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 <type_traits>
#include <variant>
namespace clang {
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
const std::vector<const CFGBlock *> &Nodes) {
OS << "Blocks{";
for (const auto *B : Nodes)
OS << B->getBlockID() << ", ";
OS << "}";
return OS;
}
void PrintTo(const std::vector<const CFGBlock *> &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 <typename... T> auto blockIDs(T... IDs) {
return UnorderedElementsAre(Property(&CFGBlock::getBlockID, IDs)...);
}
template <typename... T> 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<const CFGBlock *> 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<const CFGBlock *> 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<const CFGBlock *> I1 = buildInterval(EntryBlock);
EXPECT_THAT(I1, ElementsAre(EntryBlock, InitXBlock));
std::vector<const CFGBlock *> 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<WeakTopologicalOrdering> 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