//===- 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 "llvm/Analysis/CFG.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/Analysis/LoopInfo.h" #include "llvm/AsmParser/Parser.h" #include "llvm/IR/Dominators.h" #include "llvm/IR/Function.h" #include "llvm/IR/InstIterator.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/LegacyPassManager.h" #include "llvm/IR/Module.h" #include "llvm/InitializePasses.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/SourceMgr.h" #include "gtest/gtest.h" using namespace llvm; namespace { // This fixture assists in running the isPotentiallyReachable utility four ways // and ensuring it produces the correct answer each time. class IsPotentiallyReachableTest : public testing::Test { protected: void ParseAssembly(const char *Assembly) { SMDiagnostic Error; M = parseAssemblyString(Assembly, Error, Context); std::string errMsg; raw_string_ostream os(errMsg); Error.print("", os); // A failure here means that the test itself is buggy. if (!M) report_fatal_error(os.str().c_str()); Function *F = M->getFunction("test"); if (F == nullptr) report_fatal_error("Test must have a function named @test"); A = B = nullptr; for (inst_iterator I = inst_begin(F), E = inst_end(F); I != E; ++I) { if (I->hasName()) { if (I->getName() == "A") A = &*I; else if (I->getName() == "B") B = &*I; } } if (A == nullptr) report_fatal_error("@test must have an instruction %A"); if (B == nullptr) report_fatal_error("@test must have an instruction %B"); assert(ExclusionSet.empty()); for (auto I = F->begin(), E = F->end(); I != E; ++I) { if (I->hasName() && I->getName().startswith("excluded")) ExclusionSet.insert(&*I); } } void ExpectPath(bool ExpectedResult) { static char ID; class IsPotentiallyReachableTestPass : public FunctionPass { public: IsPotentiallyReachableTestPass(bool ExpectedResult, Instruction *A, Instruction *B, SmallPtrSet ExclusionSet) : FunctionPass(ID), ExpectedResult(ExpectedResult), A(A), B(B), ExclusionSet(ExclusionSet) {} static int initialize() { PassInfo *PI = new PassInfo("isPotentiallyReachable testing pass", "", &ID, nullptr, true, true); PassRegistry::getPassRegistry()->registerPass(*PI, false); initializeLoopInfoWrapperPassPass(*PassRegistry::getPassRegistry()); initializeDominatorTreeWrapperPassPass( *PassRegistry::getPassRegistry()); return 0; } void getAnalysisUsage(AnalysisUsage &AU) const override { AU.setPreservesAll(); AU.addRequired(); AU.addRequired(); } bool runOnFunction(Function &F) override { if (!F.hasName() || F.getName() != "test") return false; LoopInfo *LI = &getAnalysis().getLoopInfo(); DominatorTree *DT = &getAnalysis().getDomTree(); EXPECT_EQ(isPotentiallyReachable(A, B, &ExclusionSet, nullptr, nullptr), ExpectedResult); EXPECT_EQ(isPotentiallyReachable(A, B, &ExclusionSet, DT, nullptr), ExpectedResult); EXPECT_EQ(isPotentiallyReachable(A, B, &ExclusionSet, nullptr, LI), ExpectedResult); EXPECT_EQ(isPotentiallyReachable(A, B, &ExclusionSet, DT, LI), ExpectedResult); return false; } bool ExpectedResult; Instruction *A, *B; SmallPtrSet ExclusionSet; }; static int initialize = IsPotentiallyReachableTestPass::initialize(); (void)initialize; IsPotentiallyReachableTestPass *P = new IsPotentiallyReachableTestPass(ExpectedResult, A, B, ExclusionSet); legacy::PassManager PM; PM.add(P); PM.run(*M); } LLVMContext Context; std::unique_ptr M; Instruction *A, *B; SmallPtrSet ExclusionSet; }; } TEST_F(IsPotentiallyReachableTest, SameBlockNoPath) { ParseAssembly( "define void @test() {\n" "entry:\n" " bitcast i8 undef to i8\n" " %B = bitcast i8 undef to i8\n" " bitcast i8 undef to i8\n" " bitcast i8 undef to i8\n" " %A = bitcast i8 undef to i8\n" " ret void\n" "}\n"); ExpectPath(false); } TEST_F(IsPotentiallyReachableTest, SameBlockPath) { ParseAssembly( "define void @test() {\n" "entry:\n" " %A = bitcast i8 undef to i8\n" " bitcast i8 undef to i8\n" " bitcast i8 undef to i8\n" " %B = bitcast i8 undef to i8\n" " ret void\n" "}\n"); ExpectPath(true); } TEST_F(IsPotentiallyReachableTest, SameBlockNoLoop) { ParseAssembly( "define void @test() {\n" "entry:\n" " br label %middle\n" "middle:\n" " %B = bitcast i8 undef to i8\n" " bitcast i8 undef to i8\n" " bitcast i8 undef to i8\n" " %A = bitcast i8 undef to i8\n" " br label %nextblock\n" "nextblock:\n" " ret void\n" "}\n"); ExpectPath(false); } TEST_F(IsPotentiallyReachableTest, StraightNoPath) { ParseAssembly( "define void @test() {\n" "entry:\n" " %B = bitcast i8 undef to i8\n" " br label %exit\n" "exit:\n" " %A = bitcast i8 undef to i8\n" " ret void\n" "}"); ExpectPath(false); } TEST_F(IsPotentiallyReachableTest, StraightPath) { ParseAssembly( "define void @test() {\n" "entry:\n" " %A = bitcast i8 undef to i8\n" " br label %exit\n" "exit:\n" " %B = bitcast i8 undef to i8\n" " ret void\n" "}"); ExpectPath(true); } TEST_F(IsPotentiallyReachableTest, DestUnreachable) { ParseAssembly( "define void @test() {\n" "entry:\n" " br label %midblock\n" "midblock:\n" " %A = bitcast i8 undef to i8\n" " ret void\n" "unreachable:\n" " %B = bitcast i8 undef to i8\n" " br label %midblock\n" "}"); ExpectPath(false); } TEST_F(IsPotentiallyReachableTest, BranchToReturn) { ParseAssembly( "define void @test(i1 %x) {\n" "entry:\n" " %A = bitcast i8 undef to i8\n" " br i1 %x, label %block1, label %block2\n" "block1:\n" " ret void\n" "block2:\n" " %B = bitcast i8 undef to i8\n" " ret void\n" "}"); ExpectPath(true); } TEST_F(IsPotentiallyReachableTest, SimpleLoop1) { ParseAssembly( "declare i1 @switch()\n" "\n" "define void @test() {\n" "entry:\n" " br label %loop\n" "loop:\n" " %B = bitcast i8 undef to i8\n" " %A = bitcast i8 undef to i8\n" " %x = call i1 @switch()\n" " br i1 %x, label %loop, label %exit\n" "exit:\n" " ret void\n" "}"); ExpectPath(true); } TEST_F(IsPotentiallyReachableTest, SimpleLoop2) { ParseAssembly( "declare i1 @switch()\n" "\n" "define void @test() {\n" "entry:\n" " %B = bitcast i8 undef to i8\n" " br label %loop\n" "loop:\n" " %A = bitcast i8 undef to i8\n" " %x = call i1 @switch()\n" " br i1 %x, label %loop, label %exit\n" "exit:\n" " ret void\n" "}"); ExpectPath(false); } TEST_F(IsPotentiallyReachableTest, SimpleLoop3) { ParseAssembly( "declare i1 @switch()\n" "\n" "define void @test() {\n" "entry:\n" " br label %loop\n" "loop:\n" " %B = bitcast i8 undef to i8\n" " %x = call i1 @switch()\n" " br i1 %x, label %loop, label %exit\n" "exit:\n" " %A = bitcast i8 undef to i8\n" " ret void\n" "}"); ExpectPath(false); } TEST_F(IsPotentiallyReachableTest, OneLoopAfterTheOther1) { ParseAssembly( "declare i1 @switch()\n" "\n" "define void @test() {\n" "entry:\n" " br label %loop1\n" "loop1:\n" " %A = bitcast i8 undef to i8\n" " %x = call i1 @switch()\n" " br i1 %x, label %loop1, label %loop1exit\n" "loop1exit:\n" " br label %loop2\n" "loop2:\n" " %B = bitcast i8 undef to i8\n" " %y = call i1 @switch()\n" " br i1 %x, label %loop2, label %loop2exit\n" "loop2exit:" " ret void\n" "}"); ExpectPath(true); } TEST_F(IsPotentiallyReachableTest, OneLoopAfterTheOther2) { ParseAssembly( "declare i1 @switch()\n" "\n" "define void @test() {\n" "entry:\n" " br label %loop1\n" "loop1:\n" " %B = bitcast i8 undef to i8\n" " %x = call i1 @switch()\n" " br i1 %x, label %loop1, label %loop1exit\n" "loop1exit:\n" " br label %loop2\n" "loop2:\n" " %A = bitcast i8 undef to i8\n" " %y = call i1 @switch()\n" " br i1 %x, label %loop2, label %loop2exit\n" "loop2exit:" " ret void\n" "}"); ExpectPath(false); } TEST_F(IsPotentiallyReachableTest, OneLoopAfterTheOtherInsideAThirdLoop) { ParseAssembly( "declare i1 @switch()\n" "\n" "define void @test() {\n" "entry:\n" " br label %outerloop3\n" "outerloop3:\n" " br label %innerloop1\n" "innerloop1:\n" " %B = bitcast i8 undef to i8\n" " %x = call i1 @switch()\n" " br i1 %x, label %innerloop1, label %innerloop1exit\n" "innerloop1exit:\n" " br label %innerloop2\n" "innerloop2:\n" " %A = bitcast i8 undef to i8\n" " %y = call i1 @switch()\n" " br i1 %x, label %innerloop2, label %innerloop2exit\n" "innerloop2exit:" " ;; In outer loop3 now.\n" " %z = call i1 @switch()\n" " br i1 %z, label %outerloop3, label %exit\n" "exit:\n" " ret void\n" "}"); ExpectPath(true); } static const char *BranchInsideLoopIR = "declare i1 @switch()\n" "\n" "define void @test() {\n" "entry:\n" " br label %loop\n" "loop:\n" " %x = call i1 @switch()\n" " br i1 %x, label %nextloopblock, label %exit\n" "nextloopblock:\n" " %y = call i1 @switch()\n" " br i1 %y, label %left, label %right\n" "left:\n" " %A = bitcast i8 undef to i8\n" " br label %loop\n" "right:\n" " %B = bitcast i8 undef to i8\n" " br label %loop\n" "exit:\n" " ret void\n" "}"; TEST_F(IsPotentiallyReachableTest, BranchInsideLoop) { ParseAssembly(BranchInsideLoopIR); ExpectPath(true); } TEST_F(IsPotentiallyReachableTest, ModifyTest) { ParseAssembly(BranchInsideLoopIR); succ_iterator S = succ_begin(&*++M->getFunction("test")->begin()); BasicBlock *OldBB = S[0]; S[0] = S[1]; ExpectPath(false); S[0] = OldBB; ExpectPath(true); } TEST_F(IsPotentiallyReachableTest, UnreachableFromEntryTest) { ParseAssembly("define void @test() {\n" "entry:\n" " %A = bitcast i8 undef to i8\n" " ret void\n" "not.reachable:\n" " %B = bitcast i8 undef to i8\n" " ret void\n" "}"); ExpectPath(false); } TEST_F(IsPotentiallyReachableTest, UnreachableBlocksTest1) { ParseAssembly("define void @test() {\n" "entry:\n" " ret void\n" "not.reachable.1:\n" " %A = bitcast i8 undef to i8\n" " br label %not.reachable.2\n" "not.reachable.2:\n" " %B = bitcast i8 undef to i8\n" " ret void\n" "}"); ExpectPath(true); } TEST_F(IsPotentiallyReachableTest, UnreachableBlocksTest2) { ParseAssembly("define void @test() {\n" "entry:\n" " ret void\n" "not.reachable.1:\n" " %B = bitcast i8 undef to i8\n" " br label %not.reachable.2\n" "not.reachable.2:\n" " %A = bitcast i8 undef to i8\n" " ret void\n" "}"); ExpectPath(false); } TEST_F(IsPotentiallyReachableTest, SimpleExclusionTest) { ParseAssembly("define void @test() {\n" "entry:\n" " %A = bitcast i8 undef to i8\n" " br label %excluded\n" "excluded:\n" " br label %exit\n" "exit:\n" " %B = bitcast i8 undef to i8\n" " ret void\n" "}"); ExpectPath(false); } TEST_F(IsPotentiallyReachableTest, DiamondExcludedTest) { ParseAssembly("declare i1 @switch()\n" "\n" "define void @test() {\n" "entry:\n" " %x = call i1 @switch()\n" " %A = bitcast i8 undef to i8\n" " br i1 %x, label %excluded.1, label %excluded.2\n" "excluded.1:\n" " br label %exit\n" "excluded.2:\n" " br label %exit\n" "exit:\n" " %B = bitcast i8 undef to i8\n" " ret void\n" "}"); ExpectPath(false); } TEST_F(IsPotentiallyReachableTest, DiamondOneSideExcludedTest) { ParseAssembly("declare i1 @switch()\n" "\n" "define void @test() {\n" "entry:\n" " %x = call i1 @switch()\n" " %A = bitcast i8 undef to i8\n" " br i1 %x, label %excluded, label %diamond\n" "excluded:\n" " br label %exit\n" "diamond:\n" " br label %exit\n" "exit:\n" " %B = bitcast i8 undef to i8\n" " ret void\n" "}"); ExpectPath(true); } TEST_F(IsPotentiallyReachableTest, UnreachableToReachable) { ParseAssembly("define void @test() {\n" "entry:\n" " br label %exit\n" "unreachableblock:\n" " %A = bitcast i8 undef to i8\n" " br label %exit\n" "exit:\n" " %B = bitcast i8 undef to i8\n" " ret void\n" "}"); ExpectPath(true); }