//===- 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 #include #include 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 \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(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\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 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 struct Base { virtual ~Base() {} }; template struct Derived : public Base { virtual ~Derived() {} }; )"; CFG::BuildOptions Options; Options.AddImplicitDtors = true; Options.setAllAlwaysAdd(); EXPECT_EQ(BuildResult::BuiltCFG, BuildCFG(Code, Options, ast_matchers::hasName("~Derived")) .getStatus()); } TEST(CFG, SwitchCoveredEnumNoDefault) { const char *Code = R"( enum class E {E1, E2}; int f(E e) { switch(e) { case E::E1: return 1; case E::E2: return 2; } return 0; } )"; CFG::BuildOptions Options; Options.AssumeReachableDefaultInSwitchStatements = true; BuildResult B = BuildCFG(Code, Options); ASSERT_EQ(BuildResult::BuiltCFG, B.getStatus()); // [B5 (ENTRY)] // Succs (1): B2 // // [B1] // 1: 0 // 2: return [B1.1]; // Preds (1): B2 // Succs (1): B0 // // [B2] // 1: e (ImplicitCastExpr, LValueToRValue, E) // T: switch [B2.1] // Preds (1): B5 // Succs (3): B3 B4 B1 // // [B3] // case E::E2: // 1: 2 // 2: return [B3.1]; // Preds (1): B2 // Succs (1): B0 // // [B4] // case E::E1: // 1: 1 // 2: return [B4.1]; // Preds (1): B2 // Succs (1): B0 // // [B0 (EXIT)] // Preds (3): B1 B3 B4 auto *CFG = B.getCFG(); const auto &Entry = CFG->getEntry(); ASSERT_EQ(1u, Entry.succ_size()); // First successor of Entry is the switch CFGBlock *SwitchBlock = *Entry.succ_begin(); ASSERT_EQ(3u, SwitchBlock->succ_size()); // Last successor of the switch is after the switch auto NoCaseSucc = SwitchBlock->succ_rbegin(); EXPECT_TRUE(NoCaseSucc->isReachable()); // Checking that the same node is Unreachable without this setting Options.AssumeReachableDefaultInSwitchStatements = false; B = BuildCFG(Code, Options); ASSERT_EQ(BuildResult::BuiltCFG, B.getStatus()); const auto &Entry2 = B.getCFG()->getEntry(); ASSERT_EQ(1u, Entry2.succ_size()); CFGBlock *SwitchBlock2 = *Entry2.succ_begin(); ASSERT_EQ(3u, SwitchBlock2->succ_size()); auto NoCaseSucc2 = SwitchBlock2->succ_rbegin(); EXPECT_FALSE(NoCaseSucc2->isReachable()); } TEST(CFG, SwitchCoveredEnumWithDefault) { const char *Code = R"( enum class E {E1, E2}; int f(E e) { switch(e) { case E::E1: return 1; case E::E2: return 2; default: return 0; } return -1; } )"; CFG::BuildOptions Options; Options.AssumeReachableDefaultInSwitchStatements = true; BuildResult B = BuildCFG(Code, Options); ASSERT_EQ(BuildResult::BuiltCFG, B.getStatus()); // [B6 (ENTRY)] // Succs (1): B2 // // [B1] // 1: -1 // 2: return [B1.1]; // Succs (1): B0 // // [B2] // 1: e (ImplicitCastExpr, LValueToRValue, E) // T: switch [B2.1] // Preds (1): B6 // Succs (3): B4 B5 B3 // // [B3] // default: // 1: 0 // 2: return [B3.1]; // Preds (1): B2 // Succs (1): B0 // // [B4] // case E::E2: // 1: 2 // 2: return [B4.1]; // Preds (1): B2 // Succs (1): B0 // // [B5] // case E::E1: // 1: 1 // 2: return [B5.1]; // Preds (1): B2 // Succs (1): B0 // // [B0 (EXIT)] // Preds (4): B1 B3 B4 B5 const auto &Entry = B.getCFG()->getEntry(); ASSERT_EQ(1u, Entry.succ_size()); // First successor of Entry is the switch CFGBlock *SwitchBlock = *Entry.succ_begin(); ASSERT_EQ(3u, SwitchBlock->succ_size()); // Last successor of the switch is the default branch auto defaultBlock = SwitchBlock->succ_rbegin(); EXPECT_TRUE(defaultBlock->isReachable()); // Checking that the same node is Unreachable without this setting Options.AssumeReachableDefaultInSwitchStatements = false; B = BuildCFG(Code, Options); ASSERT_EQ(BuildResult::BuiltCFG, B.getStatus()); const auto &Entry2 = B.getCFG()->getEntry(); ASSERT_EQ(1u, Entry2.succ_size()); CFGBlock *SwitchBlock2 = *Entry2.succ_begin(); ASSERT_EQ(3u, SwitchBlock2->succ_size()); auto defaultBlock2 = SwitchBlock2->succ_rbegin(); EXPECT_FALSE(defaultBlock2->isReachable()); } 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()); EXPECT_TRUE((*ElementRef).getAs()); 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()); EXPECT_TRUE((*ElementRef).getAs()); 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()) { EXPECT_EQ(ElementRef.getParent(), MainBlock); EXPECT_EQ(ElementRef.getIndexInBlock(), Index); EXPECT_TRUE(ElementRef->getAs()); EXPECT_TRUE((*ElementRef).getAs()); 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()); EXPECT_TRUE((*ElementRef).getAs()); 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 ReferenceOrder; for (const auto *B : *AC.getAnalysis()) ReferenceOrder.push_back(B); { ForwardDataflowWorklist ForwardWorklist(*CFG, AC); for (const auto *B : *CFG) ForwardWorklist.enqueueBlock(B); std::vector 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())); } { using ::testing::ElementsAreArray; std::optional WTO = getIntervalWTO(*CFG); ASSERT_TRUE(WTO); WTOCompare WCmp(*WTO); WTODataflowWorklist WTOWorklist(*CFG, WCmp); for (const auto *B : *CFG) WTOWorklist.enqueueBlock(B); std::vector WTONodes; while (const CFGBlock *B = WTOWorklist.dequeue()) WTONodes.push_back(B); EXPECT_THAT(WTONodes, ElementsAreArray(*WTO)); } std::reverse(ReferenceOrder.begin(), ReferenceOrder.end()); { BackwardDataflowWorklist BackwardWorklist(*CFG, AC); for (const auto *B : *CFG) BackwardWorklist.enqueueBlock(B); std::vector 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