blob: 2d7ea86f7486dfd02c63acbc298a2d6b4c44c011 [file] [log] [blame]
Alex Lorenz045c5142018-04-07 00:03:27 +00001//===-- cc1gen_reproducer_main.cpp - Clang reproducer generator ----------===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9//
10// This is the entry point to the clang -cc1gen-reproducer functionality, which
11// generates reproducers for invocations for clang-based tools.
12//
13//===----------------------------------------------------------------------===//
14
15#include "clang/Basic/Diagnostic.h"
16#include "clang/Basic/LLVM.h"
Alex Lorenz045c5142018-04-07 00:03:27 +000017#include "clang/Driver/Compilation.h"
18#include "clang/Driver/Driver.h"
19#include "llvm/ADT/ArrayRef.h"
20#include "llvm/ADT/STLExtras.h"
21#include "llvm/Support/FileSystem.h"
22#include "llvm/Support/TargetSelect.h"
Jonas Devliegherefc514902018-10-10 13:27:25 +000023#include "llvm/Support/VirtualFileSystem.h"
Alex Lorenz045c5142018-04-07 00:03:27 +000024#include "llvm/Support/YAMLTraits.h"
25#include "llvm/Support/raw_ostream.h"
26
27using namespace clang;
28
29namespace {
30
31struct UnsavedFileHash {
32 std::string Name;
33 std::string MD5;
34};
35
36struct 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
48LLVM_YAML_IS_SEQUENCE_VECTOR(UnsavedFileHash)
49
50namespace llvm {
51namespace yaml {
52
53template <> 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
60template <> 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
74static 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.
111static llvm::Optional<driver::Driver::CompilationDiagnosticReport>
112generateReproducerForInvocationArguments(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
140std::string GetExecutablePath(const char *Argv0, bool CanonicalPrefixes);
141
142static 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
156int 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}