//===- BytesOutputStyle.cpp ----------------------------------- *- C++ --*-===//
//
// 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
//
//===----------------------------------------------------------------------===//

#include "BytesOutputStyle.h"

#include "FormatUtil.h"
#include "StreamUtil.h"
#include "llvm-pdbutil.h"

#include "llvm/DebugInfo/CodeView/Formatters.h"
#include "llvm/DebugInfo/CodeView/LazyRandomTypeCollection.h"
#include "llvm/DebugInfo/MSF/MSFCommon.h"
#include "llvm/DebugInfo/MSF/MappedBlockStream.h"
#include "llvm/DebugInfo/PDB/Native/DbiStream.h"
#include "llvm/DebugInfo/PDB/Native/InfoStream.h"
#include "llvm/DebugInfo/PDB/Native/ModuleDebugStream.h"
#include "llvm/DebugInfo/PDB/Native/PDBFile.h"
#include "llvm/DebugInfo/PDB/Native/RawError.h"
#include "llvm/DebugInfo/PDB/Native/TpiStream.h"
#include "llvm/Support/BinaryStreamReader.h"
#include "llvm/Support/FormatAdapters.h"
#include "llvm/Support/FormatVariadic.h"

using namespace llvm;
using namespace llvm::codeview;
using namespace llvm::msf;
using namespace llvm::pdb;

namespace {
struct StreamSpec {
  uint32_t SI = 0;
  uint32_t Begin = 0;
  uint32_t Size = 0;
};
} // namespace

static Expected<StreamSpec> parseStreamSpec(StringRef Str) {
  StreamSpec Result;
  if (Str.consumeInteger(0, Result.SI))
    return make_error<RawError>(raw_error_code::invalid_format,
                                "Invalid Stream Specification");
  if (Str.consume_front(":")) {
    if (Str.consumeInteger(0, Result.Begin))
      return make_error<RawError>(raw_error_code::invalid_format,
                                  "Invalid Stream Specification");
  }
  if (Str.consume_front("@")) {
    if (Str.consumeInteger(0, Result.Size))
      return make_error<RawError>(raw_error_code::invalid_format,
                                  "Invalid Stream Specification");
  }

  if (!Str.empty())
    return make_error<RawError>(raw_error_code::invalid_format,
                                "Invalid Stream Specification");
  return Result;
}

static SmallVector<StreamSpec, 2> parseStreamSpecs(LinePrinter &P) {
  SmallVector<StreamSpec, 2> Result;

  for (auto &Str : opts::bytes::DumpStreamData) {
    auto ESS = parseStreamSpec(Str);
    if (!ESS) {
      P.formatLine("Error parsing stream spec {0}: {1}", Str,
                   toString(ESS.takeError()));
      continue;
    }
    Result.push_back(*ESS);
  }
  return Result;
}

static void printHeader(LinePrinter &P, const Twine &S) {
  P.NewLine();
  P.formatLine("{0,=60}", S);
  P.formatLine("{0}", fmt_repeat('=', 60));
}

BytesOutputStyle::BytesOutputStyle(PDBFile &File)
    : File(File), P(2, false, outs()) {}

