Compiler projects using llvm
//===- llvm-ifs.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 "ErrorCollector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/ADT/Triple.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/InterfaceStub/ELFObjHandler.h"
#include "llvm/InterfaceStub/IFSHandler.h"
#include "llvm/InterfaceStub/IFSStub.h"
#include "llvm/ObjectYAML/yaml2obj.h"
#include "llvm/Option/Arg.h"
#include "llvm/Option/ArgList.h"
#include "llvm/Option/Option.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/Errc.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileOutputBuffer.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/VersionTuple.h"
#include "llvm/Support/WithColor.h"
#include "llvm/Support/YAMLTraits.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/TextAPI/InterfaceFile.h"
#include "llvm/TextAPI/TextAPIReader.h"
#include "llvm/TextAPI/TextAPIWriter.h"
#include <set>
#include <string>
#include <vector>

using namespace llvm;
using namespace llvm::yaml;
using namespace llvm::MachO;
using namespace llvm::ifs;

#define DEBUG_TYPE "llvm-ifs"

namespace {
const VersionTuple IfsVersionCurrent(3, 0);

enum class FileFormat { IFS, ELF, TBD };
} // end anonymous namespace

using namespace llvm::opt;
enum ID {
  OPT_INVALID = 0, // This is not an option ID.
#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM,  \
               HELPTEXT, METAVAR, VALUES)                                      \
  OPT_##ID,
#include "Opts.inc"
#undef OPTION
};

#define PREFIX(NAME, VALUE) const char *const NAME[] = VALUE;
#include "Opts.inc"
#undef PREFIX

const opt::OptTable::Info InfoTable[] = {
#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM,  \
               HELPTEXT, METAVAR, VALUES)                                      \
  {                                                                            \
      PREFIX,      NAME,      HELPTEXT,                                        \
      METAVAR,     OPT_##ID,  opt::Option::KIND##Class,                        \
      PARAM,       FLAGS,     OPT_##GROUP,                                     \
      OPT_##ALIAS, ALIASARGS, VALUES},
#include "Opts.inc"
#undef OPTION
};

class IFSOptTable : public opt::OptTable {
public:
  IFSOptTable() : OptTable(InfoTable) { setGroupedShortOptions(true); }
};

struct DriverConfig {
  std::vector<std::string> InputFilePaths;

  Optional<FileFormat> InputFormat;
  Optional<FileFormat> OutputFormat;

  Optional<std::string> HintIfsTarget;
  Optional<std::string> OptTargetTriple;
  Optional<IFSArch> OverrideArch;
  Optional<IFSBitWidthType> OverrideBitWidth;
  Optional<IFSEndiannessType> OverrideEndianness;

  bool StripIfsArch = false;
  bool StripIfsBitwidth = false;
  bool StripIfsEndianness = false;
  bool StripIfsTarget = false;
  bool StripNeeded = false;
  bool StripSize = false;
  bool StripUndefined = false;

  std::vector<std::string> Exclude;

  Optional<std::string> SoName;

  Optional<std::string> Output;
  Optional<std::string> OutputElf;
  Optional<std::string> OutputIfs;
  Optional<std::string> OutputTbd;

  bool WriteIfChanged = false;
};

static std::string getTypeName(IFSSymbolType Type) {
  switch (Type) {
  case IFSSymbolType::NoType:
    return "NoType";
  case IFSSymbolType::Func:
    return "Func";
  case IFSSymbolType::Object:
    return "Object";
  case IFSSymbolType::TLS:
    return "TLS";
  case IFSSymbolType::Unknown:
    return "Unknown";
  }
  llvm_unreachable("Unexpected ifs symbol type.");
}

