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