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