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