#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/ParentMap.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
#include "llvm/Support/raw_ostream.h"
using namespace clang;
using namespace ento;
static bool shouldRunOnFunctionOrMethod(const NamedDecl *ND);
static bool isInitializationMethod(const ObjCMethodDecl *MD);
static bool isInitMessage(const ObjCMethodCall &Msg);
static bool isSelfVar(SVal location, CheckerContext &C);
namespace {
class ObjCSelfInitChecker : public Checker<  check::PostObjCMessage,
                                             check::PostStmt<ObjCIvarRefExpr>,
                                             check::PreStmt<ReturnStmt>,
                                             check::PreCall,
                                             check::PostCall,
                                             check::Location,
                                             check::Bind > {
  mutable std::unique_ptr<BugType> BT;
  void checkForInvalidSelf(const Expr *E, CheckerContext &C,
                           const char *errorStr) const;
public:
  ObjCSelfInitChecker() {}
  void checkPostObjCMessage(const ObjCMethodCall &Msg, CheckerContext &C) const;
  void checkPostStmt(const ObjCIvarRefExpr *E, CheckerContext &C) const;
  void checkPreStmt(const ReturnStmt *S, CheckerContext &C) const;
  void checkLocation(SVal location, bool isLoad, const Stmt *S,
                     CheckerContext &C) const;
  void checkBind(SVal loc, SVal val, const Stmt *S, CheckerContext &C) const;
  void checkPreCall(const CallEvent &CE, CheckerContext &C) const;
  void checkPostCall(const CallEvent &CE, CheckerContext &C) const;
  void printState(raw_ostream &Out, ProgramStateRef State,
                  const char *NL, const char *Sep) const override;
};
} 
namespace {
enum SelfFlagEnum {
    SelfFlag_None = 0x0,
    SelfFlag_Self    = 0x1,
    SelfFlag_InitRes = 0x2
};
}
REGISTER_MAP_WITH_PROGRAMSTATE(SelfFlag, SymbolRef, SelfFlagEnum)
REGISTER_TRAIT_WITH_PROGRAMSTATE(CalledInit, bool)
REGISTER_TRAIT_WITH_PROGRAMSTATE(PreCallSelfFlags, SelfFlagEnum)
static SelfFlagEnum getSelfFlags(SVal val, ProgramStateRef state) {
  if (SymbolRef sym = val.getAsSymbol())
    if (const SelfFlagEnum *attachedFlags = state->get<SelfFlag>(sym))
      return *attachedFlags;
  return SelfFlag_None;
}
static SelfFlagEnum getSelfFlags(SVal val, CheckerContext &C) {
  return getSelfFlags(val, C.getState());
}
static void addSelfFlag(ProgramStateRef state, SVal val,
                        SelfFlagEnum flag, CheckerContext &C) {
    if (SymbolRef sym = val.getAsSymbol()) {
    state = state->set<SelfFlag>(sym,
                                 SelfFlagEnum(getSelfFlags(val, state) | flag));
    C.addTransition(state);
  }
}
static bool hasSelfFlag(SVal val, SelfFlagEnum flag, CheckerContext &C) {
  return getSelfFlags(val, C) & flag;
}
static bool isInvalidSelf(const Expr *E, CheckerContext &C) {
  SVal exprVal = C.getSVal(E);
  if (!hasSelfFlag(exprVal, SelfFlag_Self, C))
    return false;   if (hasSelfFlag(exprVal, SelfFlag_InitRes, C))
    return false; 
  return true;
}
void ObjCSelfInitChecker::checkForInvalidSelf(const Expr *E, CheckerContext &C,
                                              const char *errorStr) const {
  if (!E)
    return;
  if (!C.getState()->get<CalledInit>())
    return;
  if (!isInvalidSelf(E, C))
    return;
    ExplodedNode *N = C.generateErrorNode();
  if (!N)
    return;
  if (!BT)
    BT.reset(new BugType(this, "Missing \"self = [(super or self) init...]\"",
                         categories::CoreFoundationObjectiveC));
  C.emitReport(std::make_unique<PathSensitiveBugReport>(*BT, errorStr, N));
}
void ObjCSelfInitChecker::checkPostObjCMessage(const ObjCMethodCall &Msg,
                                               CheckerContext &C) const {
      
    if (!shouldRunOnFunctionOrMethod(dyn_cast<NamedDecl>(
                                C.getCurrentAnalysisDeclContext()->getDecl())))
    return;
  if (isInitMessage(Msg)) {
        ProgramStateRef state = C.getState();
                state = state->set<CalledInit>(true);
    SVal V = C.getSVal(Msg.getOriginExpr());
    addSelfFlag(state, V, SelfFlag_InitRes, C);
    return;
  }
        }
