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 using namespace llvm; 32 using namespace MachO; 33 using namespace object; 34 35 #if !defined(PATH_MAX) 36 #define PATH_MAX 1024 37 #endif 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 }; 71 72 struct Context { 73 std::vector<std::string> Inputs; 74 std::unique_ptr<llvm::raw_fd_stream> OutStream; 75 FileType WriteFT = FileType::TBD_V5; 76 StubOptions StubOpt; 77 bool Compact = false; 78 Architecture Arch = AK_unknown; 79 }; 80 81 // Use unique exit code to differentiate failures not directly caused from 82 // TextAPI operations. This is used for wrapping `compare` operations in 83 // automation and scripting. 84 const int NON_TAPI_EXIT_CODE = 2; 85 const std::string TOOLNAME = "llvm-readtapi"; 86 ExitOnError ExitOnErr; 87 } // anonymous namespace 88 89 // Handle error reporting in cases where `ExitOnError` is not used. 90 static void reportError(Twine Message, int ExitCode = EXIT_FAILURE) { 91 errs() << TOOLNAME << ": error: " << Message << "\n"; 92 errs().flush(); 93 exit(ExitCode); 94 } 95 96 static std::unique_ptr<InterfaceFile> 97 getInterfaceFile(const StringRef Filename, bool ResetBanner = true) { 98 ExitOnErr.setBanner(TOOLNAME + ": error: '" + Filename.str() + "' "); 99 ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr = 100 MemoryBuffer::getFile(Filename); 101 if (BufferOrErr.getError()) 102 ExitOnErr(errorCodeToError(BufferOrErr.getError())); 103 auto Buffer = std::move(*BufferOrErr); 104 105 std::unique_ptr<InterfaceFile> IF; 106 switch (identify_magic(Buffer->getBuffer())) { 107 case file_magic::macho_dynamically_linked_shared_lib: 108 LLVM_FALLTHROUGH; 109 case file_magic::macho_dynamically_linked_shared_lib_stub: 110 LLVM_FALLTHROUGH; 111 case file_magic::macho_universal_binary: 112 IF = ExitOnErr(DylibReader::get(Buffer->getMemBufferRef())); 113 break; 114 case file_magic::tapi_file: 115 IF = ExitOnErr(TextAPIReader::get(Buffer->getMemBufferRef())); 116 break; 117 default: 118 reportError(Filename + ": unsupported file type"); 119 } 120 121 if (ResetBanner) 122 ExitOnErr.setBanner(TOOLNAME + ": error: "); 123 return IF; 124 } 125 126 static bool handleCompareAction(const Context &Ctx) { 127 if (Ctx.Inputs.size() != 2) 128 reportError("compare only supports two input files", 129 /*ExitCode=*/NON_TAPI_EXIT_CODE); 130 131 // Override default exit code. 132 ExitOnErr = ExitOnError(TOOLNAME + ": error: ", 133 /*DefaultErrorExitCode=*/NON_TAPI_EXIT_CODE); 134 auto LeftIF = getInterfaceFile(Ctx.Inputs.front()); 135 auto RightIF = getInterfaceFile(Ctx.Inputs.at(1)); 136 137 raw_ostream &OS = Ctx.OutStream ? *Ctx.OutStream : outs(); 138 return DiffEngine(LeftIF.get(), RightIF.get()).compareFiles(OS); 139 } 140 141 static bool handleWriteAction(const Context &Ctx, 142 std::unique_ptr<InterfaceFile> Out = nullptr) { 143 if (!Out) { 144 if (Ctx.Inputs.size() != 1) 145 reportError("write only supports one input file"); 146 Out = getInterfaceFile(Ctx.Inputs.front()); 147 } 148 raw_ostream &OS = Ctx.OutStream ? *Ctx.OutStream : outs(); 149 ExitOnErr(TextAPIWriter::writeToStream(OS, *Out, Ctx.WriteFT, Ctx.Compact)); 150 return EXIT_SUCCESS; 151 } 152 153 static bool handleMergeAction(const Context &Ctx) { 154 if (Ctx.Inputs.size() < 2) 155 reportError("merge requires at least two input files"); 156 157 std::unique_ptr<InterfaceFile> Out; 158 for (StringRef FileName : Ctx.Inputs) { 159 auto IF = getInterfaceFile(FileName); 160 // On the first iteration copy the input file and skip merge. 161 if (!Out) { 162 Out = std::move(IF); 163 continue; 164 } 165 Out = ExitOnErr(Out->merge(IF.get())); 166 } 167 return handleWriteAction(Ctx, std::move(Out)); 168 } 169 170 static bool handleStubifyAction(Context &Ctx) { 171 if (Ctx.Inputs.empty()) 172 reportError("stubify requires at least one input file"); 173 174 if ((Ctx.Inputs.size() > 1) && (Ctx.OutStream != nullptr)) 175 reportError("cannot write multiple inputs into single output file"); 176 177 for (StringRef FileName : Ctx.Inputs) { 178 auto IF = getInterfaceFile(FileName); 179 if (Ctx.StubOpt.DeleteInput) { 180 std::error_code EC; 181 SmallString<PATH_MAX> OutputLoc = FileName; 182 MachO::replace_extension(OutputLoc, ".tbd"); 183 Ctx.OutStream = std::make_unique<llvm::raw_fd_stream>(OutputLoc, EC); 184 if (EC) 185 reportError("opening file '" + OutputLoc + ": " + EC.message()); 186 if (auto Err = sys::fs::remove(FileName)) 187 reportError("deleting file '" + FileName + ": " + EC.message()); 188 } 189 handleWriteAction(Ctx, std::move(IF)); 190 } 191 return EXIT_SUCCESS; 192 } 193 194 using IFOperation = 195 std::function<llvm::Expected<std::unique_ptr<InterfaceFile>>( 196 const llvm::MachO::InterfaceFile &, Architecture)>; 197 static bool handleSingleFileAction(const Context &Ctx, const StringRef Action, 198 IFOperation act) { 199 if (Ctx.Inputs.size() != 1) 200 reportError(Action + " only supports one input file"); 201 if (Ctx.Arch == AK_unknown) 202 reportError(Action + " requires -arch <arch>"); 203 204 auto IF = getInterfaceFile(Ctx.Inputs.front(), /*ResetBanner=*/false); 205 auto OutIF = act(*IF, Ctx.Arch); 206 if (!OutIF) 207 ExitOnErr(OutIF.takeError()); 208 209 return handleWriteAction(Ctx, std::move(*OutIF)); 210 } 211 212 static void setStubOptions(opt::InputArgList &Args, StubOptions &Opt) { 213 Opt.DeleteInput = Args.hasArg(OPT_delete_input); 214 } 215 216 int main(int Argc, char **Argv) { 217 InitLLVM X(Argc, Argv); 218 BumpPtrAllocator A; 219 StringSaver Saver(A); 220 TAPIOptTable Tbl; 221 Context Ctx; 222 ExitOnErr.setBanner(TOOLNAME + ": error:"); 223 opt::InputArgList Args = Tbl.parseArgs( 224 Argc, Argv, OPT_UNKNOWN, Saver, [&](StringRef Msg) { reportError(Msg); }); 225 if (Args.hasArg(OPT_help)) { 226 Tbl.printHelp(outs(), 227 "USAGE: llvm-readtapi <command> [-arch <architecture> " 228 "<options>]* <inputs> [-o " 229 "<output>]*", 230 "LLVM TAPI file reader and transformer"); 231 return EXIT_SUCCESS; 232 } 233 234 if (Args.hasArg(OPT_version)) { 235 cl::PrintVersionMessage(); 236 return EXIT_SUCCESS; 237 } 238 239 // TODO: Add support for picking up libraries from directory input. 240 for (opt::Arg *A : Args.filtered(OPT_INPUT)) 241 Ctx.Inputs.push_back(A->getValue()); 242 243 if (opt::Arg *A = Args.getLastArg(OPT_output_EQ)) { 244 std::string OutputLoc = std::move(A->getValue()); 245 std::error_code EC; 246 Ctx.OutStream = std::make_unique<llvm::raw_fd_stream>(OutputLoc, EC); 247 if (EC) 248 reportError("error opening the file '" + OutputLoc + EC.message(), 249 NON_TAPI_EXIT_CODE); 250 } 251 252 Ctx.Compact = Args.hasArg(OPT_compact); 253 254 if (opt::Arg *A = Args.getLastArg(OPT_filetype_EQ)) { 255 StringRef FT = A->getValue(); 256 Ctx.WriteFT = TextAPIWriter::parseFileType(FT); 257 if (Ctx.WriteFT < FileType::TBD_V3) 258 reportError("deprecated filetype '" + FT + "' is not supported to write"); 259 if (Ctx.WriteFT == FileType::Invalid) 260 reportError("unsupported filetype '" + FT + "'"); 261 } 262 263 if (opt::Arg *A = Args.getLastArg(OPT_arch_EQ)) { 264 StringRef Arch = A->getValue(); 265 Ctx.Arch = getArchitectureFromName(Arch); 266 if (Ctx.Arch == AK_unknown) 267 reportError("unsupported architecture '" + Arch); 268 } 269 // Handle top level and exclusive operation. 270 SmallVector<opt::Arg *, 1> ActionArgs(Args.filtered(OPT_action_group)); 271 272 if (ActionArgs.empty()) 273 // If no action specified, write out tapi file in requested format. 274 return handleWriteAction(Ctx); 275 276 if (ActionArgs.size() > 1) { 277 std::string Buf; 278 raw_string_ostream OS(Buf); 279 OS << "only one of the following actions can be specified:"; 280 for (auto *Arg : ActionArgs) 281 OS << " " << Arg->getSpelling(); 282 reportError(OS.str()); 283 } 284 285 switch (ActionArgs.front()->getOption().getID()) { 286 case OPT_compare: 287 return handleCompareAction(Ctx); 288 case OPT_merge: 289 return handleMergeAction(Ctx); 290 case OPT_extract: 291 return handleSingleFileAction(Ctx, "extract", &InterfaceFile::extract); 292 case OPT_remove: 293 return handleSingleFileAction(Ctx, "remove", &InterfaceFile::remove); 294 case OPT_stubify: 295 setStubOptions(Args, Ctx.StubOpt); 296 return handleStubifyAction(Ctx); 297 } 298 299 return EXIT_SUCCESS; 300 } 301