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