//===- RecordsSlice.cpp --------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Implements the Records Slice APIs.
//
//===----------------------------------------------------------------------===//

#include "llvm/TextAPI/RecordsSlice.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/TextAPI/InterfaceFile.h"
#include "llvm/TextAPI/Record.h"
#include "llvm/TextAPI/Symbol.h"
#include <utility>

using namespace llvm;
using namespace llvm::MachO;

Record *RecordsSlice::addRecord(StringRef Name, SymbolFlags Flags,
                                GlobalRecord::Kind GV, RecordLinkage Linkage) {
  // Find a specific Record type to capture.
  auto [APIName, SymKind, InterfaceType] = parseSymbol(Name);
  Name = APIName;
  switch (SymKind) {
  case EncodeKind::GlobalSymbol:
    return addGlobal(Name, Linkage, GV, Flags);
  case EncodeKind::ObjectiveCClass:
    return addObjCInterface(Name, Linkage, InterfaceType);
  case EncodeKind::ObjectiveCClassEHType: {
    ObjCInterfaceRecord *Rec = addObjCInterface(Name, Linkage, InterfaceType);
    // When classes without ehtype are used in try/catch blocks
    // a weak-defined symbol is exported.
    if ((Flags & SymbolFlags::WeakDefined) == SymbolFlags::WeakDefined)
      updateFlags(Rec, SymbolFlags::WeakDefined);
    return Rec;
  }
  case EncodeKind::ObjectiveCInstanceVariable: {
    auto [Super, IVar] = Name.split('.');
    // Attempt to find super class.
    ObjCContainerRecord *Container = findContainer(/*isIVar=*/false, Super);
    // If not found, create extension since there is no mapped class symbol.
    if (Container == nullptr)
      Container = addObjCCategory(Super, {});
    return addObjCIVar(Container, IVar, Linkage);
  }
  }

  llvm_unreachable("unexpected symbol kind when adding to Record Slice");
}

ObjCContainerRecord *RecordsSlice::findContainer(bool IsIVar,
                                                 StringRef Name) const {
  StringRef Super = IsIVar ? Name.split('.').first : Name;
  ObjCContainerRecord *Container = findObjCInterface(Super);
  // Ivars can only exist with extensions, if they did not come from
  // class.
  if (Container == nullptr)
    Container = findObjCCategory(Super, "");
  return Container;
}

template <typename R, typename C = RecordMap<R>, typename K = StringRef>
R *findRecord(K Key, const C &Container) {
  const auto *Record = Container.find(Key);
  if (Record == Container.end())
    return nullptr;
  return Record->second.get();
}

GlobalRecord *RecordsSlice::findGlobal(StringRef Name,
                                       GlobalRecord::Kind GV) const {
  auto *Record = findRecord<GlobalRecord>(Name, Globals);
  if (!Record)
    return nullptr;

  switch (GV) {
  case GlobalRecord::Kind::Variable: {
    if (!Record->isVariable())
      return nullptr;
    break;
  }
  case GlobalRecord::Kind::Function: {
    if (!Record->isFunction())
      return nullptr;
    break;
  }
  case GlobalRecord::Kind::Unknown:
    return Record;
  }

  return Record;
}

RecordLinkage
ObjCInterfaceRecord::getLinkageForSymbol(ObjCIFSymbolKind CurrType) const {
  assert(CurrType <= ObjCIFSymbolKind::EHType &&
         "expected single ObjCIFSymbolKind enum value");
  if (CurrType == ObjCIFSymbolKind::Class)
    return Linkages.Class;

  if (CurrType == ObjCIFSymbolKind::MetaClass)
    return Linkages.MetaClass;

  if (CurrType == ObjCIFSymbolKind::EHType)
    return Linkages.EHType;

  llvm_unreachable("unexpected ObjCIFSymbolKind");
}

