//===- RemarkLinker.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
//
//===----------------------------------------------------------------------===//
//
// This file provides an implementation of the remark linker.
//
//===----------------------------------------------------------------------===//

#include "llvm/Remarks/RemarkLinker.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Object/ObjectFile.h"
#include "llvm/Object/SymbolicFile.h"
#include "llvm/Remarks/RemarkParser.h"
#include "llvm/Remarks/RemarkSerializer.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;
using namespace llvm::remarks;

static Expected<StringRef>
getRemarksSectionName(const object::ObjectFile &Obj) {
  if (Obj.isMachO())
    return StringRef("__remarks");
  // ELF -> .remarks, but there is no ELF support at this point.
  return createStringError(std::errc::illegal_byte_sequence,
                           "Unsupported file format.");
}

Expected<Optional<StringRef>>
llvm::remarks::getRemarksSectionContents(const object::ObjectFile &Obj) {
  Expected<StringRef> SectionName = getRemarksSectionName(Obj);
  if (!SectionName)
    return SectionName.takeError();

  for (const object::SectionRef &Section : Obj.sections()) {
    Expected<StringRef> MaybeName = Section.getName();
    if (!MaybeName)
      return MaybeName.takeError();
    if (*MaybeName != *SectionName)
      continue;

    if (Expected<StringRef> Contents = Section.getContents())
      return *Contents;
    else
      return Contents.takeError();
  }
  return Optional<StringRef>{};
}

Remark &RemarkLinker::keep(std::unique_ptr<Remark> Remark) {
  StrTab.internalize(*Remark);
  auto Inserted = Remarks.insert(std::move(Remark));
  return **Inserted.first;
}

void RemarkLinker::setExternalFilePrependPath(StringRef PrependPathIn) {
  PrependPath = std::string(PrependPathIn);
}

// Discard remarks with no source location.
static bool shouldKeepRemark(const Remark &R) { return R.Loc.hasValue(); }

Error RemarkLinker::link(StringRef Buffer, Optional<Format> RemarkFormat) {
  if (!RemarkFormat) {
    Expected<Format> ParserFormat = magicToFormat(Buffer);
    if (!ParserFormat)
      return ParserFormat.takeError();
    RemarkFormat = *ParserFormat;
  }

  Expected<std::unique_ptr<RemarkParser>> MaybeParser =
      createRemarkParserFromMeta(
          *RemarkFormat, Buffer, /*StrTab=*/None,
          PrependPath ? Optional<StringRef>(StringRef(*PrependPath))
                      : Optional<StringRef>(None));
  if (!MaybeParser)
    return MaybeParser.takeError();

  RemarkParser &Parser = **MaybeParser;

  while (true) {
    Expected<std::unique_ptr<Remark>> Next = Parser.next();
    if (Error E = Next.takeError()) {
      if (E.isA<EndOfFileError>()) {
        consumeError(std::move(E));
        break;
      }
      return E;
    }

    assert(*Next != nullptr);

    if (shouldKeepRemark(**Next))
      keep(std::move(*Next));
  }
  return Error::success();
}

Error RemarkLinker::link(const object::ObjectFile &Obj,
                         Optional<Format> RemarkFormat) {
  Expected<Optional<StringRef>> SectionOrErr = getRemarksSectionContents(Obj);
  if (!SectionOrErr)
    return SectionOrErr.takeError();

  if (Optional<StringRef> Section = *SectionOrErr)
    return link(*Section, RemarkFormat);
  return Error::success();
}

Error RemarkLinker::serialize(raw_ostream &OS, Format RemarksFormat) const {
  Expected<std::unique_ptr<RemarkSerializer>> MaybeSerializer =
      createRemarkSerializer(RemarksFormat, SerializerMode::Standalone, OS,
                             std::move(const_cast<StringTable &>(StrTab)));
  if (!MaybeSerializer)
    return MaybeSerializer.takeError();

  std::unique_ptr<remarks::RemarkSerializer> Serializer =
      std::move(*MaybeSerializer);

  for (const Remark &R : remarks())
    Serializer->emit(R);
  return Error::success();
}
