brintos

brintos / llvm-project-archived public Read only

0
0
Text · 18.7 KiB · 7a8cad5 Raw
582 lines · cpp
1//===-- llvm-readtapi.cpp - tapi file reader and transformer -----*- C++-*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9// This file defines the command-line driver for llvm-readtapi.10//11//===----------------------------------------------------------------------===//12#include "DiffEngine.h"13#include "llvm/BinaryFormat/Magic.h"14#include "llvm/Option/Arg.h"15#include "llvm/Option/ArgList.h"16#include "llvm/Option/Option.h"17#include "llvm/Support/CommandLine.h"18#include "llvm/Support/Error.h"19#include "llvm/Support/FileSystem.h"20#include "llvm/Support/InitLLVM.h"21#include "llvm/Support/MemoryBuffer.h"22#include "llvm/Support/Path.h"23#include "llvm/Support/raw_ostream.h"24#include "llvm/TextAPI/DylibReader.h"25#include "llvm/TextAPI/TextAPIError.h"26#include "llvm/TextAPI/TextAPIReader.h"27#include "llvm/TextAPI/TextAPIWriter.h"28#include "llvm/TextAPI/Utils.h"29#include <cstdlib>30 31#if !defined(_MSC_VER) && !defined(__MINGW32__)32#include <unistd.h>33#endif34 35using namespace llvm;36using namespace MachO;37using namespace object;38 39namespace {40using namespace llvm::opt;41enum ID {42  OPT_INVALID = 0, // This is not an option ID.43#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),44#include "TapiOpts.inc"45#undef OPTION46};47 48#define OPTTABLE_STR_TABLE_CODE49#include "TapiOpts.inc"50#undef OPTTABLE_STR_TABLE_CODE51 52#define OPTTABLE_PREFIXES_TABLE_CODE53#include "TapiOpts.inc"54#undef OPTTABLE_PREFIXES_TABLE_CODE55 56static constexpr opt::OptTable::Info InfoTable[] = {57#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),58#include "TapiOpts.inc"59#undef OPTION60};61 62class TAPIOptTable : public opt::GenericOptTable {63public:64  TAPIOptTable()65      : opt::GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable) {66    setGroupedShortOptions(true);67  }68};69 70struct StubOptions {71  bool DeleteInput = false;72  bool DeletePrivate = false;73  bool TraceLibs = false;74};75 76struct CompareOptions {77  ArchitectureSet ArchsToIgnore;78};79 80struct Context {81  std::vector<std::string> Inputs;82  StubOptions StubOpt;83  CompareOptions CmpOpt;84  std::unique_ptr<llvm::raw_fd_stream> OutStream;85  FileType WriteFT = FileType::TBD_V5;86  bool Compact = false;87  Architecture Arch = AK_unknown;88};89 90// Use unique exit code to differentiate failures not directly caused from91// TextAPI operations. This is used for wrapping `compare` operations in92// automation and scripting.93const int NON_TAPI_EXIT_CODE = 2;94const std::string TOOLNAME = "llvm-readtapi";95ExitOnError ExitOnErr;96} // anonymous namespace97 98// Handle error reporting in cases where `ExitOnError` is not used.99static void reportError(Twine Message, int ExitCode = EXIT_FAILURE) {100  errs() << TOOLNAME << ": error: " << Message << "\n";101  errs().flush();102  exit(ExitCode);103}104 105// Handle warnings.106static void reportWarning(Twine Message) {107  errs() << TOOLNAME << ": warning: " << Message << "\n";108}109 110/// Get what the symlink points to.111/// This is a no-op on windows as it references POSIX level apis.112static void read_link(const Twine &Path, SmallVectorImpl<char> &Output) {113#if !defined(_MSC_VER) && !defined(__MINGW32__)114  Output.clear();115  if (Path.isTriviallyEmpty())116    return;117 118  SmallString<PATH_MAX> Storage;119  auto P = Path.toNullTerminatedStringRef(Storage);120  SmallString<PATH_MAX> Result;121  ssize_t Len;122  if ((Len = ::readlink(P.data(), Result.data(), PATH_MAX)) == -1)123    reportError("unable to read symlink: " + Path);124  Result.resize_for_overwrite(Len);125  Output.swap(Result);126#else127  reportError("unable to read symlink on windows: " + Path);128#endif129}130 131static std::unique_ptr<InterfaceFile>132getInterfaceFile(const StringRef Filename, bool ResetBanner = true) {133  ExitOnErr.setBanner(TOOLNAME + ": error: '" + Filename.str() + "' ");134  ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr =135      MemoryBuffer::getFile(Filename, /*IsText=*/true);136  if (BufferOrErr.getError())137    ExitOnErr(errorCodeToError(BufferOrErr.getError()));138  auto Buffer = std::move(*BufferOrErr);139 140  std::unique_ptr<InterfaceFile> IF;141  switch (identify_magic(Buffer->getBuffer())) {142  case file_magic::macho_dynamically_linked_shared_lib:143  case file_magic::macho_dynamically_linked_shared_lib_stub:144  case file_magic::macho_universal_binary:145    IF = ExitOnErr(DylibReader::get(Buffer->getMemBufferRef()));146    break;147  case file_magic::tapi_file:148    IF = ExitOnErr(TextAPIReader::get(Buffer->getMemBufferRef()));149    break;150  default:151    reportError(Filename + ": unsupported file type");152  }153 154  if (ResetBanner)155    ExitOnErr.setBanner(TOOLNAME + ": error: ");156  return IF;157}158 159static bool handleCompareAction(const Context &Ctx) {160  if (Ctx.Inputs.size() != 2)161    reportError("compare only supports two input files",162                /*ExitCode=*/NON_TAPI_EXIT_CODE);163 164  // Override default exit code.165  ExitOnErr = ExitOnError(TOOLNAME + ": error: ",166                          /*DefaultErrorExitCode=*/NON_TAPI_EXIT_CODE);167  auto LeftIF = getInterfaceFile(Ctx.Inputs.front());168  auto RightIF = getInterfaceFile(Ctx.Inputs.at(1));169 170  // Remove all architectures to ignore before running comparison.171  auto removeArchFromIF = [](auto &IF, const ArchitectureSet &ArchSet,172                             const Architecture ArchToRemove) {173    if (!ArchSet.has(ArchToRemove))174      return;175    if (ArchSet.count() == 1)176      return;177    auto OutIF = IF->remove(ArchToRemove);178    if (!OutIF)179      ExitOnErr(OutIF.takeError());180    IF = std::move(*OutIF);181  };182 183  if (!Ctx.CmpOpt.ArchsToIgnore.empty()) {184    const ArchitectureSet LeftArchs = LeftIF->getArchitectures();185    const ArchitectureSet RightArchs = RightIF->getArchitectures();186    for (const auto Arch : Ctx.CmpOpt.ArchsToIgnore) {187      removeArchFromIF(LeftIF, LeftArchs, Arch);188      removeArchFromIF(RightIF, RightArchs, Arch);189    }190  }191 192  raw_ostream &OS = Ctx.OutStream ? *Ctx.OutStream : outs();193  return DiffEngine(LeftIF.get(), RightIF.get()).compareFiles(OS);194}195 196static bool handleWriteAction(const Context &Ctx,197                              std::unique_ptr<InterfaceFile> Out = nullptr) {198  if (!Out) {199    if (Ctx.Inputs.size() != 1)200      reportError("write only supports one input file");201    Out = getInterfaceFile(Ctx.Inputs.front());202  }203  raw_ostream &OS = Ctx.OutStream ? *Ctx.OutStream : outs();204  ExitOnErr(TextAPIWriter::writeToStream(OS, *Out, Ctx.WriteFT, Ctx.Compact));205  return EXIT_SUCCESS;206}207 208static bool handleMergeAction(const Context &Ctx) {209  if (Ctx.Inputs.size() < 2)210    reportError("merge requires at least two input files");211 212  std::unique_ptr<InterfaceFile> Out;213  for (StringRef FileName : Ctx.Inputs) {214    auto IF = getInterfaceFile(FileName);215    // On the first iteration copy the input file and skip merge.216    if (!Out) {217      Out = std::move(IF);218      continue;219    }220    Out = ExitOnErr(Out->merge(IF.get()));221  }222  return handleWriteAction(Ctx, std::move(Out));223}224 225static void stubifyImpl(std::unique_ptr<InterfaceFile> IF, Context &Ctx) {226  // TODO: Add inlining and magic merge support.227  if (Ctx.OutStream == nullptr) {228    std::error_code EC;229    assert(!IF->getPath().empty() && "Unknown output location");230    SmallString<PATH_MAX> OutputLoc = IF->getPath();231    replace_extension(OutputLoc, ".tbd");232    Ctx.OutStream = std::make_unique<llvm::raw_fd_stream>(OutputLoc, EC);233    if (EC)234      reportError("opening file '" + OutputLoc + ": " + EC.message());235  }236 237  handleWriteAction(Ctx, std::move(IF));238  // Clear out output stream after file has been written incase more files are239  // stubifed.240  Ctx.OutStream = nullptr;241}242 243static void stubifyDirectory(const StringRef InputPath, Context &Ctx) {244  assert(InputPath.back() != '/' && "Unexpected / at end of input path.");245  StringMap<std::vector<SymLink>> SymLinks;246  StringMap<std::unique_ptr<InterfaceFile>> Dylibs;247  StringMap<std::string> OriginalNames;248  std::set<std::pair<std::string, bool>> LibsToDelete;249 250  std::error_code EC;251  for (sys::fs::recursive_directory_iterator IT(InputPath, EC), IE; IT != IE;252       IT.increment(EC)) {253    if (EC == std::errc::no_such_file_or_directory) {254      reportWarning(IT->path() + ": " + EC.message());255      continue;256    }257    if (EC)258      reportError(IT->path() + ": " + EC.message());259 260    // Skip header directories (include/Headers/PrivateHeaders).261    StringRef Path = IT->path();262    if (sys::fs::is_directory(Path)) {263      const StringRef Stem = sys::path::stem(Path);264      if ((Stem == "include") || (Stem == "Headers") ||265          (Stem == "PrivateHeaders") || (Stem == "Modules")) {266        IT.no_push();267        continue;268      }269    }270 271    // Skip module files too.272    if (Path.ends_with(".map") || Path.ends_with(".modulemap"))273      continue;274 275    // Check if the entry is a symlink. We don't follow symlinks but we record276    // their content.277    bool IsSymLink;278    if (auto EC = sys::fs::is_symlink_file(Path, IsSymLink))279      reportError(Path + ": " + EC.message());280 281    if (IsSymLink) {282      IT.no_push();283 284      bool ShouldSkip;285      auto SymLinkEC = shouldSkipSymLink(Path, ShouldSkip);286 287      // If symlink is broken, for some reason, we should continue288      // trying to repair it before quitting.289      if (!SymLinkEC && ShouldSkip)290        continue;291 292      if (Ctx.StubOpt.DeletePrivate &&293          isPrivateLibrary(Path.drop_front(InputPath.size()), true)) {294        LibsToDelete.emplace(Path, false);295        continue;296      }297 298      SmallString<PATH_MAX> SymPath;299      read_link(Path, SymPath);300      // Sometimes there are broken symlinks that are absolute paths, which are301      // invalid during build time, but would be correct during runtime. In the302      // case of an absolute path we should check first if the path exists with303      // the known locations as prefix.304      SmallString<PATH_MAX> LinkSrc = Path;305      SmallString<PATH_MAX> LinkTarget;306      if (sys::path::is_absolute(SymPath)) {307        LinkTarget = InputPath;308        sys::path::append(LinkTarget, SymPath);309 310        // TODO: Investigate supporting a file manager for file system accesses.311        if (sys::fs::exists(LinkTarget)) {312          // Convert the absolute path to an relative path.313          if (auto ec = MachO::make_relative(LinkSrc, LinkTarget, SymPath))314            reportError(LinkTarget + ": " + EC.message());315        } else if (!sys::fs::exists(SymPath)) {316          reportWarning("ignoring broken symlink: " + Path);317          continue;318        } else {319          LinkTarget = SymPath;320        }321      } else {322        LinkTarget = LinkSrc;323        sys::path::remove_filename(LinkTarget);324        sys::path::append(LinkTarget, SymPath);325      }326 327      // For Apple SDKs, the symlink src is guaranteed to be a canonical path328      // because we don't follow symlinks when scanning. The symlink target is329      // constructed from the symlink path and needs to be canonicalized.330      if (auto ec = sys::fs::real_path(Twine(LinkTarget), LinkTarget)) {331        reportWarning(LinkTarget + ": " + ec.message());332        continue;333      }334 335      SymLinks[LinkTarget.c_str()].emplace_back(LinkSrc.str(),336                                                std::string(SymPath.str()));337 338      continue;339    }340 341    bool IsDirectory = false;342    if (auto EC = sys::fs::is_directory(Path, IsDirectory))343      reportError(Path + ": " + EC.message());344    if (IsDirectory)345      continue;346 347    if (Ctx.StubOpt.DeletePrivate &&348        isPrivateLibrary(Path.drop_front(InputPath.size()))) {349      IT.no_push();350      LibsToDelete.emplace(Path, false);351      continue;352    }353    auto IF = getInterfaceFile(Path);354    if (Ctx.StubOpt.TraceLibs)355      errs() << Path << "\n";356 357    // Normalize path for map lookup by removing the extension.358    SmallString<PATH_MAX> NormalizedPath(Path);359    replace_extension(NormalizedPath, "");360 361    auto [It, Inserted] = Dylibs.try_emplace(NormalizedPath.str());362 363    if ((IF->getFileType() == FileType::MachO_DynamicLibrary) ||364        (IF->getFileType() == FileType::MachO_DynamicLibrary_Stub)) {365      OriginalNames[NormalizedPath.c_str()] = IF->getPath();366 367      // Don't add this MachO dynamic library because we already have a368      // text-based stub recorded for this path.369      if (!Inserted)370        continue;371    }372 373    It->second = std::move(IF);374  }375 376  for (auto &Lib : Dylibs) {377    auto &Dylib = Lib.second;378    // Get the original file name.379    SmallString<PATH_MAX> NormalizedPath(Dylib->getPath());380    stubifyImpl(std::move(Dylib), Ctx);381 382    replace_extension(NormalizedPath, "");383    auto Found = OriginalNames.find(NormalizedPath.c_str());384    if (Found == OriginalNames.end())385      continue;386 387    if (Ctx.StubOpt.DeleteInput)388      LibsToDelete.emplace(Found->second, true);389 390    // Don't allow for more than 20 levels of symlinks when searching for391    // libraries to stubify.392    StringRef LibToCheck = Found->second;393    for (int i = 0; i < 20; ++i) {394      auto LinkIt = SymLinks.find(LibToCheck);395      if (LinkIt != SymLinks.end()) {396        for (auto &SymInfo : LinkIt->second) {397          SmallString<PATH_MAX> LinkSrc(SymInfo.SrcPath);398          SmallString<PATH_MAX> LinkTarget(SymInfo.LinkContent);399          replace_extension(LinkSrc, "tbd");400          replace_extension(LinkTarget, "tbd");401 402          if (auto EC = sys::fs::remove(LinkSrc))403            reportError(LinkSrc + " : " + EC.message());404 405          if (auto EC = sys::fs::create_link(LinkTarget, LinkSrc))406            reportError(LinkTarget + " : " + EC.message());407 408          if (Ctx.StubOpt.DeleteInput)409            LibsToDelete.emplace(SymInfo.SrcPath, true);410 411          LibToCheck = SymInfo.SrcPath;412        }413      } else414        break;415    }416  }417 418  // Recursively delete the directories. This will abort when they are not empty419  // or we reach the root of the SDK.420  for (const auto &[LibPath, IsInput] : LibsToDelete) {421    if (!IsInput && SymLinks.count(LibPath))422      continue;423 424    if (auto EC = sys::fs::remove(LibPath))425      reportError(LibPath + " : " + EC.message());426 427    std::error_code EC;428    auto Dir = sys::path::parent_path(LibPath);429    do {430      EC = sys::fs::remove(Dir);431      Dir = sys::path::parent_path(Dir);432      if (!Dir.starts_with(InputPath))433        break;434    } while (!EC);435  }436}437 438static bool handleStubifyAction(Context &Ctx) {439  if (Ctx.Inputs.empty())440    reportError("stubify requires at least one input file");441 442  if ((Ctx.Inputs.size() > 1) && (Ctx.OutStream != nullptr))443    reportError("cannot write multiple inputs into single output file");444 445  for (StringRef PathName : Ctx.Inputs) {446    bool IsDirectory = false;447    if (auto EC = sys::fs::is_directory(PathName, IsDirectory))448      reportError(PathName + ": " + EC.message());449 450    if (IsDirectory) {451      if (Ctx.OutStream != nullptr)452        reportError("cannot stubify directory'" + PathName +453                    "' into single output file");454      stubifyDirectory(PathName, Ctx);455      continue;456    }457 458    stubifyImpl(getInterfaceFile(PathName), Ctx);459    if (Ctx.StubOpt.DeleteInput)460      if (auto ec = sys::fs::remove(PathName))461        reportError("deleting file '" + PathName + ": " + ec.message());462  }463  return EXIT_SUCCESS;464}465 466using IFOperation =467    std::function<llvm::Expected<std::unique_ptr<InterfaceFile>>(468        const llvm::MachO::InterfaceFile &, Architecture)>;469static bool handleSingleFileAction(const Context &Ctx, const StringRef Action,470                                   IFOperation act) {471  if (Ctx.Inputs.size() != 1)472    reportError(Action + " only supports one input file");473  if (Ctx.Arch == AK_unknown)474    reportError(Action + " requires -arch <arch>");475 476  auto IF = getInterfaceFile(Ctx.Inputs.front(), /*ResetBanner=*/false);477  auto OutIF = act(*IF, Ctx.Arch);478  if (!OutIF)479    ExitOnErr(OutIF.takeError());480 481  return handleWriteAction(Ctx, std::move(*OutIF));482}483 484static void setStubOptions(opt::InputArgList &Args, StubOptions &Opt) {485  Opt.DeleteInput = Args.hasArg(OPT_delete_input);486  Opt.DeletePrivate = Args.hasArg(OPT_delete_private_libraries);487  Opt.TraceLibs = Args.hasArg(OPT_t);488}489 490int main(int Argc, char **Argv) {491  InitLLVM X(Argc, Argv);492  BumpPtrAllocator A;493  StringSaver Saver(A);494  TAPIOptTable Tbl;495  Context Ctx;496  ExitOnErr.setBanner(TOOLNAME + ": error:");497  opt::InputArgList Args = Tbl.parseArgs(498      Argc, Argv, OPT_UNKNOWN, Saver, [&](StringRef Msg) { reportError(Msg); });499  if (Args.hasArg(OPT_help)) {500    Tbl.printHelp(outs(),501                  "USAGE: llvm-readtapi <command> [-arch <architecture> "502                  "<options>]* <inputs> [-o "503                  "<output>]*",504                  "LLVM TAPI file reader and transformer");505    return EXIT_SUCCESS;506  }507 508  if (Args.hasArg(OPT_version)) {509    cl::PrintVersionMessage();510    return EXIT_SUCCESS;511  }512 513  for (opt::Arg *A : Args.filtered(OPT_INPUT))514    Ctx.Inputs.push_back(A->getValue());515 516  if (opt::Arg *A = Args.getLastArg(OPT_output_EQ)) {517    std::string OutputLoc = std::move(A->getValue());518    std::error_code EC;519    Ctx.OutStream = std::make_unique<llvm::raw_fd_stream>(OutputLoc, EC);520    if (EC)521      reportError("error opening the file '" + OutputLoc + EC.message(),522                  NON_TAPI_EXIT_CODE);523  }524 525  Ctx.Compact = Args.hasArg(OPT_compact);526 527  if (opt::Arg *A = Args.getLastArg(OPT_filetype_EQ)) {528    StringRef FT = A->getValue();529    Ctx.WriteFT = TextAPIWriter::parseFileType(FT);530    if (Ctx.WriteFT < FileType::TBD_V3)531      reportError("deprecated filetype '" + FT + "' is not supported to write");532    if (Ctx.WriteFT == FileType::Invalid)533      reportError("unsupported filetype '" + FT + "'");534  }535 536  auto SanitizeArch = [&](opt::Arg *A) {537    StringRef ArchStr = A->getValue();538    auto Arch = getArchitectureFromName(ArchStr);539    if (Arch == AK_unknown)540      reportError("unsupported architecture '" + ArchStr);541    return Arch;542  };543 544  if (opt::Arg *A = Args.getLastArg(OPT_arch_EQ))545    Ctx.Arch = SanitizeArch(A);546 547  for (opt::Arg *A : Args.filtered(OPT_ignore_arch_EQ))548    Ctx.CmpOpt.ArchsToIgnore.set(SanitizeArch(A));549 550  // Handle top level and exclusive operation.551  SmallVector<opt::Arg *, 1> ActionArgs(Args.filtered(OPT_action_group));552 553  if (ActionArgs.empty())554    // If no action specified, write out tapi file in requested format.555    return handleWriteAction(Ctx);556 557  if (ActionArgs.size() > 1) {558    std::string Buf;559    raw_string_ostream OS(Buf);560    OS << "only one of the following actions can be specified:";561    for (auto *Arg : ActionArgs)562      OS << " " << Arg->getSpelling();563    reportError(OS.str());564  }565 566  switch (ActionArgs.front()->getOption().getID()) {567  case OPT_compare:568    return handleCompareAction(Ctx);569  case OPT_merge:570    return handleMergeAction(Ctx);571  case OPT_extract:572    return handleSingleFileAction(Ctx, "extract", &InterfaceFile::extract);573  case OPT_remove:574    return handleSingleFileAction(Ctx, "remove", &InterfaceFile::remove);575  case OPT_stubify:576    setStubOptions(Args, Ctx.StubOpt);577    return handleStubifyAction(Ctx);578  }579 580  return EXIT_SUCCESS;581}582