void ObjCInterfaceRecord::updateLinkageForSymbols(ObjCIFSymbolKind SymType,
                                                  RecordLinkage Link) {
  if ((SymType & ObjCIFSymbolKind::Class) == ObjCIFSymbolKind::Class)
    Linkages.Class = std::max(Link, Linkages.Class);
  if ((SymType & ObjCIFSymbolKind::MetaClass) == ObjCIFSymbolKind::MetaClass)
    Linkages.MetaClass = std::max(Link, Linkages.MetaClass);
  if ((SymType & ObjCIFSymbolKind::EHType) == ObjCIFSymbolKind::EHType)
    Linkages.EHType = std::max(Link, Linkages.EHType);

  // Obj-C Classes represent multiple symbols that could have competing
  // linkages, in this case assign the largest one, when querying the linkage of
  // the record itself. This allows visitors pick whether they want to account
  // for complete symbol information.
  Linkage =
      std::max(Linkages.Class, std::max(Linkages.MetaClass, Linkages.EHType));
}

ObjCInterfaceRecord *RecordsSlice::findObjCInterface(StringRef Name) const {
  return findRecord<ObjCInterfaceRecord>(Name, Classes);
}

ObjCCategoryRecord *RecordsSlice::findObjCCategory(StringRef ClassToExtend,
                                                   StringRef Category) const {
  return findRecord<ObjCCategoryRecord>(std::make_pair(ClassToExtend, Category),
                                        Categories);
}

ObjCIVarRecord *ObjCContainerRecord::findObjCIVar(StringRef IVar) const {
  return findRecord<ObjCIVarRecord>(IVar, IVars);
}

ObjCIVarRecord *RecordsSlice::findObjCIVar(bool IsScopedName,
                                           StringRef Name) const {
  // If scoped name, the name of the container is known.
  if (IsScopedName) {
    // IVar does not exist if there is not a container assigned to it.
    auto *Container = findContainer(/*IsIVar=*/true, Name);
    if (!Container)
      return nullptr;

    StringRef IVar = Name.substr(Name.find_first_of('.') + 1);
    return Container->findObjCIVar(IVar);
  }

  // Otherwise traverse through containers and attempt to find IVar.
  auto getIVar = [Name](auto &Records) -> ObjCIVarRecord * {
    for (const auto &[_, Container] : Records) {
      if (auto *IVarR = Container->findObjCIVar(Name))
        return IVarR;
    }
    return nullptr;
  };

  if (auto *IVarRecord = getIVar(Classes))
    return IVarRecord;

  return getIVar(Categories);
}

GlobalRecord *RecordsSlice::addGlobal(StringRef Name, RecordLinkage Linkage,
                                      GlobalRecord::Kind GV, SymbolFlags Flags,
                                      bool Inlined) {
  if (GV == GlobalRecord::Kind::Function)
    Flags |= SymbolFlags::Text;
  else if (GV == GlobalRecord::Kind::Variable)
    Flags |= SymbolFlags::Data;

  Name = copyString(Name);
  auto Result = Globals.try_emplace(Name);
  if (Result.second)
    Result.first->second =
        std::make_unique<GlobalRecord>(Name, Linkage, Flags, GV, Inlined);
  else {
    updateLinkage(Result.first->second.get(), Linkage);
    updateFlags(Result.first->second.get(), Flags);
  }
  return Result.first->second.get();
}

ObjCInterfaceRecord *RecordsSlice::addObjCInterface(StringRef Name,
                                                    RecordLinkage Linkage,
                                                    ObjCIFSymbolKind SymType) {
  Name = copyString(Name);
  auto Result = Classes.try_emplace(Name);
  if (Result.second)
    Result.first->second =
        std::make_unique<ObjCInterfaceRecord>(Name, Linkage, SymType);
  else
    Result.first->second->updateLinkageForSymbols(SymType, Linkage);
  return Result.first->second.get();
}

SymbolFlags Record::mergeFlags(SymbolFlags Flags, RecordLinkage Linkage) {
  // Add Linkage properties into Flags.
  switch (Linkage) {
  case RecordLinkage::Rexported:
    Flags |= SymbolFlags::Rexported;
    return Flags;
  case RecordLinkage::Undefined:
    Flags |= SymbolFlags::Undefined;
    return Flags;
  default:
    return Flags;
  }
}

bool ObjCInterfaceRecord::addObjCCategory(ObjCCategoryRecord *Record) {
  auto Result = Categories.insert({Name, Record});
  return Result.second;
}

