Compiler projects using llvm
//===- ObjCopyTest.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
//
//===----------------------------------------------------------------------===//

#include "llvm/ObjCopy/ObjCopy.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ObjCopy/ConfigManager.h"
#include "llvm/Object/ObjectFile.h"
#include "llvm/ObjectYAML/yaml2obj.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileUtilities.h"
#include "llvm/Testing/Support/Error.h"
#include "gtest/gtest.h"

using namespace llvm;
using namespace object;
using namespace objcopy;
using namespace yaml;

const char *SimpleFileCOFFYAML = R"(
--- !COFF
header:
  Machine:         IMAGE_FILE_MACHINE_AMD64
  Characteristics: [  ]
sections:
  - Name:            .text
    Characteristics: [  ]
    Alignment:       4
    SectionData:     E800000000C3C3C3
symbols:
...
)";

const char *SimpleFileELFYAML = R"(
--- !ELF
FileHeader:
  Class:    ELFCLASS64
  Data:     ELFDATA2LSB
  Type:     ET_REL
Sections:
  - Name:            .text
    Type:            SHT_PROGBITS
    Flags:           [ SHF_ALLOC ]
    Content:        "12345678"
)";

const char *SimpleFileMachOYAML = R"(
--- !mach-o
FileHeader:
  magic:           0xFEEDFACF
  cputype:         0x01000007
  cpusubtype:      0x80000003
  filetype:        0x00000001
  ncmds:           1
  sizeofcmds:      152
  flags:           0x00002000
  reserved:        0x00000000
LoadCommands:
  - cmd:             LC_SEGMENT_64
    cmdsize:         152
    segname:         __TEXT
    vmaddr:          0
    vmsize:          4
    fileoff:         184
    filesize:        4
    maxprot:         7
    initprot:        7
    nsects:          1
    flags:           0
    Sections:
      - sectname:        __text
        segname:         __TEXT
        addr:            0x0000000000000000
        content:         'AABBCCDD'
        size:            4
        offset:          184
        align:           0
        reloff:          0x00000000
        nreloc:          0
        flags:           0x80000400
        reserved1:       0x00000000
        reserved2:       0x00000000
        reserved3:       0x00000000
...
)";

const char *SimpleFileWasmYAML = R"(
--- !WASM
FileHeader:
  Version:         0x00000001
Sections:
  - Type: CUSTOM
    Name: text
    Payload: ABC123
...
)";

// Create ObjectFile from \p YamlCreationString and do validation using \p
// IsValidFormat checker. \p Storage is a storage for data. \returns created
// ObjectFile.
Expected<std::unique_ptr<ObjectFile>> createObjectFileFromYamlDescription(
    const char *YamlCreationString, SmallVector<char> &Storage,
    function_ref<bool(const Binary &File)> IsValidFormat) {
  auto ErrHandler = [&](const Twine &Msg) { FAIL() << "Error: " << Msg; };

  std::unique_ptr<ObjectFile> Obj =
      yaml2ObjectFile(Storage, YamlCreationString, ErrHandler);
  if (!Obj)
    return createError("could not create ObjectFile from yaml description");

  if (!IsValidFormat(*Obj))
    return createError("wrong file format");

  return std::move(Obj);
}

// Call objcopy::executeObjcopyOnBinary for \p Config and \p In. \p DataVector
// is a holder for data. \returns Binary for copied data.
Expected<std::unique_ptr<Binary>>
callObjCopy(ConfigManager &Config, object::Binary &In,
            SmallVector<char> &DataVector,
            function_ref<bool(const Binary &File)> IsValidFormat) {
  raw_svector_ostream OutStream(DataVector);

  if (Error Err = objcopy::executeObjcopyOnBinary(Config, In, OutStream))
    return std::move(Err);

  MemoryBufferRef Buffer(StringRef(DataVector.data(), DataVector.size()),
                         Config.Common.OutputFilename);

  Expected<std::unique_ptr<Binary>> Result = createBinary(Buffer);

  // Check copied file.
  if (!Result)
    return Result;

  if (!IsValidFormat(**Result))
    return createError("wrong file format");

  if (!(*Result)->isObject())
    return createError("binary is not object file");

  return Result;
}

// \returns true if specified \p File has a section named \p SectionName.
bool hasSection(ObjectFile &File, StringRef SectionName) {
  for (const object::SectionRef &Sec : File.sections()) {
    Expected<StringRef> CurSecNameOrErr = Sec.getName();
    if (!CurSecNameOrErr)
      continue;

    if (*CurSecNameOrErr == SectionName)
      return true;
  }

  return false;
}

