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