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