//=======- VirtualCallChecker.cpp --------------------------------*- C++ -*-==//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
//  This file defines a checker that checks virtual function calls during
//  construction or destruction of C++ objects.
//
//===----------------------------------------------------------------------===//

#include "ClangSACheckers.h"
#include "clang/AST/DeclCXX.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h"

using namespace clang;
using namespace ento;

namespace {
enum class ObjectState : bool { CtorCalled, DtorCalled };
} // end namespace
  // FIXME: Ascending over StackFrameContext maybe another method.

namespace llvm {
template <> struct FoldingSetTrait<ObjectState> {
  static inline void Profile(ObjectState X, FoldingSetNodeID &ID) {
    ID.AddInteger(static_cast<int>(X));
  }
};
} // end namespace llvm

namespace {
class VirtualCallChecker
    : public Checker<check::BeginFunction, check::EndFunction, check::PreCall> {
  mutable std::unique_ptr<BugType> BT;

public:
  // The flag to determine if pure virtual functions should be issued only.
  DefaultBool IsPureOnly;

  void checkBeginFunction(CheckerContext &C) const;
  void checkEndFunction(CheckerContext &C) const;
  void checkPreCall(const CallEvent &Call, CheckerContext &C) const;

private:
  void registerCtorDtorCallInState(bool IsBeginFunction,
                                   CheckerContext &C) const;
  void reportBug(StringRef Msg, bool PureError, const MemRegion *Reg,
                 CheckerContext &C) const;

  class VirtualBugVisitor : public BugReporterVisitorImpl<VirtualBugVisitor> {
  private:
    const MemRegion *ObjectRegion;
    bool Found;

  public:
    VirtualBugVisitor(const MemRegion *R) : ObjectRegion(R), Found(false) {}

    void Profile(llvm::FoldingSetNodeID &ID) const override {
      static int X = 0;
      ID.AddPointer(&X);
      ID.AddPointer(ObjectRegion);
    }

    std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N,
                                                   const ExplodedNode *PrevN,
                                                   BugReporterContext &BRC,
                                                   BugReport &BR) override;
  };
};
} // end namespace

// GDM (generic data map) to the memregion of this for the ctor and dtor.
REGISTER_MAP_WITH_PROGRAMSTATE(CtorDtorMap, const MemRegion *, ObjectState)

std::shared_ptr<PathDiagnosticPiece>
VirtualCallChecker::VirtualBugVisitor::VisitNode(const ExplodedNode *N,
                                                 const ExplodedNode *PrevN,
                                                 BugReporterContext &BRC,
                                                 BugReport &BR) {
  // We need the last ctor/dtor which call the virtual function.
  // The visitor walks the ExplodedGraph backwards.
  if (Found)
    return nullptr;

  ProgramStateRef State = N->getState();
  const LocationContext *LCtx = N->getLocationContext();
  const CXXConstructorDecl *CD =
      dyn_cast_or_null<CXXConstructorDecl>(LCtx->getDecl());
  const CXXDestructorDecl *DD =
      dyn_cast_or_null<CXXDestructorDecl>(LCtx->getDecl());

  if (!CD && !DD)
    return nullptr;

  ProgramStateManager &PSM = State->getStateManager();
  auto &SVB = PSM.getSValBuilder();
  const auto *MD = dyn_cast<CXXMethodDecl>(LCtx->getDecl());
  if (!MD)
    return nullptr;
  auto ThiSVal =
      State->getSVal(SVB.getCXXThis(MD, LCtx->getCurrentStackFrame()));
  const MemRegion *Reg = ThiSVal.castAs<loc::MemRegionVal>().getRegion();
  if (!Reg)
    return nullptr;
  if (Reg != ObjectRegion)
    return nullptr;

  const Stmt *S = PathDiagnosticLocation::getStmt(N);
  if (!S)
    return nullptr;
  Found = true;

  std::string InfoText;
  if (CD)
    InfoText = "This constructor of an object of type '" +
               CD->getNameAsString() +
               "' has not returned when the virtual method was called";
  else
    InfoText = "This destructor of an object of type '" +
               DD->getNameAsString() +
               "' has not returned when the virtual method was called";

  // Generate the extra diagnostic.
  PathDiagnosticLocation Pos(S, BRC.getSourceManager(),
                             N->getLocationContext());
  return std::make_shared<PathDiagnosticEventPiece>(Pos, InfoText, true);
}

// The function to check if a callexpr is a virtual function.
static bool isVirtualCall(const CallExpr *CE) {
  bool CallIsNonVirtual = false;

  if (const MemberExpr *CME = dyn_cast<MemberExpr>(CE->getCallee())) {
    // The member access is fully qualified (i.e., X::F).
    // Treat this as a non-virtual call and do not warn.
    if (CME->getQualifier())
      CallIsNonVirtual = true;

    if (const Expr *Base = CME->getBase()) {
      // The most derived class is marked final.
      if (Base->getBestDynamicClassType()->hasAttr<FinalAttr>())
        CallIsNonVirtual = true;
    }
  }

  const CXXMethodDecl *MD =
      dyn_cast_or_null<CXXMethodDecl>(CE->getDirectCallee());
  if (MD && MD->isVirtual() && !CallIsNonVirtual && !MD->hasAttr<FinalAttr>() &&
      !MD->getParent()->hasAttr<FinalAttr>())
    return true;
  return false;
}