static Expected<std::unique_ptr<IFSStub>>
readInputFile(Optional<FileFormat> &InputFormat, StringRef FilePath) {
  // Read in file.
  ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrError =
      MemoryBuffer::getFileOrSTDIN(FilePath, /*IsText=*/true);
  if (!BufOrError)
    return createStringError(BufOrError.getError(), "Could not open `%s`",
                             FilePath.data());

  std::unique_ptr<MemoryBuffer> FileReadBuffer = std::move(*BufOrError);
  ErrorCollector EC(/*UseFatalErrors=*/false);

  // First try to read as a binary (fails fast if not binary).
  if (!InputFormat || *InputFormat == FileFormat::ELF) {
    Expected<std::unique_ptr<IFSStub>> StubFromELF =
        readELFFile(FileReadBuffer->getMemBufferRef());
    if (StubFromELF) {
      InputFormat = FileFormat::ELF;
      (*StubFromELF)->IfsVersion = IfsVersionCurrent;
      return std::move(*StubFromELF);
    }
    EC.addError(StubFromELF.takeError(), "BinaryRead");
  }

  // Fall back to reading as a ifs.
  if (!InputFormat || *InputFormat == FileFormat::IFS) {
    Expected<std::unique_ptr<IFSStub>> StubFromIFS =
        readIFSFromBuffer(FileReadBuffer->getBuffer());
    if (StubFromIFS) {
      InputFormat = FileFormat::IFS;
      if ((*StubFromIFS)->IfsVersion > IfsVersionCurrent)
        EC.addError(
            createStringError(errc::not_supported,
                              "IFS version " +
                                  (*StubFromIFS)->IfsVersion.getAsString() +
                                  " is unsupported."),
            "ReadInputFile");
      else
        return std::move(*StubFromIFS);
    } else {
      EC.addError(StubFromIFS.takeError(), "YamlParse");
    }
  }

  // If both readers fail, build a new error that includes all information.
  EC.addError(createStringError(errc::not_supported,
                                "No file readers succeeded reading `%s` "
                                "(unsupported/malformed file?)",
                                FilePath.data()),
              "ReadInputFile");
  EC.escalateToFatal();
  return EC.makeError();
}

static int writeTbdStub(const Triple &T, const std::vector<IFSSymbol> &Symbols,
                        const StringRef Format, raw_ostream &Out) {

  auto PlatformTypeOrError =
      [](const llvm::Triple &T) -> llvm::Expected<llvm::MachO::PlatformType> {
    if (T.isMacOSX())
      return llvm::MachO::PLATFORM_MACOS;
    if (T.isTvOS())
      return llvm::MachO::PLATFORM_TVOS;
    if (T.isWatchOS())
      return llvm::MachO::PLATFORM_WATCHOS;
    // Note: put isiOS last because tvOS and watchOS are also iOS according
    // to the Triple.
    if (T.isiOS())
      return llvm::MachO::PLATFORM_IOS;

    return createStringError(errc::not_supported, "Invalid Platform.\n");
  }(T);

  if (!PlatformTypeOrError)
    return -1;

  PlatformType Plat = PlatformTypeOrError.get();
  TargetList Targets({Target(llvm::MachO::mapToArchitecture(T), Plat)});

  InterfaceFile File;
  File.setFileType(FileType::TBD_V3); // Only supporting v3 for now.
  File.addTargets(Targets);

  for (const auto &Symbol : Symbols) {
    auto Name = Symbol.Name;
    auto Kind = SymbolKind::GlobalSymbol;
    switch (Symbol.Type) {
    default:
    case IFSSymbolType::NoType:
      Kind = SymbolKind::GlobalSymbol;
      break;
    case IFSSymbolType::Object:
      Kind = SymbolKind::GlobalSymbol;
      break;
    case IFSSymbolType::Func:
      Kind = SymbolKind::GlobalSymbol;
      break;
    }
    if (Symbol.Weak)
      File.addSymbol(Kind, Name, Targets, SymbolFlags::WeakDefined);
    else
      File.addSymbol(Kind, Name, Targets);
  }

  SmallString<4096> Buffer;
  raw_svector_ostream OS(Buffer);
  if (Error Result = TextAPIWriter::writeToStream(OS, File))
    return -1;
  Out << OS.str();
  return 0;
}

static void fatalError(Error Err) {
  WithColor::defaultErrorHandler(std::move(Err));
  exit(1);
}

static void fatalError(Twine T) {
  WithColor::error() << T.str() << '\n';
  exit(1);
}