Error BytesOutputStyle::dump() {

  if (opts::bytes::DumpBlockRange.hasValue()) {
    auto &R = *opts::bytes::DumpBlockRange;
    uint32_t Max = R.Max.getValueOr(R.Min);

    if (Max < R.Min)
      return make_error<StringError>(
          "Invalid block range specified.  Max < Min",
          inconvertibleErrorCode());
    if (Max >= File.getBlockCount())
      return make_error<StringError>(
          "Invalid block range specified.  Requested block out of bounds",
          inconvertibleErrorCode());

    dumpBlockRanges(R.Min, Max);
    P.NewLine();
  }

  if (opts::bytes::DumpByteRange.hasValue()) {
    auto &R = *opts::bytes::DumpByteRange;
    uint32_t Max = R.Max.getValueOr(File.getFileSize());

    if (Max < R.Min)
      return make_error<StringError>("Invalid byte range specified.  Max < Min",
                                     inconvertibleErrorCode());
    if (Max >= File.getFileSize())
      return make_error<StringError>(
          "Invalid byte range specified.  Requested byte larger than file size",
          inconvertibleErrorCode());

    dumpByteRanges(R.Min, Max);
    P.NewLine();
  }

  if (opts::bytes::Fpm) {
    dumpFpm();
    P.NewLine();
  }

  if (!opts::bytes::DumpStreamData.empty()) {
    dumpStreamBytes();
    P.NewLine();
  }

  if (opts::bytes::NameMap) {
    dumpNameMap();
    P.NewLine();
  }

  if (opts::bytes::SectionContributions) {
    dumpSectionContributions();
    P.NewLine();
  }

  if (opts::bytes::SectionMap) {
    dumpSectionMap();
    P.NewLine();
  }

  if (opts::bytes::ModuleInfos) {
    dumpModuleInfos();
    P.NewLine();
  }

  if (opts::bytes::FileInfo) {
    dumpFileInfo();
    P.NewLine();
  }

  if (opts::bytes::TypeServerMap) {
    dumpTypeServerMap();
    P.NewLine();
  }

  if (opts::bytes::ECData) {
    dumpECData();
    P.NewLine();
  }

  if (!opts::bytes::TypeIndex.empty()) {
    dumpTypeIndex(StreamTPI, opts::bytes::TypeIndex);
    P.NewLine();
  }

  if (!opts::bytes::IdIndex.empty()) {
    dumpTypeIndex(StreamIPI, opts::bytes::IdIndex);
    P.NewLine();
  }

  if (opts::bytes::ModuleSyms) {
    dumpModuleSyms();
    P.NewLine();
  }

  if (opts::bytes::ModuleC11) {
    dumpModuleC11();
    P.NewLine();
  }

  if (opts::bytes::ModuleC13) {
    dumpModuleC13();
    P.NewLine();
  }

  return Error::success();
}

void BytesOutputStyle::dumpNameMap() {
  printHeader(P, "Named Stream Map");

  AutoIndent Indent(P);

  auto &InfoS = Err(File.getPDBInfoStream());
  BinarySubstreamRef NS = InfoS.getNamedStreamsBuffer();
  auto Layout = File.getStreamLayout(StreamPDB);
  P.formatMsfStreamData("Named Stream Map", File, Layout, NS);
}

void BytesOutputStyle::dumpBlockRanges(uint32_t Min, uint32_t Max) {
  printHeader(P, "MSF Blocks");

  AutoIndent Indent(P);
  for (uint32_t I = Min; I <= Max; ++I) {
    uint64_t Base = I;
    Base *= File.getBlockSize();

    auto ExpectedData = File.getBlockData(I, File.getBlockSize());
    if (!ExpectedData) {
      P.formatLine("Could not get block {0}.  Reason = {1}", I,
                   toString(ExpectedData.takeError()));
      continue;
    }
    std::string Label = formatv("Block {0}", I).str();
    P.formatBinary(Label, *ExpectedData, Base, 0);
  }
}

void BytesOutputStyle::dumpSectionContributions() {
  printHeader(P, "Section Contributions");

  AutoIndent Indent(P);

  auto &DbiS = Err(File.getPDBDbiStream());
  BinarySubstreamRef NS = DbiS.getSectionContributionData();
  auto Layout = File.getStreamLayout(StreamDBI);
  P.formatMsfStreamData("Section Contributions", File, Layout, NS);
}

void BytesOutputStyle::dumpSectionMap() {
  printHeader(P, "Section Map");

  AutoIndent Indent(P);

  auto &DbiS = Err(File.getPDBDbiStream());
  BinarySubstreamRef NS = DbiS.getSecMapSubstreamData();
  auto Layout = File.getStreamLayout(StreamDBI);
  P.formatMsfStreamData("Section Map", File, Layout, NS);
}

void BytesOutputStyle::dumpModuleInfos() {
  printHeader(P, "Module Infos");

  AutoIndent Indent(P);

  auto &DbiS = Err(File.getPDBDbiStream());
  BinarySubstreamRef NS = DbiS.getModiSubstreamData();
  auto Layout = File.getStreamLayout(StreamDBI);
  P.formatMsfStreamData("Module Infos", File, Layout, NS);
}

void BytesOutputStyle::dumpFileInfo() {
  printHeader(P, "File Info");

  AutoIndent Indent(P);

  auto &DbiS = Err(File.getPDBDbiStream());
  BinarySubstreamRef NS = DbiS.getFileInfoSubstreamData();
  auto Layout = File.getStreamLayout(StreamDBI);
  P.formatMsfStreamData("File Info", File, Layout, NS);
}

