llvm-project/clang/unittests/Analysis/IntervalPartitionTest.cpp
Yitzhak Mandelbaum e21b1dd9cc [clang][CFG] Fix 2 memory errors in interval computation.
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
2023-08-04 21:10:35 +00:00

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