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