// The BeginFunction callback when enter a constructor or a destructor.
void VirtualCallChecker::checkBeginFunction(CheckerContext &C) const {
  registerCtorDtorCallInState(true, C);
}

// The EndFunction callback when leave a constructor or a destructor.
void VirtualCallChecker::checkEndFunction(CheckerContext &C) const {
  registerCtorDtorCallInState(false, C);
}

void VirtualCallChecker::checkPreCall(const CallEvent &Call,
                                      CheckerContext &C) const {
  const auto MC = dyn_cast<CXXMemberCall>(&Call);
  if (!MC)
    return;

  const CXXMethodDecl *MD = dyn_cast_or_null<CXXMethodDecl>(Call.getDecl());
  if (!MD)
    return;
  ProgramStateRef State = C.getState();
  const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());

  if (IsPureOnly && !MD->isPure())
    return;
  if (!isVirtualCall(CE))
    return;

  const MemRegion *Reg = MC->getCXXThisVal().getAsRegion();
  const ObjectState *ObState = State->get<CtorDtorMap>(Reg);
  if (!ObState)
    return;
  // Check if a virtual method is called.
  // The GDM of constructor and destructor should be true.
  if (*ObState == ObjectState::CtorCalled) {
    if (IsPureOnly && MD->isPure())
      reportBug("Call to pure virtual function during construction", true, Reg,
                C);
    else if (!MD->isPure())
      reportBug("Call to virtual function during construction", false, Reg, C);
    else
      reportBug("Call to pure virtual function during construction", false, Reg,
                C);
  }

  if (*ObState == ObjectState::DtorCalled) {
    if (IsPureOnly && MD->isPure())
      reportBug("Call to pure virtual function during destruction", true, Reg,
                C);
    else if (!MD->isPure())
      reportBug("Call to virtual function during destruction", false, Reg, C);
    else
      reportBug("Call to pure virtual function during construction", false, Reg,
                C);
  }
}

void VirtualCallChecker::registerCtorDtorCallInState(bool IsBeginFunction,
                                                     CheckerContext &C) const {
  const auto *LCtx = C.getLocationContext();
  const auto *MD = dyn_cast_or_null<CXXMethodDecl>(LCtx->getDecl());
  if (!MD)
    return;

  ProgramStateRef State = C.getState();
  auto &SVB = C.getSValBuilder();

  // Enter a constructor, set the corresponding memregion be true.
  if (isa<CXXConstructorDecl>(MD)) {
    auto ThiSVal =
        State->getSVal(SVB.getCXXThis(MD, LCtx->getCurrentStackFrame()));
    const MemRegion *Reg = ThiSVal.getAsRegion();
    if (IsBeginFunction)
      State = State->set<CtorDtorMap>(Reg, ObjectState::CtorCalled);
    else
      State = State->remove<CtorDtorMap>(Reg);

    C.addTransition(State);
    return;
  }

  // Enter a Destructor, set the corresponding memregion be true.
  if (isa<CXXDestructorDecl>(MD)) {
    auto ThiSVal =
        State->getSVal(SVB.getCXXThis(MD, LCtx->getCurrentStackFrame()));
    const MemRegion *Reg = ThiSVal.getAsRegion();
    if (IsBeginFunction)
      State = State->set<CtorDtorMap>(Reg, ObjectState::DtorCalled);
    else
      State = State->remove<CtorDtorMap>(Reg);

    C.addTransition(State);
    return;
  }
}

void VirtualCallChecker::reportBug(StringRef Msg, bool IsSink,
                                   const MemRegion *Reg,
                                   CheckerContext &C) const {
  ExplodedNode *N;
  if (IsSink)
    N = C.generateErrorNode();
  else
    N = C.generateNonFatalErrorNode();

  if (!N)
    return;
  if (!BT)
    BT.reset(new BugType(
        this, "Call to virtual function during construction or destruction",
        "C++ Object Lifecycle"));

  auto Reporter = llvm::make_unique<BugReport>(*BT, Msg, N);
  Reporter->addVisitor(llvm::make_unique<VirtualBugVisitor>(Reg));
  C.emitReport(std::move(Reporter));
}

void ento::registerVirtualCallChecker(CheckerManager &mgr) {
  VirtualCallChecker *checker = mgr.registerChecker<VirtualCallChecker>();

  checker->IsPureOnly =
      mgr.getAnalyzerOptions().getBooleanOption("PureOnly", false, checker);
}
