//===- TestingSupport.cpp - Convert objects files into test files --------===//
//
// 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 "llvm/Object/ObjectFile.h"
#include "llvm/ProfileData/InstrProf.h"
#include "llvm/Support/Alignment.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/LEB128.h"
#include "llvm/Support/raw_ostream.h"
#include <functional>
#include <system_error>

using namespace llvm;
using namespace object;

int convertForTestingMain(int argc, const char *argv[]) {
  cl::opt<std::string> InputSourceFile(cl::Positional, cl::Required,
                                       cl::desc("<Source file>"));

  cl::opt<std::string> OutputFilename(
      "o", cl::Required,
      cl::desc(
          "File with the profile data obtained after an instrumented run"));

  cl::ParseCommandLineOptions(argc, argv, "LLVM code coverage tool\n");

  auto ObjErr = llvm::object::ObjectFile::createObjectFile(InputSourceFile);
  if (!ObjErr) {
    std::string Buf;
    raw_string_ostream OS(Buf);
    logAllUnhandledErrors(ObjErr.takeError(), OS);
    OS.flush();
    errs() << "error: " << Buf;
    return 1;
  }
  ObjectFile *OF = ObjErr.get().getBinary();
  auto BytesInAddress = OF->getBytesInAddress();
  if (BytesInAddress != 8) {
    errs() << "error: 64 bit binary expected\n";
    return 1;
  }

  // Look for the sections that we are interested in.
  int FoundSectionCount = 0;
  SectionRef ProfileNames, CoverageMapping, CoverageRecords;
  auto ObjFormat = OF->getTripleObjectFormat();
  for (const auto &Section : OF->sections()) {
    StringRef Name;
    if (Expected<StringRef> NameOrErr = Section.getName()) {
      Name = *NameOrErr;
    } else {
      consumeError(NameOrErr.takeError());
      return 1;
    }

    if (Name == llvm::getInstrProfSectionName(IPSK_name, ObjFormat,
                                              /*AddSegmentInfo=*/false)) {
      ProfileNames = Section;
    } else if (Name == llvm::getInstrProfSectionName(
                           IPSK_covmap, ObjFormat, /*AddSegmentInfo=*/false)) {
      CoverageMapping = Section;
    } else if (Name == llvm::getInstrProfSectionName(
                           IPSK_covfun, ObjFormat, /*AddSegmentInfo=*/false)) {
      CoverageRecords = Section;
    } else
      continue;
    ++FoundSectionCount;
  }
  if (FoundSectionCount != 3)
    return 1;

  // Get the contents of the given sections.
  uint64_t ProfileNamesAddress = ProfileNames.getAddress();
  StringRef CoverageMappingData;
  StringRef CoverageRecordsData;
  StringRef ProfileNamesData;
  if (Expected<StringRef> E = CoverageMapping.getContents())
    CoverageMappingData = *E;
  else {
    consumeError(E.takeError());
    return 1;
  }
  if (Expected<StringRef> E = CoverageRecords.getContents())
    CoverageRecordsData = *E;
  else {
    consumeError(E.takeError());
    return 1;
  }
  if (Expected<StringRef> E = ProfileNames.getContents())
    ProfileNamesData = *E;
  else {
    consumeError(E.takeError());
    return 1;
  }

  int FD;
  if (auto Err = sys::fs::openFileForWrite(OutputFilename, FD)) {
    errs() << "error: " << Err.message() << "\n";
    return 1;
  }

  raw_fd_ostream OS(FD, true);
  OS << "llvmcovmtestdata";
  encodeULEB128(ProfileNamesData.size(), OS);
  encodeULEB128(ProfileNamesAddress, OS);
  OS << ProfileNamesData;
  // Coverage mapping data is expected to have an alignment of 8.
  for (unsigned Pad = offsetToAlignment(OS.tell(), Align(8)); Pad; --Pad)
    OS.write(uint8_t(0));
  OS << CoverageMappingData;
  // Coverage records data is expected to have an alignment of 8.
  for (unsigned Pad = offsetToAlignment(OS.tell(), Align(8)); Pad; --Pad)
    OS.write(uint8_t(0));
  OS << CoverageRecordsData;

  return 0;
}
