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, /*IsText=*/true);
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