void BytesOutputStyle::dumpTypeServerMap() {
  printHeader(P, "Type Server Map");

  AutoIndent Indent(P);

  auto &DbiS = Err(File.getPDBDbiStream());
  BinarySubstreamRef NS = DbiS.getTypeServerMapSubstreamData();
  auto Layout = File.getStreamLayout(StreamDBI);
  P.formatMsfStreamData("Type Server Map", File, Layout, NS);
}

void BytesOutputStyle::dumpECData() {
  printHeader(P, "Edit and Continue Data");

  AutoIndent Indent(P);

  auto &DbiS = Err(File.getPDBDbiStream());
  BinarySubstreamRef NS = DbiS.getECSubstreamData();
  auto Layout = File.getStreamLayout(StreamDBI);
  P.formatMsfStreamData("Edit and Continue Data", File, Layout, NS);
}

void BytesOutputStyle::dumpTypeIndex(uint32_t StreamIdx,
                                     ArrayRef<uint32_t> Indices) {
  assert(StreamIdx == StreamTPI || StreamIdx == StreamIPI);
  assert(!Indices.empty());

  bool IsTpi = (StreamIdx == StreamTPI);

  StringRef Label = IsTpi ? "Type (TPI) Records" : "Index (IPI) Records";
  printHeader(P, Label);
  auto &Stream = Err(IsTpi ? File.getPDBTpiStream() : File.getPDBIpiStream());

  AutoIndent Indent(P);

  auto Substream = Stream.getTypeRecordsSubstream();
  auto &Types = Err(initializeTypes(StreamIdx));
  auto Layout = File.getStreamLayout(StreamIdx);
  for (const auto &Id : Indices) {
    TypeIndex TI(Id);
    if (TI.toArrayIndex() >= Types.capacity()) {
      P.formatLine("Error: TypeIndex {0} does not exist", TI);
      continue;
    }

    auto Type = Types.getType(TI);
    uint32_t Offset = Types.getOffsetOfType(TI);
    auto OneType = Substream.slice(Offset, Type.length());
    P.formatMsfStreamData(formatv("Type {0}", TI).str(), File, Layout, OneType);
  }
}

template <typename CallbackT>
static void iterateOneModule(PDBFile &File, LinePrinter &P,
                             const DbiModuleList &Modules, uint32_t I,
                             uint32_t Digits, uint32_t IndentLevel,
                             CallbackT Callback) {
  if (I >= Modules.getModuleCount()) {
    P.formatLine("Mod {0:4} | Invalid module index ",
                 fmt_align(I, AlignStyle::Right, std::max(Digits, 4U)));
    return;
  }

  auto Modi = Modules.getModuleDescriptor(I);
  P.formatLine("Mod {0:4} | `{1}`: ",
               fmt_align(I, AlignStyle::Right, std::max(Digits, 4U)),
               Modi.getModuleName());

  uint16_t ModiStream = Modi.getModuleStreamIndex();
  AutoIndent Indent2(P, IndentLevel);
  if (ModiStream == kInvalidStreamIndex)
    return;

  auto ModStreamData = File.createIndexedStream(ModiStream);
  ModuleDebugStreamRef ModStream(Modi, std::move(ModStreamData));
  if (auto EC = ModStream.reload()) {
    P.formatLine("Could not parse debug information.");
    return;
  }
  auto Layout = File.getStreamLayout(ModiStream);
  Callback(I, ModStream, Layout);
}

template <typename CallbackT>
static void iterateModules(PDBFile &File, LinePrinter &P, uint32_t IndentLevel,
                           CallbackT Callback) {
  AutoIndent Indent(P);
  if (!File.hasPDBDbiStream()) {
    P.formatLine("DBI Stream not present");
    return;
  }

  ExitOnError Err("Unexpected error processing modules");

  auto &Stream = Err(File.getPDBDbiStream());

  const DbiModuleList &Modules = Stream.modules();

  if (opts::bytes::ModuleIndex.getNumOccurrences() > 0) {
    iterateOneModule(File, P, Modules, opts::bytes::ModuleIndex, 1, IndentLevel,
                     Callback);
  } else {
    uint32_t Count = Modules.getModuleCount();
    uint32_t Digits = NumDigits(Count);
    for (uint32_t I = 0; I < Count; ++I) {
      iterateOneModule(File, P, Modules, I, Digits, IndentLevel, Callback);
    }
  }
}

