1 //===-- cc1gen_reproducer_main.cpp - Clang reproducer generator ----------===// 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 is the entry point to the clang -cc1gen-reproducer functionality, which 10 // generates reproducers for invocations for clang-based tools. 11 // 12 //===----------------------------------------------------------------------===// 13 14 #include "clang/Basic/Diagnostic.h" 15 #include "clang/Basic/LLVM.h" 16 #include "clang/Driver/Compilation.h" 17 #include "clang/Driver/Driver.h" 18 #include "llvm/ADT/ArrayRef.h" 19 #include "llvm/ADT/STLExtras.h" 20 #include "llvm/Support/FileSystem.h" 21 #include "llvm/Support/LLVMDriver.h" 22 #include "llvm/Support/TargetSelect.h" 23 #include "llvm/Support/VirtualFileSystem.h" 24 #include "llvm/Support/YAMLTraits.h" 25 #include "llvm/Support/raw_ostream.h" 26 #include "llvm/TargetParser/Host.h" 27 #include <optional> 28 29 using namespace clang; 30 31 namespace { 32 33 struct UnsavedFileHash { 34 std::string Name; 35 std::string MD5; 36 }; 37 38 struct ClangInvocationInfo { 39 std::string Toolchain; 40 std::string LibclangOperation; 41 std::string LibclangOptions; 42 std::vector<std::string> Arguments; 43 std::vector<std::string> InvocationArguments; 44 std::vector<UnsavedFileHash> UnsavedFileHashes; 45 bool Dump = false; 46 }; 47 48 } // end anonymous namespace 49 50 LLVM_YAML_IS_SEQUENCE_VECTOR(UnsavedFileHash) 51 52 namespace llvm { 53 namespace yaml { 54 55 template <> struct MappingTraits<UnsavedFileHash> { 56 static void mapping(IO &IO, UnsavedFileHash &Info) { 57 IO.mapRequired("name", Info.Name); 58 IO.mapRequired("md5", Info.MD5); 59 } 60 }; 61 62 template <> struct MappingTraits<ClangInvocationInfo> { 63 static void mapping(IO &IO, ClangInvocationInfo &Info) { 64 IO.mapRequired("toolchain", Info.Toolchain); 65 IO.mapOptional("libclang.operation", Info.LibclangOperation); 66 IO.mapOptional("libclang.opts", Info.LibclangOptions); 67 IO.mapRequired("args", Info.Arguments); 68 IO.mapOptional("invocation-args", Info.InvocationArguments); 69 IO.mapOptional("unsaved_file_hashes", Info.UnsavedFileHashes); 70 } 71 }; 72 73 } // end namespace yaml 74 } // end namespace llvm 75 76 static std::string generateReproducerMetaInfo(const ClangInvocationInfo &Info) { 77 std::string Result; 78 llvm::raw_string_ostream OS(Result); 79 OS << '{'; 80 bool NeedComma = false; 81 auto EmitKey = [&](StringRef Key) { 82 if (NeedComma) 83 OS << ", "; 84 NeedComma = true; 85 OS << '"' << Key << "\": "; 86 }; 87 auto EmitStringKey = [&](StringRef Key, StringRef Value) { 88 if (Value.empty()) 89 return; 90 EmitKey(Key); 91 OS << '"' << Value << '"'; 92 }; 93 EmitStringKey("libclang.operation", Info.LibclangOperation); 94 EmitStringKey("libclang.opts", Info.LibclangOptions); 95 if (!Info.InvocationArguments.empty()) { 96 EmitKey("invocation-args"); 97 OS << '['; 98 for (const auto &Arg : llvm::enumerate(Info.InvocationArguments)) { 99 if (Arg.index()) 100 OS << ','; 101 OS << '"' << Arg.value() << '"'; 102 } 103 OS << ']'; 104 } 105 OS << '}'; 106 // FIXME: Compare unsaved file hashes and report mismatch in the reproducer. 107 if (Info.Dump) 108 llvm::outs() << "REPRODUCER METAINFO: " << Result << "\n"; 109 return Result; 110 } 111 112 /// Generates a reproducer for a set of arguments from a specific invocation. 113 static std::optional<driver::Driver::CompilationDiagnosticReport> 114 generateReproducerForInvocationArguments(ArrayRef<const char *> Argv, 115 const ClangInvocationInfo &Info, 116 const llvm::ToolContext &ToolContext) { 117 using namespace driver; 118 auto TargetAndMode = ToolChain::getTargetAndModeFromProgramName(Argv[0]); 119 120 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions; 121 122 IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); 123 DiagnosticsEngine Diags(DiagID, &*DiagOpts, new IgnoringDiagConsumer()); 124 auto VFS = llvm::vfs::getRealFileSystem(); 125 ProcessWarningOptions(Diags, *DiagOpts, *VFS, /*ReportDiags=*/false); 126 Driver TheDriver(ToolContext.Path, llvm::sys::getDefaultTargetTriple(), Diags, 127 /*Title=*/"clang LLVM compiler", VFS); 128 TheDriver.setTargetAndMode(TargetAndMode); 129 if (ToolContext.NeedsPrependArg) 130 TheDriver.setPrependArg(ToolContext.PrependArg); 131 132 std::unique_ptr<Compilation> C(TheDriver.BuildCompilation(Argv)); 133 if (C && !C->containsError()) { 134 for (const auto &J : C->getJobs()) { 135 if (const Command *Cmd = dyn_cast<Command>(&J)) { 136 Driver::CompilationDiagnosticReport Report; 137 TheDriver.generateCompilationDiagnostics( 138 *C, *Cmd, generateReproducerMetaInfo(Info), &Report); 139 return Report; 140 } 141 } 142 } 143 144 return std::nullopt; 145 } 146 147 std::string GetExecutablePath(const char *Argv0, bool CanonicalPrefixes); 148 149 static void printReproducerInformation( 150 llvm::raw_ostream &OS, const ClangInvocationInfo &Info, 151 const driver::Driver::CompilationDiagnosticReport &Report) { 152 OS << "REPRODUCER:\n"; 153 OS << "{\n"; 154 OS << R"("files":[)"; 155 for (const auto &File : llvm::enumerate(Report.TemporaryFiles)) { 156 if (File.index()) 157 OS << ','; 158 OS << '"' << File.value() << '"'; 159 } 160 OS << "]\n}\n"; 161 } 162 163 int cc1gen_reproducer_main(ArrayRef<const char *> Argv, const char *Argv0, 164 void *MainAddr, 165 const llvm::ToolContext &ToolContext) { 166 if (Argv.size() < 1) { 167 llvm::errs() << "error: missing invocation file\n"; 168 return 1; 169 } 170 // Parse the invocation descriptor. 171 StringRef Input = Argv[0]; 172 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Buffer = 173 llvm::MemoryBuffer::getFile(Input, /*IsText=*/true); 174 if (!Buffer) { 175 llvm::errs() << "error: failed to read " << Input << ": " 176 << Buffer.getError().message() << "\n"; 177 return 1; 178 } 179 llvm::yaml::Input YAML(Buffer.get()->getBuffer()); 180 ClangInvocationInfo InvocationInfo; 181 YAML >> InvocationInfo; 182 if (Argv.size() > 1 && Argv[1] == StringRef("-v")) 183 InvocationInfo.Dump = true; 184 185 // Create an invocation that will produce the reproducer. 186 std::vector<const char *> DriverArgs; 187 for (const auto &Arg : InvocationInfo.Arguments) 188 DriverArgs.push_back(Arg.c_str()); 189 std::string Path = GetExecutablePath(Argv0, /*CanonicalPrefixes=*/true); 190 DriverArgs[0] = Path.c_str(); 191 std::optional<driver::Driver::CompilationDiagnosticReport> Report = 192 generateReproducerForInvocationArguments(DriverArgs, InvocationInfo, 193 ToolContext); 194 195 // Emit the information about the reproduce files to stdout. 196 int Result = 1; 197 if (Report) { 198 printReproducerInformation(llvm::outs(), InvocationInfo, *Report); 199 Result = 0; 200 } 201 202 // Remove the input file. 203 llvm::sys::fs::remove(Input); 204 return Result; 205 } 206