/// writeIFS() writes a Text-Based ELF stub to a file using the latest version
/// of the YAML parser.
static Error writeIFS(StringRef FilePath, IFSStub &Stub, bool WriteIfChanged) {
  // Write IFS to memory first.
  std::string IFSStr;
  raw_string_ostream OutStr(IFSStr);
  Error YAMLErr = writeIFSToOutputStream(OutStr, Stub);
  if (YAMLErr)
    return YAMLErr;
  OutStr.flush();

  if (WriteIfChanged) {
    if (ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrError =
            MemoryBuffer::getFile(FilePath)) {
      // Compare IFS output with the existing IFS file. If unchanged, avoid
      // changing the file.
      if ((*BufOrError)->getBuffer() == IFSStr)
        return Error::success();
    }
  }
  // Open IFS file for writing.
  std::error_code SysErr;
  raw_fd_ostream Out(FilePath, SysErr);
  if (SysErr)
    return createStringError(SysErr, "Couldn't open `%s` for writing",
                             FilePath.data());
  Out << IFSStr;
  return Error::success();
}

static DriverConfig parseArgs(int argc, char *const *argv) {
  BumpPtrAllocator A;
  StringSaver Saver(A);
  IFSOptTable Tbl;
  StringRef ToolName = argv[0];
  llvm::opt::InputArgList Args = Tbl.parseArgs(
      argc, argv, OPT_UNKNOWN, Saver, [&](StringRef Msg) { fatalError(Msg); });
  if (Args.hasArg(OPT_help)) {
    Tbl.printHelp(llvm::outs(),
                  (Twine(ToolName) + " <input_file> <output_file> [options]")
                      .str()
                      .c_str(),
                  "shared object stubbing tool");
    std::exit(0);
  }
  if (Args.hasArg(OPT_version)) {
    llvm::outs() << ToolName << '\n';
    cl::PrintVersionMessage();
    std::exit(0);
  }

  DriverConfig Config;
  for (const opt::Arg *A : Args.filtered(OPT_INPUT))
    Config.InputFilePaths.push_back(A->getValue());
  if (const opt::Arg *A = Args.getLastArg(OPT_input_format_EQ)) {
    Config.InputFormat = StringSwitch<Optional<FileFormat>>(A->getValue())
                             .Case("IFS", FileFormat::IFS)
                             .Case("ELF", FileFormat::ELF)
                             .Default(None);
    if (!Config.InputFormat)
      fatalError(Twine("invalid argument '") + A->getValue());
  }

  auto OptionNotFound = [ToolName](StringRef FlagName, StringRef OptionName) {
    fatalError(Twine(ToolName) + ": for the " + FlagName +
               " option: Cannot find option named '" + OptionName + "'!");
  };
  if (const opt::Arg *A = Args.getLastArg(OPT_output_format_EQ)) {
    Config.OutputFormat = StringSwitch<Optional<FileFormat>>(A->getValue())
                              .Case("IFS", FileFormat::IFS)
                              .Case("ELF", FileFormat::ELF)
                              .Case("TBD", FileFormat::TBD)
                              .Default(None);
    if (!Config.OutputFormat)
      OptionNotFound("--output-format", A->getValue());
  }
  if (const opt::Arg *A = Args.getLastArg(OPT_arch_EQ))
    Config.OverrideArch = ELF::convertArchNameToEMachine(A->getValue());

  if (const opt::Arg *A = Args.getLastArg(OPT_bitwidth_EQ)) {
    size_t Width;
    llvm::StringRef S(A->getValue());
    if (!S.getAsInteger<size_t>(10, Width) || Width == 64 || Width == 32)
      Config.OverrideBitWidth =
          Width == 64 ? IFSBitWidthType::IFS64 : IFSBitWidthType::IFS32;
    else
      OptionNotFound("--bitwidth", A->getValue());
  }
  if (const opt::Arg *A = Args.getLastArg(OPT_endianness_EQ)) {
    Config.OverrideEndianness =
        StringSwitch<Optional<IFSEndiannessType>>(A->getValue())
            .Case("little", IFSEndiannessType::Little)
            .Case("big", IFSEndiannessType::Big)
            .Default(None);
    if (!Config.OverrideEndianness)
      OptionNotFound("--endianness", A->getValue());
  }
  if (const opt::Arg *A = Args.getLastArg(OPT_target_EQ))
    Config.OptTargetTriple = A->getValue();
  if (const opt::Arg *A = Args.getLastArg(OPT_hint_ifs_target_EQ))
    Config.HintIfsTarget = A->getValue();

  Config.StripIfsArch = Args.hasArg(OPT_strip_ifs_arch);
  Config.StripIfsBitwidth = Args.hasArg(OPT_strip_ifs_bitwidth);
  Config.StripIfsEndianness = Args.hasArg(OPT_strip_ifs_endianness);
  Config.StripIfsTarget = Args.hasArg(OPT_strip_ifs_target);
  Config.StripUndefined = Args.hasArg(OPT_strip_undefined);
  Config.StripNeeded = Args.hasArg(OPT_strip_needed);
  Config.StripSize = Args.hasArg(OPT_strip_size);

  for (const opt::Arg *A : Args.filtered(OPT_exclude_EQ))
    Config.Exclude.push_back(A->getValue());
  if (const opt::Arg *A = Args.getLastArg(OPT_soname_EQ))
    Config.SoName = A->getValue();
  if (const opt::Arg *A = Args.getLastArg(OPT_output_EQ))
    Config.Output = A->getValue();
  if (const opt::Arg *A = Args.getLastArg(OPT_output_elf_EQ))
    Config.OutputElf = A->getValue();
  if (const opt::Arg *A = Args.getLastArg(OPT_output_ifs_EQ))
    Config.OutputIfs = A->getValue();
  if (const opt::Arg *A = Args.getLastArg(OPT_output_tbd_EQ))
    Config.OutputTbd = A->getValue();
  Config.WriteIfChanged = Args.hasArg(OPT_write_if_changed);
  return Config;
}

