//===- subzero/src/IceASanInstrumentation.cpp - ASan ------------*- C++ -*-===//
//
//                        The Subzero Code Generator
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief Implements the AddressSanitizer instrumentation class.
///
//===----------------------------------------------------------------------===//

#include "IceASanInstrumentation.h"

#include "IceBuildDefs.h"
#include "IceCfg.h"
#include "IceCfgNode.h"
#include "IceGlobalInits.h"
#include "IceInst.h"
#include "IceTargetLowering.h"
#include "IceTypes.h"

#include <sstream>
#include <unordered_map>
#include <unordered_set>
#include <vector>

namespace Ice {

namespace {

constexpr SizeT BytesPerWord = sizeof(uint32_t);
constexpr SizeT RzSize = 32;
constexpr SizeT ShadowScaleLog2 = 3;
constexpr SizeT ShadowScale = 1 << ShadowScaleLog2;
constexpr SizeT ShadowLength32 = 1 << (32 - ShadowScaleLog2);
constexpr int32_t StackPoisonVal = -1;
constexpr const char *ASanPrefix = "__asan";
constexpr const char *RzPrefix = "__$rz";
constexpr const char *RzArrayName = "__$rz_array";
constexpr const char *RzSizesName = "__$rz_sizes";
const llvm::NaClBitcodeRecord::RecordVector RzContents =
    llvm::NaClBitcodeRecord::RecordVector(RzSize, 'R');

// In order to instrument the code correctly, the .pexe must not have had its
// symbols stripped.
using StringMap = std::unordered_map<std::string, std::string>;
using StringSet = std::unordered_set<std::string>;
// TODO(tlively): Handle all allocation functions
const StringMap FuncSubstitutions = {{"malloc", "__asan_malloc"},
                                     {"free", "__asan_free"},
                                     {"calloc", "__asan_calloc"},
                                     {"__asan_dummy_calloc", "__asan_calloc"},
                                     {"realloc", "__asan_realloc"}};
const StringSet FuncBlackList = {"_Balloc"};

llvm::NaClBitcodeRecord::RecordVector sizeToByteVec(SizeT Size) {
  llvm::NaClBitcodeRecord::RecordVector SizeContents;
  for (unsigned i = 0; i < sizeof(Size); ++i) {
    SizeContents.emplace_back(Size % (1 << CHAR_BIT));
    Size >>= CHAR_BIT;
  }
  return SizeContents;
}

} // end of anonymous namespace

ICE_TLS_DEFINE_FIELD(VarSizeMap *, ASanInstrumentation, LocalVars);
ICE_TLS_DEFINE_FIELD(std::vector<InstStore *> *, ASanInstrumentation,
                     LocalDtors);
ICE_TLS_DEFINE_FIELD(CfgNode *, ASanInstrumentation, CurNode);
ICE_TLS_DEFINE_FIELD(VarSizeMap *, ASanInstrumentation, CheckedVars);

bool ASanInstrumentation::isInstrumentable(Cfg *Func) {
  std::string FuncName = Func->getFunctionName().toStringOrEmpty();
  return FuncName == "" ||
         (FuncBlackList.count(FuncName) == 0 && FuncName.find(ASanPrefix) != 0);
}

// Create redzones around all global variables, ensuring that the initializer
// types of the redzones and their associated globals match so that they are
// laid out together in memory.
void ASanInstrumentation::instrumentGlobals(VariableDeclarationList &Globals) {
  std::unique_lock<std::mutex> _(GlobalsMutex);
  if (DidProcessGlobals)
    return;
  VariableDeclarationList NewGlobals;
  // Global holding pointers to all redzones
  auto *RzArray = VariableDeclaration::create(&NewGlobals);
  // Global holding sizes of all redzones
  auto *RzSizes = VariableDeclaration::create(&NewGlobals);

  RzArray->setName(Ctx, RzArrayName);
  RzSizes->setName(Ctx, RzSizesName);
  RzArray->setIsConstant(true);
  RzSizes->setIsConstant(true);
  NewGlobals.push_back(RzArray);
  NewGlobals.push_back(RzSizes);

  using PrototypeMap = std::unordered_map<std::string, FunctionDeclaration *>;
  PrototypeMap ProtoSubstitutions;
  for (VariableDeclaration *Global : Globals) {
    assert(Global->getAlignment() <= RzSize);
    VariableDeclaration *RzLeft = VariableDeclaration::create(&NewGlobals);
    VariableDeclaration *NewGlobal = Global;
    VariableDeclaration *RzRight = VariableDeclaration::create(&NewGlobals);
    RzLeft->setName(Ctx, nextRzName());
    RzRight->setName(Ctx, nextRzName());
    SizeT Alignment = std::max(RzSize, Global->getAlignment());
    SizeT RzLeftSize = Alignment;
    SizeT RzRightSize =
        RzSize + Utils::OffsetToAlignment(Global->getNumBytes(), Alignment);
    if (!Global->hasNonzeroInitializer()) {
      RzLeft->addInitializer(VariableDeclaration::ZeroInitializer::create(
          &NewGlobals, RzLeftSize));
      RzRight->addInitializer(VariableDeclaration::ZeroInitializer::create(
          &NewGlobals, RzRightSize));
    } else {
      RzLeft->addInitializer(VariableDeclaration::DataInitializer::create(
          &NewGlobals, llvm::NaClBitcodeRecord::RecordVector(RzLeftSize, 'R')));
      RzRight->addInitializer(VariableDeclaration::DataInitializer::create(
          &NewGlobals,
          llvm::NaClBitcodeRecord::RecordVector(RzRightSize, 'R')));

      // replace any pointers to allocator functions
      NewGlobal = VariableDeclaration::create(&NewGlobals);
      NewGlobal->setName(Global->getName());
      std::vector<VariableDeclaration::Initializer *> GlobalInits =
          Global->getInitializers();
      for (VariableDeclaration::Initializer *Init : GlobalInits) {
        auto *RelocInit =
            llvm::dyn_cast<VariableDeclaration::RelocInitializer>(Init);
        if (RelocInit == nullptr) {
          NewGlobal->addInitializer(Init);
          continue;
        }
        const GlobalDeclaration *TargetDecl = RelocInit->getDeclaration();
        const auto *TargetFunc =
            llvm::dyn_cast<FunctionDeclaration>(TargetDecl);
        if (TargetFunc == nullptr) {
          NewGlobal->addInitializer(Init);
          continue;
        }
        std::string TargetName = TargetDecl->getName().toStringOrEmpty();
        StringMap::const_iterator Subst = FuncSubstitutions.find(TargetName);
        if (Subst == FuncSubstitutions.end()) {
          NewGlobal->addInitializer(Init);
          continue;
        }
        std::string SubstName = Subst->second;
        PrototypeMap::iterator SubstProtoEntry =
            ProtoSubstitutions.find(SubstName);
        FunctionDeclaration *SubstProto;
        if (SubstProtoEntry != ProtoSubstitutions.end())
          SubstProto = SubstProtoEntry->second;
        else {
          constexpr bool IsProto = true;
          SubstProto = FunctionDeclaration::create(
              Ctx, TargetFunc->getSignature(), TargetFunc->getCallingConv(),
              llvm::GlobalValue::ExternalLinkage, IsProto);
          SubstProto->setName(Ctx, SubstName);
          ProtoSubstitutions.insert({SubstName, SubstProto});
        }

        NewGlobal->addInitializer(VariableDeclaration::RelocInitializer::create(
            &NewGlobals, SubstProto, RelocOffsetArray(0)));
      }
    }

    RzLeft->setIsConstant(Global->getIsConstant());
    NewGlobal->setIsConstant(Global->getIsConstant());
    RzRight->setIsConstant(Global->getIsConstant());
    RzLeft->setAlignment(Alignment);
    NewGlobal->setAlignment(Alignment);
    RzRight->setAlignment(1);
    RzArray->addInitializer(VariableDeclaration::RelocInitializer::create(
        &NewGlobals, RzLeft, RelocOffsetArray(0)));
    RzArray->addInitializer(VariableDeclaration::RelocInitializer::create(
        &NewGlobals, RzRight, RelocOffsetArray(0)));
    RzSizes->addInitializer(VariableDeclaration::DataInitializer::create(
        &NewGlobals, sizeToByteVec(RzLeftSize)));
    RzSizes->addInitializer(VariableDeclaration::DataInitializer::create(
        &NewGlobals, sizeToByteVec(RzRightSize)));

    NewGlobals.push_back(RzLeft);
    NewGlobals.push_back(NewGlobal);
    NewGlobals.push_back(RzRight);
    RzGlobalsNum += 2;

    GlobalSizes.insert({NewGlobal->getName(), NewGlobal->getNumBytes()});
  }

  // Replace old list of globals, without messing up arena allocators
  Globals.clear();
  Globals.merge(&NewGlobals);
  DidProcessGlobals = true;

  // Log the new set of globals
  if (BuildDefs::dump() && (getFlags().getVerbose() & IceV_GlobalInit)) {
    OstreamLocker _(Ctx);
    Ctx->getStrDump() << "========= Instrumented Globals =========\n";
    for (VariableDeclaration *Global : Globals) {
      Global->dump(Ctx->getStrDump());
    }
  }
}

std::string ASanInstrumentation::nextRzName() {
  std::stringstream Name;
  Name << RzPrefix << RzNum++;
  return Name.str();
}

// Check for an alloca signaling the presence of local variables and add a
// redzone if it is found
void ASanInstrumentation::instrumentFuncStart(LoweringContext &Context) {
  if (ICE_TLS_GET_FIELD(LocalDtors) == nullptr) {
    ICE_TLS_SET_FIELD(LocalDtors, new std::vector<InstStore *>());
    ICE_TLS_SET_FIELD(LocalVars, new VarSizeMap());
  }
  Cfg *Func = Context.getNode()->getCfg();
  using Entry = std::pair<SizeT, int32_t>;
  std::vector<InstAlloca *> NewAllocas;
  std::vector<Entry> PoisonVals;
  Variable *FirstShadowLocVar;
  InstArithmetic *ShadowIndexCalc;
  InstArithmetic *ShadowLocCalc;
  InstAlloca *Cur;
  ConstantInteger32 *VarSizeOp;
  while (!Context.atEnd()) {
    Cur = llvm::dyn_cast<InstAlloca>(iteratorToInst(Context.getCur()));
    VarSizeOp = (Cur == nullptr)
                    ? nullptr
                    : llvm::dyn_cast<ConstantInteger32>(Cur->getSizeInBytes());
    if (Cur == nullptr || VarSizeOp == nullptr) {
      Context.advanceCur();
      Context.advanceNext();
      continue;
    }

    Cur->setDeleted();

    if (PoisonVals.empty()) {
      // insert leftmost redzone
      auto *LastRzVar = Func->makeVariable(IceType_i32);
      LastRzVar->setName(Func, nextRzName());
      auto *ByteCount = ConstantInteger32::create(Ctx, IceType_i32, RzSize);
      constexpr SizeT Alignment = 8;
      NewAllocas.emplace_back(
          InstAlloca::create(Func, LastRzVar, ByteCount, Alignment));
      PoisonVals.emplace_back(Entry{RzSize >> ShadowScaleLog2, StackPoisonVal});

      // Calculate starting address for poisoning
      FirstShadowLocVar = Func->makeVariable(IceType_i32);
      FirstShadowLocVar->setName(Func, "firstShadowLoc");
      auto *ShadowIndexVar = Func->makeVariable(IceType_i32);
      ShadowIndexVar->setName(Func, "shadowIndex");

      auto *ShadowScaleLog2Const =
          ConstantInteger32::create(Ctx, IceType_i32, ShadowScaleLog2);
      auto *ShadowMemLocConst =
          ConstantInteger32::create(Ctx, IceType_i32, ShadowLength32);

      ShadowIndexCalc =
          InstArithmetic::create(Func, InstArithmetic::Lshr, ShadowIndexVar,
                                 LastRzVar, ShadowScaleLog2Const);
      ShadowLocCalc =
          InstArithmetic::create(Func, InstArithmetic::Add, FirstShadowLocVar,
                                 ShadowIndexVar, ShadowMemLocConst);
    }

    // create the new alloca that includes a redzone
    SizeT VarSize = VarSizeOp->getValue();
    Variable *Dest = Cur->getDest();
    ICE_TLS_GET_FIELD(LocalVars)->insert({Dest, VarSize});
    SizeT RzPadding = RzSize + Utils::OffsetToAlignment(VarSize, RzSize);
    auto *ByteCount =
        ConstantInteger32::create(Ctx, IceType_i32, VarSize + RzPadding);
    constexpr SizeT Alignment = 8;
    NewAllocas.emplace_back(
        InstAlloca::create(Func, Dest, ByteCount, Alignment));

    const SizeT Zeros = VarSize >> ShadowScaleLog2;
    const SizeT Offset = VarSize % ShadowScale;
    const SizeT PoisonBytes =
        ((VarSize + RzPadding) >> ShadowScaleLog2) - Zeros - 1;
    if (Zeros > 0)
      PoisonVals.emplace_back(Entry{Zeros, 0});
    PoisonVals.emplace_back(Entry{1, (Offset == 0) ? StackPoisonVal : Offset});
    PoisonVals.emplace_back(Entry{PoisonBytes, StackPoisonVal});
    Context.advanceCur();
    Context.advanceNext();
  }

  Context.rewind();
  if (PoisonVals.empty()) {
    Context.advanceNext();
    return;
  }
  for (InstAlloca *RzAlloca : NewAllocas) {
    Context.insert(RzAlloca);
  }
  Context.insert(ShadowIndexCalc);
  Context.insert(ShadowLocCalc);

  // Poison redzones
  std::vector<Entry>::iterator Iter = PoisonVals.begin();
  for (SizeT Offset = 0; Iter != PoisonVals.end(); Offset += BytesPerWord) {
    int32_t CurVals[BytesPerWord] = {0};
    for (uint32_t i = 0; i < BytesPerWord; ++i) {
      if (Iter == PoisonVals.end())
        break;
      Entry Val = *Iter;
      CurVals[i] = Val.second;
      --Val.first;
      if (Val.first > 0)
        *Iter = Val;
      else
        ++Iter;
    }
    int32_t Poison = ((CurVals[3] & 0xff) << 24) | ((CurVals[2] & 0xff) << 16) |
                     ((CurVals[1] & 0xff) << 8) | (CurVals[0] & 0xff);
    if (Poison == 0)
      continue;
    auto *PoisonConst = ConstantInteger32::create(Ctx, IceType_i32, Poison);
    auto *ZeroConst = ConstantInteger32::create(Ctx, IceType_i32, 0);
    auto *OffsetConst = ConstantInteger32::create(Ctx, IceType_i32, Offset);
    auto *PoisonAddrVar = Func->makeVariable(IceType_i32);
    Context.insert(InstArithmetic::create(Func, InstArithmetic::Add,
                                          PoisonAddrVar, FirstShadowLocVar,
                                          OffsetConst));
    Context.insert(InstStore::create(Func, PoisonConst, PoisonAddrVar));
    ICE_TLS_GET_FIELD(LocalDtors)
        ->emplace_back(InstStore::create(Func, ZeroConst, PoisonAddrVar));
  }
  Context.advanceNext();
}

void ASanInstrumentation::instrumentCall(LoweringContext &Context,
                                         InstCall *Instr) {
  auto *CallTarget =
      llvm::dyn_cast<ConstantRelocatable>(Instr->getCallTarget());
  if (CallTarget == nullptr)
    return;

  std::string TargetName = CallTarget->getName().toStringOrEmpty();
  auto Subst = FuncSubstitutions.find(TargetName);
  if (Subst == FuncSubstitutions.end())
    return;

  std::string SubName = Subst->second;
  Constant *NewFunc = Ctx->getConstantExternSym(Ctx->getGlobalString(SubName));
  auto *NewCall =
      InstCall::create(Context.getNode()->getCfg(), Instr->getNumArgs(),
                       Instr->getDest(), NewFunc, Instr->isTailcall());
  for (SizeT I = 0, Args = Instr->getNumArgs(); I < Args; ++I)
    NewCall->addArg(Instr->getArg(I));
  Context.insert(NewCall);
  Instr->setDeleted();
}

void ASanInstrumentation::instrumentLoad(LoweringContext &Context,
                                         InstLoad *Instr) {
  Operand *Src = Instr->getSourceAddress();
  if (auto *Reloc = llvm::dyn_cast<ConstantRelocatable>(Src)) {
    auto *NewLoad = InstLoad::create(Context.getNode()->getCfg(),
                                     Instr->getDest(), instrumentReloc(Reloc));
    Instr->setDeleted();
    Context.insert(NewLoad);
    Instr = NewLoad;
  }
  Constant *Func =
      Ctx->getConstantExternSym(Ctx->getGlobalString("__asan_check_load"));
  instrumentAccess(Context, Instr->getSourceAddress(),
                   typeWidthInBytes(Instr->getDest()->getType()), Func);
}

void ASanInstrumentation::instrumentStore(LoweringContext &Context,
                                          InstStore *Instr) {
  Operand *Data = Instr->getData();
  if (auto *Reloc = llvm::dyn_cast<ConstantRelocatable>(Data)) {
    auto *NewStore = InstStore::create(
        Context.getNode()->getCfg(), instrumentReloc(Reloc), Instr->getAddr());
    Instr->setDeleted();
    Context.insert(NewStore);
    Instr = NewStore;
  }
  Constant *Func =
      Ctx->getConstantExternSym(Ctx->getGlobalString("__asan_check_store"));
  instrumentAccess(Context, Instr->getAddr(),
                   typeWidthInBytes(Instr->getData()->getType()), Func);
}

ConstantRelocatable *
ASanInstrumentation::instrumentReloc(ConstantRelocatable *Reloc) {
  std::string DataName = Reloc->getName().toString();
  StringMap::const_iterator DataSub = FuncSubstitutions.find(DataName);
  if (DataSub != FuncSubstitutions.end()) {
    return ConstantRelocatable::create(
        Ctx, Reloc->getType(),
        RelocatableTuple(Reloc->getOffset(), RelocOffsetArray(0),
                         Ctx->getGlobalString(DataSub->second),
                         Reloc->getEmitString()));
  }
  return Reloc;
}

void ASanInstrumentation::instrumentAccess(LoweringContext &Context,
                                           Operand *Op, SizeT Size,
                                           Constant *CheckFunc) {
  // Skip redundant checks within basic blocks
  VarSizeMap *Checked = ICE_TLS_GET_FIELD(CheckedVars);
  if (ICE_TLS_GET_FIELD(CurNode) != Context.getNode()) {
    ICE_TLS_SET_FIELD(CurNode, Context.getNode());
    if (Checked == NULL) {
      Checked = new VarSizeMap();
      ICE_TLS_SET_FIELD(CheckedVars, Checked);
    }
    Checked->clear();
  }
  VarSizeMap::iterator PrevCheck = Checked->find(Op);
  if (PrevCheck != Checked->end() && PrevCheck->second >= Size)
    return;
  else
    Checked->insert({Op, Size});

  // check for known good local access
  VarSizeMap::iterator LocalSize = ICE_TLS_GET_FIELD(LocalVars)->find(Op);
  if (LocalSize != ICE_TLS_GET_FIELD(LocalVars)->end() &&
      LocalSize->second >= Size)
    return;
  if (isOkGlobalAccess(Op, Size))
    return;
  constexpr SizeT NumArgs = 2;
  constexpr Variable *Void = nullptr;
  constexpr bool NoTailCall = false;
  auto *Call = InstCall::create(Context.getNode()->getCfg(), NumArgs, Void,
                                CheckFunc, NoTailCall);
  Call->addArg(Op);
  Call->addArg(ConstantInteger32::create(Ctx, IceType_i32, Size));
  // play games to insert the call before the access instruction
  InstList::iterator Next = Context.getNext();
  Context.setInsertPoint(Context.getCur());
  Context.insert(Call);
  Context.setNext(Next);
}

// TODO(tlively): Trace back load and store addresses to find their real offsets
bool ASanInstrumentation::isOkGlobalAccess(Operand *Op, SizeT Size) {
  auto *Reloc = llvm::dyn_cast<ConstantRelocatable>(Op);
  if (Reloc == nullptr)
    return false;
  RelocOffsetT Offset = Reloc->getOffset();
  GlobalSizeMap::iterator GlobalSize = GlobalSizes.find(Reloc->getName());
  return GlobalSize != GlobalSizes.end() && GlobalSize->second - Offset >= Size;
}

void ASanInstrumentation::instrumentRet(LoweringContext &Context, InstRet *) {
  Cfg *Func = Context.getNode()->getCfg();
  Context.setInsertPoint(Context.getCur());
  for (InstStore *RzUnpoison : *ICE_TLS_GET_FIELD(LocalDtors)) {
    Context.insert(
        InstStore::create(Func, RzUnpoison->getData(), RzUnpoison->getAddr()));
  }
  Context.advanceCur();
  Context.advanceNext();
}

void ASanInstrumentation::instrumentStart(Cfg *Func) {
  Constant *ShadowMemInit =
      Ctx->getConstantExternSym(Ctx->getGlobalString("__asan_init"));
  constexpr SizeT NumArgs = 3;
  constexpr Variable *Void = nullptr;
  constexpr bool NoTailCall = false;
  auto *Call = InstCall::create(Func, NumArgs, Void, ShadowMemInit, NoTailCall);
  Func->getEntryNode()->getInsts().push_front(Call);

  instrumentGlobals(*getGlobals());

  Call->addArg(ConstantInteger32::create(Ctx, IceType_i32, RzGlobalsNum));
  Call->addArg(Ctx->getConstantSym(0, Ctx->getGlobalString(RzArrayName)));
  Call->addArg(Ctx->getConstantSym(0, Ctx->getGlobalString(RzSizesName)));
}

// TODO(tlively): make this more efficient with swap idiom
void ASanInstrumentation::finishFunc(Cfg *) {
  ICE_TLS_GET_FIELD(LocalVars)->clear();
  ICE_TLS_GET_FIELD(LocalDtors)->clear();
}

} // end of namespace Ice