void ObjCSelfInitChecker::checkPostStmt(const ObjCIvarRefExpr *E,
                                        CheckerContext &C) const {
    if (!shouldRunOnFunctionOrMethod(dyn_cast<NamedDecl>(
                                 C.getCurrentAnalysisDeclContext()->getDecl())))
    return;
  checkForInvalidSelf(
      E->getBase(), C,
      "Instance variable used while 'self' is not set to the result of "
      "'[(super or self) init...]'");
}
void ObjCSelfInitChecker::checkPreStmt(const ReturnStmt *S,
                                       CheckerContext &C) const {
    if (!shouldRunOnFunctionOrMethod(dyn_cast<NamedDecl>(
                                 C.getCurrentAnalysisDeclContext()->getDecl())))
    return;
  checkForInvalidSelf(S->getRetValue(), C,
                      "Returning 'self' while it is not set to the result of "
                      "'[(super or self) init...]'");
}
void ObjCSelfInitChecker::checkPreCall(const CallEvent &CE,
                                       CheckerContext &C) const {
    if (!shouldRunOnFunctionOrMethod(dyn_cast<NamedDecl>(
                                 C.getCurrentAnalysisDeclContext()->getDecl())))
    return;
  ProgramStateRef state = C.getState();
  unsigned NumArgs = CE.getNumArgs();
            for (unsigned i = 0; i < NumArgs; ++i) {
    SVal argV = CE.getArgSVal(i);
    if (isSelfVar(argV, C)) {
      SelfFlagEnum selfFlags =
          getSelfFlags(state->getSVal(argV.castAs<Loc>()), C);
      C.addTransition(state->set<PreCallSelfFlags>(selfFlags));
      return;
    } else if (hasSelfFlag(argV, SelfFlag_Self, C)) {
      SelfFlagEnum selfFlags = getSelfFlags(argV, C);
      C.addTransition(state->set<PreCallSelfFlags>(selfFlags));
      return;
    }
  }
}
void ObjCSelfInitChecker::checkPostCall(const CallEvent &CE,
                                        CheckerContext &C) const {
    if (!shouldRunOnFunctionOrMethod(dyn_cast<NamedDecl>(
                                 C.getCurrentAnalysisDeclContext()->getDecl())))
    return;
  ProgramStateRef state = C.getState();
  SelfFlagEnum prevFlags = state->get<PreCallSelfFlags>();
  if (!prevFlags)
    return;
  state = state->remove<PreCallSelfFlags>();
  unsigned NumArgs = CE.getNumArgs();
  for (unsigned i = 0; i < NumArgs; ++i) {
    SVal argV = CE.getArgSVal(i);
    if (isSelfVar(argV, C)) {
                        addSelfFlag(state, state->getSVal(argV.castAs<Loc>()), prevFlags, C);
      return;
    } else if (hasSelfFlag(argV, SelfFlag_Self, C)) {
                              addSelfFlag(state, CE.getReturnValue(), prevFlags, C);
      return;
    }
  }
  C.addTransition(state);
}
void ObjCSelfInitChecker::checkLocation(SVal location, bool isLoad,
                                        const Stmt *S,
                                        CheckerContext &C) const {
  if (!shouldRunOnFunctionOrMethod(dyn_cast<NamedDecl>(
        C.getCurrentAnalysisDeclContext()->getDecl())))
    return;
      ProgramStateRef state = C.getState();
  if (isSelfVar(location, C))
    addSelfFlag(state, state->getSVal(location.castAs<Loc>()), SelfFlag_Self,
                C);
}
void ObjCSelfInitChecker::checkBind(SVal loc, SVal val, const Stmt *S,
                                    CheckerContext &C) const {
            if ((isSelfVar(loc, C)) &&
      !hasSelfFlag(val, SelfFlag_InitRes, C) &&
      !hasSelfFlag(val, SelfFlag_Self, C) &&
      !isSelfVar(val, C)) {
        ProgramStateRef State = C.getState();
    State = State->remove<CalledInit>();
    if (SymbolRef sym = loc.getAsSymbol())
      State = State->remove<SelfFlag>(sym);
    C.addTransition(State);
  }
}
void ObjCSelfInitChecker::printState(raw_ostream &Out, ProgramStateRef State,
                                     const char *NL, const char *Sep) const {
  SelfFlagTy FlagMap = State->get<SelfFlag>();
  bool DidCallInit = State->get<CalledInit>();
  SelfFlagEnum PreCallFlags = State->get<PreCallSelfFlags>();
  if (FlagMap.isEmpty() && !DidCallInit && !PreCallFlags)
    return;
  Out << Sep << NL << *this << " :" << NL;
  if (DidCallInit)
    Out << "  An init method has been called." << NL;
  if (PreCallFlags != SelfFlag_None) {
    if (PreCallFlags & SelfFlag_Self) {
      Out << "  An argument of the current call came from the 'self' variable."
          << NL;
    }
    if (PreCallFlags & SelfFlag_InitRes) {
      Out << "  An argument of the current call came from an init method."
          << NL;
    }
  }
  Out << NL;
  for (SelfFlagTy::iterator I = FlagMap.begin(), E = FlagMap.end();
       I != E; ++I) {
    Out << I->first << " : ";
    if (I->second == SelfFlag_None)
      Out << "none";
    if (I->second & SelfFlag_Self)
      Out << "self variable";
    if (I->second & SelfFlag_InitRes) {
      if (I->second != SelfFlag_InitRes)
        Out << " | ";
      Out << "result of init method";
    }
    Out << NL;
  }
}
static bool shouldRunOnFunctionOrMethod(const NamedDecl *ND) {
  if (!ND)
    return false;
  const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(ND);
  if (!MD)
    return false;
  if (!isInitializationMethod(MD))
    return false;
      ASTContext &Ctx = MD->getASTContext();
  IdentifierInfo* NSObjectII = &Ctx.Idents.get("NSObject");
  ObjCInterfaceDecl *ID = MD->getClassInterface()->getSuperClass();
  for ( ; ID ; ID = ID->getSuperClass()) {
    IdentifierInfo *II = ID->getIdentifier();
    if (II == NSObjectII)
      break;
  }
  return ID != nullptr;
}
static bool isSelfVar(SVal location, CheckerContext &C) {
  AnalysisDeclContext *analCtx = C.getCurrentAnalysisDeclContext();
  if (!analCtx->getSelfDecl())
    return false;
  if (!isa<loc::MemRegionVal>(location))
    return false;
  loc::MemRegionVal MRV = location.castAs<loc::MemRegionVal>();
  if (const DeclRegion *DR = dyn_cast<DeclRegion>(MRV.stripCasts()))
    return (DR->getDecl() == analCtx->getSelfDecl());
  return false;
}
static bool isInitializationMethod(const ObjCMethodDecl *MD) {
  return MD->getMethodFamily() == OMF_init;
}
static bool isInitMessage(const ObjCMethodCall &Call) {
  return Call.getMethodFamily() == OMF_init;
}
void ento::registerObjCSelfInitChecker(CheckerManager &mgr) {
  mgr.registerChecker<ObjCSelfInitChecker>();
}
bool ento::shouldRegisterObjCSelfInitChecker(const CheckerManager &mgr) {
  return true;
}