xref: /llvm-project/clang/tools/driver/cc1gen_reproducer_main.cpp (revision 5845688e91d85d46c0f47daaf4edfdfc772853cf)
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