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: " << OS.str() << "\n"; 109 return std::move(OS.str()); 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 ProcessWarningOptions(Diags, *DiagOpts, /*ReportDiags=*/false); 125 Driver TheDriver(ToolContext.Path, llvm::sys::getDefaultTargetTriple(), 126 Diags); 127 TheDriver.setTargetAndMode(TargetAndMode); 128 if (ToolContext.NeedsPrependArg) 129 TheDriver.setPrependArg(ToolContext.PrependArg); 130 131 std::unique_ptr<Compilation> C(TheDriver.BuildCompilation(Argv)); 132 if (C && !C->containsError()) { 133 for (const auto &J : C->getJobs()) { 134 if (const Command *Cmd = dyn_cast<Command>(&J)) { 135 Driver::CompilationDiagnosticReport Report; 136 TheDriver.generateCompilationDiagnostics( 137 *C, *Cmd, generateReproducerMetaInfo(Info), &Report); 138 return Report; 139 } 140 } 141 } 142 143 return std::nullopt; 144 } 145 146 std::string GetExecutablePath(const char *Argv0, bool CanonicalPrefixes); 147 148 static void printReproducerInformation( 149 llvm::raw_ostream &OS, const ClangInvocationInfo &Info, 150 const driver::Driver::CompilationDiagnosticReport &Report) { 151 OS << "REPRODUCER:\n"; 152 OS << "{\n"; 153 OS << R"("files":[)"; 154 for (const auto &File : llvm::enumerate(Report.TemporaryFiles)) { 155 if (File.index()) 156 OS << ','; 157 OS << '"' << File.value() << '"'; 158 } 159 OS << "]\n}\n"; 160 } 161 162 int cc1gen_reproducer_main(ArrayRef<const char *> Argv, const char *Argv0, 163 void *MainAddr, 164 const llvm::ToolContext &ToolContext) { 165 if (Argv.size() < 1) { 166 llvm::errs() << "error: missing invocation file\n"; 167 return 1; 168 } 169 // Parse the invocation descriptor. 170 StringRef Input = Argv[0]; 171 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Buffer = 172 llvm::MemoryBuffer::getFile(Input, /*IsText=*/true); 173 if (!Buffer) { 174 llvm::errs() << "error: failed to read " << Input << ": " 175 << Buffer.getError().message() << "\n"; 176 return 1; 177 } 178 llvm::yaml::Input YAML(Buffer.get()->getBuffer()); 179 ClangInvocationInfo InvocationInfo; 180 YAML >> InvocationInfo; 181 if (Argv.size() > 1 && Argv[1] == StringRef("-v")) 182 InvocationInfo.Dump = true; 183 184 // Create an invocation that will produce the reproducer. 185 std::vector<const char *> DriverArgs; 186 for (const auto &Arg : InvocationInfo.Arguments) 187 DriverArgs.push_back(Arg.c_str()); 188 std::string Path = GetExecutablePath(Argv0, /*CanonicalPrefixes=*/true); 189 DriverArgs[0] = Path.c_str(); 190 std::optional<driver::Driver::CompilationDiagnosticReport> Report = 191 generateReproducerForInvocationArguments(DriverArgs, InvocationInfo, 192 ToolContext); 193 194 // Emit the information about the reproduce files to stdout. 195 int Result = 1; 196 if (Report) { 197 printReproducerInformation(llvm::outs(), InvocationInfo, *Report); 198 Result = 0; 199 } 200 201 // Remove the input file. 202 llvm::sys::fs::remove(Input); 203 return Result; 204 } 205