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