ObjCCategoryRecord *RecordsSlice::addObjCCategory(StringRef ClassToExtend,
                                                  StringRef Category) {
  Category = copyString(Category);
  ClassToExtend = copyString(ClassToExtend);

  // Add owning record first into record slice.
  auto Result = Categories.try_emplace(std::make_pair(ClassToExtend, Category));
  if (Result.second)
    Result.first->second =
        std::make_unique<ObjCCategoryRecord>(ClassToExtend, Category);

  // Then add reference to it in in the class.
  if (auto *ObjCClass = findObjCInterface(ClassToExtend))
    ObjCClass->addObjCCategory(Result.first->second.get());

  return Result.first->second.get();
}

std::vector<ObjCIVarRecord *> ObjCContainerRecord::getObjCIVars() const {
  std::vector<ObjCIVarRecord *> Records;
  Records.reserve(IVars.size());
  for (const auto &Record : IVars)
    Records.push_back(Record.second.get());
  return Records;
}

std::vector<ObjCCategoryRecord *>
ObjCInterfaceRecord::getObjCCategories() const {
  std::vector<ObjCCategoryRecord *> Records;
  Records.reserve(Categories.size());
  for (const auto &Record : Categories)
    Records.push_back(Record.second);
  return Records;
}

ObjCIVarRecord *ObjCContainerRecord::addObjCIVar(StringRef IVar,
                                                 RecordLinkage Linkage) {
  auto Result = IVars.try_emplace(IVar);
  if (Result.second)
    Result.first->second = std::make_unique<ObjCIVarRecord>(IVar, Linkage);
  return Result.first->second.get();
}

ObjCIVarRecord *RecordsSlice::addObjCIVar(ObjCContainerRecord *Container,
                                          StringRef Name,
                                          RecordLinkage Linkage) {
  Name = copyString(Name);
  ObjCIVarRecord *Record = Container->addObjCIVar(Name, Linkage);
  updateLinkage(Record, Linkage);
  return Record;
}

StringRef RecordsSlice::copyString(StringRef String) {
  if (String.empty())
    return {};

  if (StringAllocator.identifyObject(String.data()))
    return String;

  void *Ptr = StringAllocator.Allocate(String.size(), 1);
  memcpy(Ptr, String.data(), String.size());
  return StringRef(reinterpret_cast<const char *>(Ptr), String.size());
}

RecordsSlice::BinaryAttrs &RecordsSlice::getBinaryAttrs() {
  if (!hasBinaryAttrs())
    BA = std::make_unique<BinaryAttrs>();
  return *BA;
}

void RecordsSlice::visit(RecordVisitor &V) const {
  for (auto &G : Globals)
    V.visitGlobal(*G.second);
  for (auto &C : Classes)
    V.visitObjCInterface(*C.second);
  for (auto &Cat : Categories)
    V.visitObjCCategory(*Cat.second);
}

static std::unique_ptr<InterfaceFile>
createInterfaceFile(const Records &Slices, StringRef InstallName) {
  // Pickup symbols first.
  auto Symbols = std::make_unique<SymbolSet>();
  for (auto &S : Slices) {
    if (S->empty())
      continue;
    auto &BA = S->getBinaryAttrs();
    if (BA.InstallName != InstallName)
      continue;

    SymbolConverter Converter(Symbols.get(), S->getTarget(),
                              !BA.TwoLevelNamespace);
    S->visit(Converter);
  }

  auto File = std::make_unique<InterfaceFile>(std::move(Symbols));
  File->setInstallName(InstallName);
  // Assign other attributes.
  for (auto &S : Slices) {
    if (S->empty())
      continue;
    auto &BA = S->getBinaryAttrs();
    if (BA.InstallName != InstallName)
      continue;
    const Target &Targ = S->getTarget();
    File->addTarget(Targ);
    File->setFromBinaryAttrs(BA, Targ);
  }

  return File;
}

std::unique_ptr<InterfaceFile>
llvm::MachO::convertToInterfaceFile(const Records &Slices) {
  std::unique_ptr<InterfaceFile> File;
  if (Slices.empty())
    return File;

  SetVector<StringRef> InstallNames;
  for (auto &S : Slices) {
    auto Name = S->getBinaryAttrs().InstallName;
    if (Name.empty())
      continue;
    InstallNames.insert(Name);
  }

  File = createInterfaceFile(Slices, *InstallNames.begin());
  for (StringRef IN : llvm::drop_begin(InstallNames))
    File->addDocument(createInterfaceFile(Slices, IN));

  return File;
}
