//===- 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<BasicBlock *, 4> 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<LoopInfoWrapperPass>();
        AU.addRequired<DominatorTreeWrapperPass>();
      }

      bool runOnFunction(Function &F) override {
        if (!F.hasName() || F.getName() != "test")
          return false;

        LoopInfo *LI = &getAnalysis<LoopInfoWrapperPass>().getLoopInfo();
        DominatorTree *DT =
            &getAnalysis<DominatorTreeWrapperPass>().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<BasicBlock *, 4> 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<Module> M;
  Instruction *A, *B;
  SmallPtrSet<BasicBlock *, 4> 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);
}