// Check that specified \p File has a section \p SectionName and its data
// matches \p SectionData.
void checkSectionData(ObjectFile &File, StringRef SectionName,
                      StringRef SectionData) {
  for (const object::SectionRef &Sec : File.sections()) {
    Expected<StringRef> CurSecNameOrErr = Sec.getName();
    ASSERT_THAT_EXPECTED(CurSecNameOrErr, Succeeded());

    if (*CurSecNameOrErr == SectionName) {
      Expected<StringRef> CurSectionData = Sec.getContents();
      ASSERT_THAT_EXPECTED(CurSectionData, Succeeded());
      EXPECT_TRUE(Sec.getSize() == SectionData.size());
      EXPECT_TRUE(memcmp(CurSectionData->data(), SectionData.data(),
                         SectionData.size()) == 0);
      return;
    }
  }

  // Section SectionName must be presented.
  EXPECT_TRUE(false);
}

void copySimpleInMemoryFileImpl(
    const char *YamlCreationString,
    function_ref<bool(const Binary &File)> IsValidFormat) {
  SCOPED_TRACE("copySimpleInMemoryFileImpl");

  // Create Object file from YAML description.
  SmallVector<char> Storage;
  Expected<std::unique_ptr<ObjectFile>> Obj =
      createObjectFileFromYamlDescription(YamlCreationString, Storage,
                                          IsValidFormat);
  ASSERT_THAT_EXPECTED(Obj, Succeeded());

  ConfigManager Config;
  Config.Common.OutputFilename = "a.out";

  // Call executeObjcopyOnBinary()
  SmallVector<char> DataVector;
  Expected<std::unique_ptr<Binary>> Result =
      callObjCopy(Config, *Obj.get(), DataVector, IsValidFormat);
  ASSERT_THAT_EXPECTED(Result, Succeeded());
}

TEST(CopySimpleInMemoryFile, COFF) {
  SCOPED_TRACE("CopySimpleInMemoryFileCOFF");

  copySimpleInMemoryFileImpl(SimpleFileCOFFYAML,
                             [](const Binary &File) { return File.isCOFF(); });
}

TEST(CopySimpleInMemoryFile, ELF) {
  SCOPED_TRACE("CopySimpleInMemoryFileELF");

  copySimpleInMemoryFileImpl(SimpleFileELFYAML,
                             [](const Binary &File) { return File.isELF(); });
}

TEST(CopySimpleInMemoryFile, MachO) {
  SCOPED_TRACE("CopySimpleInMemoryFileMachO");

  copySimpleInMemoryFileImpl(SimpleFileMachOYAML,
                             [](const Binary &File) { return File.isMachO(); });
}

TEST(CopySimpleInMemoryFile, Wasm) {
  SCOPED_TRACE("CopySimpleInMemoryFileWasm");

  copySimpleInMemoryFileImpl(SimpleFileWasmYAML,
                             [](const Binary &File) { return File.isWasm(); });
}

enum Action : uint8_t { AddSection, UpdateSection };

void addOrUpdateSectionToFileImpl(
    const char *YamlCreationString,
    function_ref<bool(const Binary &File)> IsValidFormat,
    StringRef NewSectionName, StringRef NewSectionData, Action SectionAction) {
  SCOPED_TRACE("addOrUpdateSectionToFileImpl");

  // Create Object file from YAML description.
  SmallVector<char> Storage;
  Expected<std::unique_ptr<ObjectFile>> Obj =
      createObjectFileFromYamlDescription(YamlCreationString, Storage,
                                          IsValidFormat);
  ASSERT_THAT_EXPECTED(Obj, Succeeded());

  std::unique_ptr<MemoryBuffer> NewSectionBuffer =
      MemoryBuffer::getMemBuffer(NewSectionData, NewSectionName, false);
  std::string Name;
  if ((*Obj)->isMachO())
    Name = "__TEXT," + NewSectionName.str();
  else
    Name = NewSectionName.str();

  ConfigManager Config;
  Config.Common.OutputFilename = "a.out";
  if (SectionAction == AddSection)
    Config.Common.AddSection.push_back({Name, std::move(NewSectionBuffer)});
  else
    Config.Common.UpdateSection.push_back({Name, std::move(NewSectionBuffer)});

  // Call executeObjcopyOnBinary()
  SmallVector<char> DataVector;
  Expected<std::unique_ptr<Binary>> Result =
      callObjCopy(Config, *Obj.get(), DataVector, IsValidFormat);
  ASSERT_THAT_EXPECTED(Result, Succeeded());

  // Check that copied file has the new section.
  checkSectionData(*static_cast<ObjectFile *>((*Result).get()), NewSectionName,
                   NewSectionData);
}