void BytesOutputStyle::dumpModuleSyms() {
  printHeader(P, "Module Symbols");

  AutoIndent Indent(P);

  iterateModules(File, P, 2,
                 [this](uint32_t Modi, const ModuleDebugStreamRef &Stream,
                        const MSFStreamLayout &Layout) {
                   auto Symbols = Stream.getSymbolsSubstream();
                   P.formatMsfStreamData("Symbols", File, Layout, Symbols);
                 });
}

void BytesOutputStyle::dumpModuleC11() {
  printHeader(P, "C11 Debug Chunks");

  AutoIndent Indent(P);

  iterateModules(File, P, 2,
                 [this](uint32_t Modi, const ModuleDebugStreamRef &Stream,
                        const MSFStreamLayout &Layout) {
                   auto Chunks = Stream.getC11LinesSubstream();
                   P.formatMsfStreamData("C11 Debug Chunks", File, Layout,
                                         Chunks);
                 });
}

void BytesOutputStyle::dumpModuleC13() {
  printHeader(P, "Debug Chunks");

  AutoIndent Indent(P);

  iterateModules(
      File, P, 2,
      [this](uint32_t Modi, const ModuleDebugStreamRef &Stream,
             const MSFStreamLayout &Layout) {
        auto Chunks = Stream.getC13LinesSubstream();
        if (opts::bytes::SplitChunks) {
          for (const auto &SS : Stream.subsections()) {
            BinarySubstreamRef ThisChunk;
            std::tie(ThisChunk, Chunks) = Chunks.split(SS.getRecordLength());
            P.formatMsfStreamData(formatChunkKind(SS.kind()), File, Layout,
                                  ThisChunk);
          }
        } else {
          P.formatMsfStreamData("Debug Chunks", File, Layout, Chunks);
        }
      });
}

void BytesOutputStyle::dumpByteRanges(uint32_t Min, uint32_t Max) {
  printHeader(P, "MSF Bytes");

  AutoIndent Indent(P);

  BinaryStreamReader Reader(File.getMsfBuffer());
  ArrayRef<uint8_t> Data;
  consumeError(Reader.skip(Min));
  uint32_t Size = Max - Min + 1;
  auto EC = Reader.readBytes(Data, Size);
  assert(!EC);
  consumeError(std::move(EC));
  P.formatBinary("Bytes", Data, Min);
}

Expected<codeview::LazyRandomTypeCollection &>
BytesOutputStyle::initializeTypes(uint32_t StreamIdx) {
  auto &TypeCollection = (StreamIdx == StreamTPI) ? TpiTypes : IpiTypes;
  if (TypeCollection)
    return *TypeCollection;

  auto Tpi = (StreamIdx == StreamTPI) ? File.getPDBTpiStream()
                                      : File.getPDBIpiStream();
  if (!Tpi)
    return Tpi.takeError();

  auto &Types = Tpi->typeArray();
  uint32_t Count = Tpi->getNumTypeRecords();
  auto Offsets = Tpi->getTypeIndexOffsets();
  TypeCollection =
      std::make_unique<LazyRandomTypeCollection>(Types, Count, Offsets);

  return *TypeCollection;
}

void BytesOutputStyle::dumpFpm() {
  printHeader(P, "Free Page Map");

  msf::MSFStreamLayout FpmLayout = File.getFpmStreamLayout();
  P.formatMsfStreamBlocks(File, FpmLayout);
}

void BytesOutputStyle::dumpStreamBytes() {
  if (StreamPurposes.empty())
    discoverStreamPurposes(File, StreamPurposes);

  printHeader(P, "Stream Data");
  ExitOnError Err("Unexpected error reading stream data");

  auto Specs = parseStreamSpecs(P);

  for (const auto &Spec : Specs) {
    AutoIndent Indent(P);
    if (Spec.SI >= StreamPurposes.size()) {
      P.formatLine("Stream {0}: Not present", Spec.SI);
      continue;
    }
    P.formatMsfStreamData("Data", File, Spec.SI,
                          StreamPurposes[Spec.SI].getShortName(), Spec.Begin,
                          Spec.Size);
  }
}
