
This fixes 2 bugs and adds corresponding tests. Both related to unreachable blocks. One occured in the `WTOCompare` construction, which assumed the size of the order was the same as the number of blocks in the CFG, which isn't true when some blocks are unreachable. The other assumed predecessor pointers were non-null, which can be false for blocks with unreachable predecessors. Differential Revision: https://reviews.llvm.org/D157033
398 lines
13 KiB
C++
398 lines
13 KiB
C++
//===- 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
|