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