TEST(AddSection, COFF) {
  SCOPED_TRACE("addSectionToFileCOFF");

  addOrUpdateSectionToFileImpl(
      SimpleFileCOFFYAML, [](const Binary &File) { return File.isCOFF(); },
      ".foo", "1234", AddSection);
}

TEST(AddSection, ELF) {
  SCOPED_TRACE("addSectionToFileELF");

  addOrUpdateSectionToFileImpl(
      SimpleFileELFYAML, [](const Binary &File) { return File.isELF(); },
      ".foo", "1234", AddSection);
}

TEST(AddSection, MachO) {
  SCOPED_TRACE("addSectionToFileMachO");

  addOrUpdateSectionToFileImpl(
      SimpleFileMachOYAML, [](const Binary &File) { return File.isMachO(); },
      "__foo", "1234", AddSection);
}

TEST(AddSection, Wasm) {
  SCOPED_TRACE("addSectionToFileWasm");

  addOrUpdateSectionToFileImpl(
      SimpleFileWasmYAML, [](const Binary &File) { return File.isWasm(); },
      ".foo", "1234", AddSection);
}

TEST(UpdateSection, COFF) {
  SCOPED_TRACE("updateSectionToFileCOFF");

  addOrUpdateSectionToFileImpl(
      SimpleFileCOFFYAML, [](const Binary &File) { return File.isCOFF(); },
      ".text", "1234", UpdateSection);
}

TEST(UpdateSection, ELF) {
  SCOPED_TRACE("updateSectionToFileELF");

  addOrUpdateSectionToFileImpl(
      SimpleFileELFYAML, [](const Binary &File) { return File.isELF(); },
      ".text", "1234", UpdateSection);
}

TEST(UpdateSection, MachO) {
  SCOPED_TRACE("updateSectionToFileMachO");

  addOrUpdateSectionToFileImpl(
      SimpleFileMachOYAML, [](const Binary &File) { return File.isMachO(); },
      "__text", "1234", UpdateSection);
}

void removeSectionByPatternImpl(
    const char *YamlCreationString,
    function_ref<bool(const Binary &File)> IsValidFormat,
    StringRef SectionWildcard, StringRef SectionName) {
  SCOPED_TRACE("removeSectionByPatternImpl");

  // Create Object file from YAML description.
  SmallVector<char> Storage;
  Expected<std::unique_ptr<ObjectFile>> Obj =
      createObjectFileFromYamlDescription(YamlCreationString, Storage,
                                          IsValidFormat);
  ASSERT_THAT_EXPECTED(Obj, Succeeded());

  // Check that section is present.
  EXPECT_TRUE(hasSection(**Obj, SectionName));

  Expected<NameOrPattern> Pattern = objcopy::NameOrPattern::create(
      SectionWildcard, objcopy::MatchStyle::Wildcard,
      [](Error Err) { return Err; });

  ConfigManager Config;
  Config.Common.OutputFilename = "a.out";
  EXPECT_THAT_ERROR(Config.Common.ToRemove.addMatcher(std::move(Pattern)),
                    Succeeded());

  SmallVector<char> DataVector;
  Expected<std::unique_ptr<Binary>> Result =
      callObjCopy(Config, *Obj.get(), DataVector, IsValidFormat);
  ASSERT_THAT_EXPECTED(Result, Succeeded());

  // Check that section was removed.
  EXPECT_FALSE(
      hasSection(*static_cast<ObjectFile *>((*Result).get()), SectionName));
}

TEST(RemoveSectionByPattern, COFF) {
  SCOPED_TRACE("removeSectionByPatternCOFF");

  removeSectionByPatternImpl(
      SimpleFileCOFFYAML, [](const Binary &File) { return File.isCOFF(); },
      "\\.text*", ".text");
}

TEST(RemoveSectionByPattern, ELF) {
  SCOPED_TRACE("removeSectionByPatternELF");

  removeSectionByPatternImpl(
      SimpleFileELFYAML, [](const Binary &File) { return File.isELF(); },
      "\\.text*", ".text");
}

TEST(RemoveSectionByPattern, MachO) {
  SCOPED_TRACE("removeSectionByPatternMachO");

  removeSectionByPatternImpl(
      SimpleFileMachOYAML, [](const Binary &File) { return File.isMachO(); },
      "__TEXT,__text*", "__text");
}

TEST(RemoveSectionByPattern, Wasm) {
  SCOPED_TRACE("removeSectionByPatternWasm");

  removeSectionByPatternImpl(
      SimpleFileWasmYAML, [](const Binary &File) { return File.isWasm(); },
      "text*", "text");
}