int main(int argc, char *argv[]) {
  DriverConfig Config = parseArgs(argc, argv);

  if (Config.InputFilePaths.empty())
    Config.InputFilePaths.push_back("-");

  // If input files are more than one, they can only be IFS files.
  if (Config.InputFilePaths.size() > 1)
    Config.InputFormat = FileFormat::IFS;

  // Attempt to merge input.
  IFSStub Stub;
  std::map<std::string, IFSSymbol> SymbolMap;
  std::string PreviousInputFilePath;
  for (const std::string &InputFilePath : Config.InputFilePaths) {
    Expected<std::unique_ptr<IFSStub>> StubOrErr =
        readInputFile(Config.InputFormat, InputFilePath);
    if (!StubOrErr)
      fatalError(StubOrErr.takeError());

    std::unique_ptr<IFSStub> TargetStub = std::move(StubOrErr.get());
    if (PreviousInputFilePath.empty()) {
      Stub.IfsVersion = TargetStub->IfsVersion;
      Stub.Target = TargetStub->Target;
      Stub.SoName = TargetStub->SoName;
      Stub.NeededLibs = TargetStub->NeededLibs;
    } else {
      if (Stub.IfsVersion != TargetStub->IfsVersion) {
        if (Stub.IfsVersion.getMajor() != IfsVersionCurrent.getMajor()) {
          WithColor::error()
              << "Interface Stub: IfsVersion Mismatch."
              << "\nFilenames: " << PreviousInputFilePath << " "
              << InputFilePath << "\nIfsVersion Values: " << Stub.IfsVersion
              << " " << TargetStub->IfsVersion << "\n";
          return -1;
        }
        if (TargetStub->IfsVersion > Stub.IfsVersion)
          Stub.IfsVersion = TargetStub->IfsVersion;
      }
      if (Stub.Target != TargetStub->Target && !TargetStub->Target.empty()) {
        WithColor::error() << "Interface Stub: Target Mismatch."
                           << "\nFilenames: " << PreviousInputFilePath << " "
                           << InputFilePath;
        return -1;
      }
      if (Stub.SoName != TargetStub->SoName) {
        WithColor::error() << "Interface Stub: SoName Mismatch."
                           << "\nFilenames: " << PreviousInputFilePath << " "
                           << InputFilePath
                           << "\nSoName Values: " << Stub.SoName << " "
                           << TargetStub->SoName << "\n";
        return -1;
      }
      if (Stub.NeededLibs != TargetStub->NeededLibs) {
        WithColor::error() << "Interface Stub: NeededLibs Mismatch."
                           << "\nFilenames: " << PreviousInputFilePath << " "
                           << InputFilePath << "\n";
        return -1;
      }
    }

    for (auto Symbol : TargetStub->Symbols) {
      auto SI = SymbolMap.find(Symbol.Name);
      if (SI == SymbolMap.end()) {
        SymbolMap.insert(
            std::pair<std::string, IFSSymbol>(Symbol.Name, Symbol));
        continue;
      }

      assert(Symbol.Name == SI->second.Name && "Symbol Names Must Match.");

      // Check conflicts:
      if (Symbol.Type != SI->second.Type) {
        WithColor::error() << "Interface Stub: Type Mismatch for "
                           << Symbol.Name << ".\nFilename: " << InputFilePath
                           << "\nType Values: " << getTypeName(SI->second.Type)
                           << " " << getTypeName(Symbol.Type) << "\n";

        return -1;
      }
      if (Symbol.Size != SI->second.Size) {
        WithColor::error() << "Interface Stub: Size Mismatch for "
                           << Symbol.Name << ".\nFilename: " << InputFilePath
                           << "\nSize Values: " << SI->second.Size << " "
                           << Symbol.Size << "\n";

        return -1;
      }
      if (Symbol.Weak != SI->second.Weak) {
        Symbol.Weak = false;
        continue;
      }
      // TODO: Not checking Warning. Will be dropped.
    }

    PreviousInputFilePath = InputFilePath;
  }

  if (Stub.IfsVersion != IfsVersionCurrent)
    if (Stub.IfsVersion.getMajor() != IfsVersionCurrent.getMajor()) {
      WithColor::error() << "Interface Stub: Bad IfsVersion: "
                         << Stub.IfsVersion << ", llvm-ifs supported version: "
                         << IfsVersionCurrent << ".\n";
      return -1;
    }

  for (auto &Entry : SymbolMap)
    Stub.Symbols.push_back(Entry.second);

  // Change SoName before emitting stubs.
  if (Config.SoName)
    Stub.SoName = *Config.SoName;

  Error OverrideError =
      overrideIFSTarget(Stub, Config.OverrideArch, Config.OverrideEndianness,
                        Config.OverrideBitWidth, Config.OptTargetTriple);
  if (OverrideError)
    fatalError(std::move(OverrideError));

  if (Config.StripNeeded)
    Stub.NeededLibs.clear();

  if (Error E = filterIFSSyms(Stub, Config.StripUndefined, Config.Exclude))
    fatalError(std::move(E));

  if (Config.StripSize)
    for (IFSSymbol &Sym : Stub.Symbols)
      Sym.Size.reset();

  if (!Config.OutputElf && !Config.OutputIfs && !Config.OutputTbd) {
    if (!Config.OutputFormat) {
      WithColor::error() << "at least one output should be specified.";
      return -1;
    }
  } else if (Config.OutputFormat) {
    WithColor::error() << "'--output-format' cannot be used with "
                          "'--output-{FILE_FORMAT}' options at the same time";
    return -1;
  }
  if (Config.OutputFormat) {
    // TODO: Remove OutputFormat flag in the next revision.
    WithColor::warning() << "--output-format option is deprecated, please use "
                            "--output-{FILE_FORMAT} options instead\n";
    switch (*Config.OutputFormat) {
    case FileFormat::TBD: {
      std::error_code SysErr;
      raw_fd_ostream Out(*Config.Output, SysErr);
      if (SysErr) {
        WithColor::error() << "Couldn't open " << *Config.Output
                           << " for writing.\n";
        return -1;
      }
      if (!Stub.Target.Triple) {
        WithColor::error()
            << "Triple should be defined when output format is TBD";
        return -1;
      }
      return writeTbdStub(llvm::Triple(Stub.Target.Triple.value()),
                          Stub.Symbols, "TBD", Out);
    }
    case FileFormat::IFS: {
      Stub.IfsVersion = IfsVersionCurrent;
      if (Config.InputFormat.value() == FileFormat::ELF &&
          Config.HintIfsTarget) {
        std::error_code HintEC(1, std::generic_category());
        IFSTarget HintTarget = parseTriple(*Config.HintIfsTarget);
        if (Stub.Target.Arch.value() != HintTarget.Arch.value())
          fatalError(make_error<StringError>(
              "Triple hint does not match the actual architecture", HintEC));
        if (Stub.Target.Endianness.value() != HintTarget.Endianness.value())
          fatalError(make_error<StringError>(
              "Triple hint does not match the actual endianness", HintEC));
        if (Stub.Target.BitWidth.value() != HintTarget.BitWidth.value())
          fatalError(make_error<StringError>(
              "Triple hint does not match the actual bit width", HintEC));

        stripIFSTarget(Stub, true, false, false, false);
        Stub.Target.Triple = Config.HintIfsTarget.value();
      } else {
        stripIFSTarget(Stub, Config.StripIfsTarget, Config.StripIfsArch,
                       Config.StripIfsEndianness, Config.StripIfsBitwidth);
      }
      Error IFSWriteError =
          writeIFS(Config.Output.value(), Stub, Config.WriteIfChanged);
      if (IFSWriteError)
        fatalError(std::move(IFSWriteError));
      break;
    }
    case FileFormat::ELF: {
      Error TargetError = validateIFSTarget(Stub, true);
      if (TargetError)
        fatalError(std::move(TargetError));
      Error BinaryWriteError =
          writeBinaryStub(*Config.Output, Stub, Config.WriteIfChanged);
      if (BinaryWriteError)
        fatalError(std::move(BinaryWriteError));
      break;
    }
    }
  } else {
    // Check if output path for individual format.
    if (Config.OutputElf) {
      Error TargetError = validateIFSTarget(Stub, true);
      if (TargetError)
        fatalError(std::move(TargetError));
      Error BinaryWriteError =
          writeBinaryStub(*Config.OutputElf, Stub, Config.WriteIfChanged);
      if (BinaryWriteError)
        fatalError(std::move(BinaryWriteError));
    }
    if (Config.OutputIfs) {
      Stub.IfsVersion = IfsVersionCurrent;
      if (Config.InputFormat.value() == FileFormat::ELF &&
          Config.HintIfsTarget) {
        std::error_code HintEC(1, std::generic_category());
        IFSTarget HintTarget = parseTriple(*Config.HintIfsTarget);
        if (Stub.Target.Arch.value() != HintTarget.Arch.value())
          fatalError(make_error<StringError>(
              "Triple hint does not match the actual architecture", HintEC));
        if (Stub.Target.Endianness.value() != HintTarget.Endianness.value())
          fatalError(make_error<StringError>(
              "Triple hint does not match the actual endianness", HintEC));
        if (Stub.Target.BitWidth.value() != HintTarget.BitWidth.value())
          fatalError(make_error<StringError>(
              "Triple hint does not match the actual bit width", HintEC));

        stripIFSTarget(Stub, true, false, false, false);
        Stub.Target.Triple = Config.HintIfsTarget.value();
      } else {
        stripIFSTarget(Stub, Config.StripIfsTarget, Config.StripIfsArch,
                       Config.StripIfsEndianness, Config.StripIfsBitwidth);
      }
      Error IFSWriteError =
          writeIFS(Config.OutputIfs.value(), Stub, Config.WriteIfChanged);
      if (IFSWriteError)
        fatalError(std::move(IFSWriteError));
    }
    if (Config.OutputTbd) {
      std::error_code SysErr;
      raw_fd_ostream Out(*Config.OutputTbd, SysErr);
      if (SysErr) {
        WithColor::error() << "Couldn't open " << *Config.OutputTbd
                           << " for writing.\n";
        return -1;
      }
      if (!Stub.Target.Triple) {
        WithColor::error()
            << "Triple should be defined when output format is TBD";
        return -1;
      }
      return writeTbdStub(llvm::Triple(Stub.Target.Triple.value()),
                          Stub.Symbols, "TBD", Out);
    }
  }
  return 0;
}