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 OPTTABLE_STR_TABLE_CODE 49 #include "TapiOpts.inc" 50 #undef OPTTABLE_STR_TABLE_CODE 51 52 #define OPTTABLE_PREFIXES_TABLE_CODE 53 #include "TapiOpts.inc" 54 #undef OPTTABLE_PREFIXES_TABLE_CODE 55 56 static constexpr opt::OptTable::Info InfoTable[] = { 57 #define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__), 58 #include "TapiOpts.inc" 59 #undef OPTION 60 }; 61 62 class TAPIOptTable : public opt::GenericOptTable { 63 public: 64 TAPIOptTable() 65 : opt::GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable) { 66 setGroupedShortOptions(true); 67 } 68 }; 69 70 struct StubOptions { 71 bool DeleteInput = false; 72 bool DeletePrivate = false; 73 bool TraceLibs = false; 74 }; 75 76 struct CompareOptions { 77 ArchitectureSet ArchsToIgnore; 78 }; 79 80 struct 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 from 91 // TextAPI operations. This is used for wrapping `compare` operations in 92 // automation and scripting. 93 const int NON_TAPI_EXIT_CODE = 2; 94 const std::string TOOLNAME = "llvm-readtapi"; 95 ExitOnError ExitOnErr; 96 } // anonymous namespace 97 98 // Handle error reporting in cases where `ExitOnError` is not used. 99 static 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. 106 static 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. 112 static 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 #else 127 reportError("unable to read symlink on windows: " + Path); 128 #endif 129 } 130 131 static std::unique_ptr<InterfaceFile> 132 getInterfaceFile(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 159 static 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 196 static 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 208 static 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 225 static 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 are 239 // stubifed. 240 Ctx.OutStream = nullptr; 241 } 242 243 static 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 record 276 // 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 continue 288 // 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 are 301 // invalid during build time, but would be correct during runtime. In the 302 // case of an absolute path we should check first if the path exists with 303 // 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 path 328 // because we don't follow symlinks when scanning. The symlink target is 329 // 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 if ((IF->getFileType() == FileType::MachO_DynamicLibrary) || 362 (IF->getFileType() == FileType::MachO_DynamicLibrary_Stub)) { 363 OriginalNames[NormalizedPath.c_str()] = IF->getPath(); 364 365 // Don't add this MachO dynamic library because we already have a 366 // text-based stub recorded for this path. 367 if (Dylibs.count(NormalizedPath.c_str())) 368 continue; 369 } 370 371 Dylibs[NormalizedPath.c_str()] = std::move(IF); 372 } 373 374 for (auto &Lib : Dylibs) { 375 auto &Dylib = Lib.second; 376 // Get the original file name. 377 SmallString<PATH_MAX> NormalizedPath(Dylib->getPath()); 378 stubifyImpl(std::move(Dylib), Ctx); 379 380 replace_extension(NormalizedPath, ""); 381 auto Found = OriginalNames.find(NormalizedPath.c_str()); 382 if (Found == OriginalNames.end()) 383 continue; 384 385 if (Ctx.StubOpt.DeleteInput) 386 LibsToDelete.emplace(Found->second, true); 387 388 // Don't allow for more than 20 levels of symlinks when searching for 389 // libraries to stubify. 390 StringRef LibToCheck = Found->second; 391 for (int i = 0; i < 20; ++i) { 392 auto LinkIt = SymLinks.find(LibToCheck); 393 if (LinkIt != SymLinks.end()) { 394 for (auto &SymInfo : LinkIt->second) { 395 SmallString<PATH_MAX> LinkSrc(SymInfo.SrcPath); 396 SmallString<PATH_MAX> LinkTarget(SymInfo.LinkContent); 397 replace_extension(LinkSrc, "tbd"); 398 replace_extension(LinkTarget, "tbd"); 399 400 if (auto EC = sys::fs::remove(LinkSrc)) 401 reportError(LinkSrc + " : " + EC.message()); 402 403 if (auto EC = sys::fs::create_link(LinkTarget, LinkSrc)) 404 reportError(LinkTarget + " : " + EC.message()); 405 406 if (Ctx.StubOpt.DeleteInput) 407 LibsToDelete.emplace(SymInfo.SrcPath, true); 408 409 LibToCheck = SymInfo.SrcPath; 410 } 411 } else 412 break; 413 } 414 } 415 416 // Recursively delete the directories. This will abort when they are not empty 417 // or we reach the root of the SDK. 418 for (const auto &[LibPath, IsInput] : LibsToDelete) { 419 if (!IsInput && SymLinks.count(LibPath)) 420 continue; 421 422 if (auto EC = sys::fs::remove(LibPath)) 423 reportError(LibPath + " : " + EC.message()); 424 425 std::error_code EC; 426 auto Dir = sys::path::parent_path(LibPath); 427 do { 428 EC = sys::fs::remove(Dir); 429 Dir = sys::path::parent_path(Dir); 430 if (!Dir.starts_with(InputPath)) 431 break; 432 } while (!EC); 433 } 434 } 435 436 static bool handleStubifyAction(Context &Ctx) { 437 if (Ctx.Inputs.empty()) 438 reportError("stubify requires at least one input file"); 439 440 if ((Ctx.Inputs.size() > 1) && (Ctx.OutStream != nullptr)) 441 reportError("cannot write multiple inputs into single output file"); 442 443 for (StringRef PathName : Ctx.Inputs) { 444 bool IsDirectory = false; 445 if (auto EC = sys::fs::is_directory(PathName, IsDirectory)) 446 reportError(PathName + ": " + EC.message()); 447 448 if (IsDirectory) { 449 if (Ctx.OutStream != nullptr) 450 reportError("cannot stubify directory'" + PathName + 451 "' into single output file"); 452 stubifyDirectory(PathName, Ctx); 453 continue; 454 } 455 456 stubifyImpl(getInterfaceFile(PathName), Ctx); 457 if (Ctx.StubOpt.DeleteInput) 458 if (auto ec = sys::fs::remove(PathName)) 459 reportError("deleting file '" + PathName + ": " + ec.message()); 460 } 461 return EXIT_SUCCESS; 462 } 463 464 using IFOperation = 465 std::function<llvm::Expected<std::unique_ptr<InterfaceFile>>( 466 const llvm::MachO::InterfaceFile &, Architecture)>; 467 static bool handleSingleFileAction(const Context &Ctx, const StringRef Action, 468 IFOperation act) { 469 if (Ctx.Inputs.size() != 1) 470 reportError(Action + " only supports one input file"); 471 if (Ctx.Arch == AK_unknown) 472 reportError(Action + " requires -arch <arch>"); 473 474 auto IF = getInterfaceFile(Ctx.Inputs.front(), /*ResetBanner=*/false); 475 auto OutIF = act(*IF, Ctx.Arch); 476 if (!OutIF) 477 ExitOnErr(OutIF.takeError()); 478 479 return handleWriteAction(Ctx, std::move(*OutIF)); 480 } 481 482 static void setStubOptions(opt::InputArgList &Args, StubOptions &Opt) { 483 Opt.DeleteInput = Args.hasArg(OPT_delete_input); 484 Opt.DeletePrivate = Args.hasArg(OPT_delete_private_libraries); 485 Opt.TraceLibs = Args.hasArg(OPT_t); 486 } 487 488 int main(int Argc, char **Argv) { 489 InitLLVM X(Argc, Argv); 490 BumpPtrAllocator A; 491 StringSaver Saver(A); 492 TAPIOptTable Tbl; 493 Context Ctx; 494 ExitOnErr.setBanner(TOOLNAME + ": error:"); 495 opt::InputArgList Args = Tbl.parseArgs( 496 Argc, Argv, OPT_UNKNOWN, Saver, [&](StringRef Msg) { reportError(Msg); }); 497 if (Args.hasArg(OPT_help)) { 498 Tbl.printHelp(outs(), 499 "USAGE: llvm-readtapi <command> [-arch <architecture> " 500 "<options>]* <inputs> [-o " 501 "<output>]*", 502 "LLVM TAPI file reader and transformer"); 503 return EXIT_SUCCESS; 504 } 505 506 if (Args.hasArg(OPT_version)) { 507 cl::PrintVersionMessage(); 508 return EXIT_SUCCESS; 509 } 510 511 for (opt::Arg *A : Args.filtered(OPT_INPUT)) 512 Ctx.Inputs.push_back(A->getValue()); 513 514 if (opt::Arg *A = Args.getLastArg(OPT_output_EQ)) { 515 std::string OutputLoc = std::move(A->getValue()); 516 std::error_code EC; 517 Ctx.OutStream = std::make_unique<llvm::raw_fd_stream>(OutputLoc, EC); 518 if (EC) 519 reportError("error opening the file '" + OutputLoc + EC.message(), 520 NON_TAPI_EXIT_CODE); 521 } 522 523 Ctx.Compact = Args.hasArg(OPT_compact); 524 525 if (opt::Arg *A = Args.getLastArg(OPT_filetype_EQ)) { 526 StringRef FT = A->getValue(); 527 Ctx.WriteFT = TextAPIWriter::parseFileType(FT); 528 if (Ctx.WriteFT < FileType::TBD_V3) 529 reportError("deprecated filetype '" + FT + "' is not supported to write"); 530 if (Ctx.WriteFT == FileType::Invalid) 531 reportError("unsupported filetype '" + FT + "'"); 532 } 533 534 auto SanitizeArch = [&](opt::Arg *A) { 535 StringRef ArchStr = A->getValue(); 536 auto Arch = getArchitectureFromName(ArchStr); 537 if (Arch == AK_unknown) 538 reportError("unsupported architecture '" + ArchStr); 539 return Arch; 540 }; 541 542 if (opt::Arg *A = Args.getLastArg(OPT_arch_EQ)) 543 Ctx.Arch = SanitizeArch(A); 544 545 for (opt::Arg *A : Args.filtered(OPT_ignore_arch_EQ)) 546 Ctx.CmpOpt.ArchsToIgnore.set(SanitizeArch(A)); 547 548 // Handle top level and exclusive operation. 549 SmallVector<opt::Arg *, 1> ActionArgs(Args.filtered(OPT_action_group)); 550 551 if (ActionArgs.empty()) 552 // If no action specified, write out tapi file in requested format. 553 return handleWriteAction(Ctx); 554 555 if (ActionArgs.size() > 1) { 556 std::string Buf; 557 raw_string_ostream OS(Buf); 558 OS << "only one of the following actions can be specified:"; 559 for (auto *Arg : ActionArgs) 560 OS << " " << Arg->getSpelling(); 561 reportError(OS.str()); 562 } 563 564 switch (ActionArgs.front()->getOption().getID()) { 565 case OPT_compare: 566 return handleCompareAction(Ctx); 567 case OPT_merge: 568 return handleMergeAction(Ctx); 569 case OPT_extract: 570 return handleSingleFileAction(Ctx, "extract", &InterfaceFile::extract); 571 case OPT_remove: 572 return handleSingleFileAction(Ctx, "remove", &InterfaceFile::remove); 573 case OPT_stubify: 574 setStubOptions(Args, Ctx.StubOpt); 575 return handleStubifyAction(Ctx); 576 } 577 578 return EXIT_SUCCESS; 579 } 580