323 lines
12 KiB
C++
323 lines
12 KiB
C++
|
//===- unittests/Analysis/CFGTest.cpp - CFG tests -------------------------===//
|
||
|
//
|
||
|
// 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/CFG.h"
|
||
|
#include "CFGBuildResult.h"
|
||
|
#include "clang/AST/Decl.h"
|
||
|
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
||
|
#include "clang/Analysis/Analyses/IntervalPartition.h"
|
||
|
#include "clang/Analysis/AnalysisDeclContext.h"
|
||
|
#include "clang/Analysis/FlowSensitive/DataflowWorklist.h"
|
||
|
#include "clang/Tooling/Tooling.h"
|
||
|
#include "gmock/gmock.h"
|
||
|
#include "gtest/gtest.h"
|
||
|
#include <algorithm>
|
||
|
#include <string>
|
||
|
#include <vector>
|
||
|
|
||
|
namespace clang {
|
||
|
namespace analysis {
|
||
|
namespace {
|
||
|
|
||
|
// Constructing a CFG for a range-based for over a dependent type fails (but
|
||
|
// should not crash).
|
||
|
TEST(CFG, RangeBasedForOverDependentType) {
|
||
|
const char *Code = "class Foo;\n"
|
||
|
"template <typename T>\n"
|
||
|
"void f(const T &Range) {\n"
|
||
|
" for (const Foo *TheFoo : Range) {\n"
|
||
|
" }\n"
|
||
|
"}\n";
|
||
|
EXPECT_EQ(BuildResult::SawFunctionBody, BuildCFG(Code).getStatus());
|
||
|
}
|
||
|
|
||
|
TEST(CFG, StaticInitializerLastCondition) {
|
||
|
const char *Code = "void f() {\n"
|
||
|
" int i = 5 ;\n"
|
||
|
" static int j = 3 ;\n"
|
||
|
"}\n";
|
||
|
CFG::BuildOptions Options;
|
||
|
Options.AddStaticInitBranches = true;
|
||
|
Options.setAllAlwaysAdd();
|
||
|
BuildResult B = BuildCFG(Code, Options);
|
||
|
EXPECT_EQ(BuildResult::BuiltCFG, B.getStatus());
|
||
|
EXPECT_EQ(1u, B.getCFG()->getEntry().succ_size());
|
||
|
CFGBlock *Block = *B.getCFG()->getEntry().succ_begin();
|
||
|
EXPECT_TRUE(isa<DeclStmt>(Block->getTerminatorStmt()));
|
||
|
EXPECT_EQ(nullptr, Block->getLastCondition());
|
||
|
}
|
||
|
|
||
|
// Constructing a CFG containing a delete expression on a dependent type should
|
||
|
// not crash.
|
||
|
TEST(CFG, DeleteExpressionOnDependentType) {
|
||
|
const char *Code = "template<class T>\n"
|
||
|
"void f(T t) {\n"
|
||
|
" delete t;\n"
|
||
|
"}\n";
|
||
|
EXPECT_EQ(BuildResult::BuiltCFG, BuildCFG(Code).getStatus());
|
||
|
}
|
||
|
|
||
|
// Constructing a CFG on a function template with a variable of incomplete type
|
||
|
// should not crash.
|
||
|
TEST(CFG, VariableOfIncompleteType) {
|
||
|
const char *Code = "template<class T> void f() {\n"
|
||
|
" class Undefined;\n"
|
||
|
" Undefined u;\n"
|
||
|
"}\n";
|
||
|
EXPECT_EQ(BuildResult::BuiltCFG, BuildCFG(Code).getStatus());
|
||
|
}
|
||
|
|
||
|
// Constructing a CFG with a dependent base should not crash.
|
||
|
TEST(CFG, DependantBaseAddImplicitDtors) {
|
||
|
const char *Code = R"(
|
||
|
template <class T>
|
||
|
struct Base {
|
||
|
virtual ~Base() {}
|
||
|
};
|
||
|
|
||
|
template <typename T>
|
||
|
struct Derived : public Base<T> {
|
||
|
virtual ~Derived() {}
|
||
|
};
|
||
|
)";
|
||
|
CFG::BuildOptions Options;
|
||
|
Options.AddImplicitDtors = true;
|
||
|
Options.setAllAlwaysAdd();
|
||
|
EXPECT_EQ(BuildResult::BuiltCFG,
|
||
|
BuildCFG(Code, Options, ast_matchers::hasName("~Derived<T>"))
|
||
|
.getStatus());
|
||
|
}
|
||
|
|
||
|
TEST(CFG, IsLinear) {
|
||
|
auto expectLinear = [](bool IsLinear, const char *Code) {
|
||
|
BuildResult B = BuildCFG(Code);
|
||
|
EXPECT_EQ(BuildResult::BuiltCFG, B.getStatus());
|
||
|
EXPECT_EQ(IsLinear, B.getCFG()->isLinear());
|
||
|
};
|
||
|
|
||
|
expectLinear(true, "void foo() {}");
|
||
|
expectLinear(true, "void foo() { if (true) return; }");
|
||
|
expectLinear(true, "void foo() { if constexpr (false); }");
|
||
|
expectLinear(false, "void foo(bool coin) { if (coin) return; }");
|
||
|
expectLinear(false, "void foo() { for(;;); }");
|
||
|
expectLinear(false, "void foo() { do {} while (true); }");
|
||
|
expectLinear(true, "void foo() { do {} while (false); }");
|
||
|
expectLinear(true, "void foo() { foo(); }"); // Recursion is not our problem.
|
||
|
}
|
||
|
|
||
|
TEST(CFG, ElementRefIterator) {
|
||
|
const char *Code = R"(void f() {
|
||
|
int i;
|
||
|
int j;
|
||
|
i = 5;
|
||
|
i = 6;
|
||
|
j = 7;
|
||
|
})";
|
||
|
|
||
|
BuildResult B = BuildCFG(Code);
|
||
|
EXPECT_EQ(BuildResult::BuiltCFG, B.getStatus());
|
||
|
CFG *Cfg = B.getCFG();
|
||
|
|
||
|
// [B2 (ENTRY)]
|
||
|
// Succs (1): B1
|
||
|
|
||
|
// [B1]
|
||
|
// 1: int i;
|
||
|
// 2: int j;
|
||
|
// 3: i = 5
|
||
|
// 4: i = 6
|
||
|
// 5: j = 7
|
||
|
// Preds (1): B2
|
||
|
// Succs (1): B0
|
||
|
|
||
|
// [B0 (EXIT)]
|
||
|
// Preds (1): B1
|
||
|
CFGBlock *MainBlock = *(Cfg->begin() + 1);
|
||
|
|
||
|
constexpr CFGBlock::ref_iterator::difference_type MainBlockSize = 4;
|
||
|
|
||
|
// The rest of this test looks totally insane, but there iterators are
|
||
|
// templates under the hood, to it's important to instantiate everything for
|
||
|
// proper converage.
|
||
|
|
||
|
// Non-reverse, non-const version
|
||
|
size_t Index = 0;
|
||
|
for (CFGBlock::CFGElementRef ElementRef : MainBlock->refs()) {
|
||
|
EXPECT_EQ(ElementRef.getParent(), MainBlock);
|
||
|
EXPECT_EQ(ElementRef.getIndexInBlock(), Index);
|
||
|
EXPECT_TRUE(ElementRef->getAs<CFGStmt>());
|
||
|
EXPECT_TRUE((*ElementRef).getAs<CFGStmt>());
|
||
|
EXPECT_EQ(ElementRef.getParent(), MainBlock);
|
||
|
++Index;
|
||
|
}
|
||
|
EXPECT_TRUE(*MainBlock->ref_begin() < *(MainBlock->ref_begin() + 1));
|
||
|
EXPECT_TRUE(*MainBlock->ref_begin() == *MainBlock->ref_begin());
|
||
|
EXPECT_FALSE(*MainBlock->ref_begin() != *MainBlock->ref_begin());
|
||
|
|
||
|
EXPECT_TRUE(MainBlock->ref_begin() < MainBlock->ref_begin() + 1);
|
||
|
EXPECT_TRUE(MainBlock->ref_begin() == MainBlock->ref_begin());
|
||
|
EXPECT_FALSE(MainBlock->ref_begin() != MainBlock->ref_begin());
|
||
|
EXPECT_EQ(MainBlock->ref_end() - MainBlock->ref_begin(), MainBlockSize + 1);
|
||
|
EXPECT_EQ(MainBlock->ref_end() - MainBlockSize - 1, MainBlock->ref_begin());
|
||
|
EXPECT_EQ(MainBlock->ref_begin() + MainBlockSize + 1, MainBlock->ref_end());
|
||
|
EXPECT_EQ(MainBlock->ref_begin()++, MainBlock->ref_begin());
|
||
|
EXPECT_EQ(++(MainBlock->ref_begin()), MainBlock->ref_begin() + 1);
|
||
|
|
||
|
// Non-reverse, non-const version
|
||
|
const CFGBlock *CMainBlock = MainBlock;
|
||
|
Index = 0;
|
||
|
for (CFGBlock::ConstCFGElementRef ElementRef : CMainBlock->refs()) {
|
||
|
EXPECT_EQ(ElementRef.getParent(), CMainBlock);
|
||
|
EXPECT_EQ(ElementRef.getIndexInBlock(), Index);
|
||
|
EXPECT_TRUE(ElementRef->getAs<CFGStmt>());
|
||
|
EXPECT_TRUE((*ElementRef).getAs<CFGStmt>());
|
||
|
EXPECT_EQ(ElementRef.getParent(), MainBlock);
|
||
|
++Index;
|
||
|
}
|
||
|
EXPECT_TRUE(*CMainBlock->ref_begin() < *(CMainBlock->ref_begin() + 1));
|
||
|
EXPECT_TRUE(*CMainBlock->ref_begin() == *CMainBlock->ref_begin());
|
||
|
EXPECT_FALSE(*CMainBlock->ref_begin() != *CMainBlock->ref_begin());
|
||
|
|
||
|
EXPECT_TRUE(CMainBlock->ref_begin() < CMainBlock->ref_begin() + 1);
|
||
|
EXPECT_TRUE(CMainBlock->ref_begin() == CMainBlock->ref_begin());
|
||
|
EXPECT_FALSE(CMainBlock->ref_begin() != CMainBlock->ref_begin());
|
||
|
EXPECT_EQ(CMainBlock->ref_end() - CMainBlock->ref_begin(), MainBlockSize + 1);
|
||
|
EXPECT_EQ(CMainBlock->ref_end() - MainBlockSize - 1, CMainBlock->ref_begin());
|
||
|
EXPECT_EQ(CMainBlock->ref_begin() + MainBlockSize + 1, CMainBlock->ref_end());
|
||
|
EXPECT_EQ(CMainBlock->ref_begin()++, CMainBlock->ref_begin());
|
||
|
EXPECT_EQ(++(CMainBlock->ref_begin()), CMainBlock->ref_begin() + 1);
|
||
|
|
||
|
// Reverse, non-const version
|
||
|
Index = MainBlockSize;
|
||
|
for (CFGBlock::CFGElementRef ElementRef : MainBlock->rrefs()) {
|
||
|
llvm::errs() << Index << '\n';
|
||
|
EXPECT_EQ(ElementRef.getParent(), MainBlock);
|
||
|
EXPECT_EQ(ElementRef.getIndexInBlock(), Index);
|
||
|
EXPECT_TRUE(ElementRef->getAs<CFGStmt>());
|
||
|
EXPECT_TRUE((*ElementRef).getAs<CFGStmt>());
|
||
|
EXPECT_EQ(ElementRef.getParent(), MainBlock);
|
||
|
--Index;
|
||
|
}
|
||
|
EXPECT_FALSE(*MainBlock->rref_begin() < *(MainBlock->rref_begin() + 1));
|
||
|
EXPECT_TRUE(*MainBlock->rref_begin() == *MainBlock->rref_begin());
|
||
|
EXPECT_FALSE(*MainBlock->rref_begin() != *MainBlock->rref_begin());
|
||
|
|
||
|
EXPECT_TRUE(MainBlock->rref_begin() < MainBlock->rref_begin() + 1);
|
||
|
EXPECT_TRUE(MainBlock->rref_begin() == MainBlock->rref_begin());
|
||
|
EXPECT_FALSE(MainBlock->rref_begin() != MainBlock->rref_begin());
|
||
|
EXPECT_EQ(MainBlock->rref_end() - MainBlock->rref_begin(), MainBlockSize + 1);
|
||
|
EXPECT_EQ(MainBlock->rref_end() - MainBlockSize - 1, MainBlock->rref_begin());
|
||
|
EXPECT_EQ(MainBlock->rref_begin() + MainBlockSize + 1, MainBlock->rref_end());
|
||
|
EXPECT_EQ(MainBlock->rref_begin()++, MainBlock->rref_begin());
|
||
|
EXPECT_EQ(++(MainBlock->rref_begin()), MainBlock->rref_begin() + 1);
|
||
|
|
||
|
// Reverse, const version
|
||
|
Index = MainBlockSize;
|
||
|
for (CFGBlock::ConstCFGElementRef ElementRef : CMainBlock->rrefs()) {
|
||
|
EXPECT_EQ(ElementRef.getParent(), CMainBlock);
|
||
|
EXPECT_EQ(ElementRef.getIndexInBlock(), Index);
|
||
|
EXPECT_TRUE(ElementRef->getAs<CFGStmt>());
|
||
|
EXPECT_TRUE((*ElementRef).getAs<CFGStmt>());
|
||
|
EXPECT_EQ(ElementRef.getParent(), MainBlock);
|
||
|
--Index;
|
||
|
}
|
||
|
EXPECT_FALSE(*CMainBlock->rref_begin() < *(CMainBlock->rref_begin() + 1));
|
||
|
EXPECT_TRUE(*CMainBlock->rref_begin() == *CMainBlock->rref_begin());
|
||
|
EXPECT_FALSE(*CMainBlock->rref_begin() != *CMainBlock->rref_begin());
|
||
|
|
||
|
EXPECT_TRUE(CMainBlock->rref_begin() < CMainBlock->rref_begin() + 1);
|
||
|
EXPECT_TRUE(CMainBlock->rref_begin() == CMainBlock->rref_begin());
|
||
|
EXPECT_FALSE(CMainBlock->rref_begin() != CMainBlock->rref_begin());
|
||
|
EXPECT_EQ(CMainBlock->rref_end() - CMainBlock->rref_begin(),
|
||
|
MainBlockSize + 1);
|
||
|
EXPECT_EQ(CMainBlock->rref_end() - MainBlockSize - 1,
|
||
|
CMainBlock->rref_begin());
|
||
|
EXPECT_EQ(CMainBlock->rref_begin() + MainBlockSize + 1,
|
||
|
CMainBlock->rref_end());
|
||
|
EXPECT_EQ(CMainBlock->rref_begin()++, CMainBlock->rref_begin());
|
||
|
EXPECT_EQ(++(CMainBlock->rref_begin()), CMainBlock->rref_begin() + 1);
|
||
|
}
|
||
|
|
||
|
TEST(CFG, Worklists) {
|
||
|
const char *Code = "int f(bool cond) {\n"
|
||
|
" int a = 5;\n"
|
||
|
" while (a < 6)\n"
|
||
|
" ++a;\n"
|
||
|
" if (cond)\n"
|
||
|
" a += 1;\n"
|
||
|
" return a;\n"
|
||
|
"}\n";
|
||
|
BuildResult B = BuildCFG(Code);
|
||
|
EXPECT_EQ(BuildResult::BuiltCFG, B.getStatus());
|
||
|
const FunctionDecl *Func = B.getFunc();
|
||
|
AnalysisDeclContext AC(nullptr, Func);
|
||
|
auto *CFG = AC.getCFG();
|
||
|
|
||
|
std::vector<const CFGBlock *> ReferenceOrder;
|
||
|
for (const auto *B : *AC.getAnalysis<PostOrderCFGView>())
|
||
|
ReferenceOrder.push_back(B);
|
||
|
|
||
|
{
|
||
|
ForwardDataflowWorklist ForwardWorklist(*CFG, AC);
|
||
|
for (const auto *B : *CFG)
|
||
|
ForwardWorklist.enqueueBlock(B);
|
||
|
|
||
|
std::vector<const CFGBlock *> ForwardNodes;
|
||
|
while (const CFGBlock *B = ForwardWorklist.dequeue())
|
||
|
ForwardNodes.push_back(B);
|
||
|
|
||
|
EXPECT_EQ(ForwardNodes.size(), ReferenceOrder.size());
|
||
|
EXPECT_TRUE(std::equal(ReferenceOrder.begin(), ReferenceOrder.end(),
|
||
|
ForwardNodes.begin()));
|
||
|
}
|
||
|
|
||
|
// RPO: 876321054
|
||
|
// WTO: 876534210
|
||
|
// So, we construct the WTO order accordingly from the reference order.
|
||
|
std::vector<const CFGBlock *> WTOOrder = {
|
||
|
ReferenceOrder[0], ReferenceOrder[1], ReferenceOrder[2],
|
||
|
ReferenceOrder[7], ReferenceOrder[3], ReferenceOrder[8],
|
||
|
ReferenceOrder[4], ReferenceOrder[5], ReferenceOrder[6]};
|
||
|
|
||
|
{
|
||
|
using ::testing::ElementsAreArray;
|
||
|
std::optional<WeakTopologicalOrdering> WTO = getIntervalWTO(*CFG);
|
||
|
ASSERT_TRUE(WTO);
|
||
|
WTOCompare WCmp(*WTO);
|
||
|
WTODataflowWorklist WTOWorklist(*CFG, WCmp);
|
||
|
for (const auto *B : *CFG)
|
||
|
WTOWorklist.enqueueBlock(B);
|
||
|
|
||
|
std::vector<const CFGBlock *> WTONodes;
|
||
|
while (const CFGBlock *B = WTOWorklist.dequeue())
|
||
|
WTONodes.push_back(B);
|
||
|
|
||
|
EXPECT_THAT(WTONodes, ElementsAreArray(WTOOrder));
|
||
|
}
|
||
|
|
||
|
std::reverse(ReferenceOrder.begin(), ReferenceOrder.end());
|
||
|
|
||
|
{
|
||
|
BackwardDataflowWorklist BackwardWorklist(*CFG, AC);
|
||
|
for (const auto *B : *CFG)
|
||
|
BackwardWorklist.enqueueBlock(B);
|
||
|
|
||
|
std::vector<const CFGBlock *> BackwardNodes;
|
||
|
while (const CFGBlock *B = BackwardWorklist.dequeue())
|
||
|
BackwardNodes.push_back(B);
|
||
|
|
||
|
EXPECT_EQ(BackwardNodes.size(), ReferenceOrder.size());
|
||
|
EXPECT_TRUE(std::equal(ReferenceOrder.begin(), ReferenceOrder.end(),
|
||
|
BackwardNodes.begin()));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} // namespace
|
||
|
} // namespace analysis
|
||
|
} // namespace clang
|