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