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
28using namespace clang;
29
30namespace {
31
32struct UnsavedFileHash {
33  std::string Name;
34  std::string MD5;
35};
36
37struct 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
49LLVM_YAML_IS_SEQUENCE_VECTOR(UnsavedFileHash)
50
51namespace llvm {
52namespace yaml {
53
54template <> struct MappingTraits<UnsavedFileHash> {
55  static void mapping(IO &IO, UnsavedFileHash &Info) {
56    IO.mapRequired("name", Info.Name);
57    IO.mapRequired("md5", Info.MD5);
58  }
59};
60
61template <> struct MappingTraits<ClangInvocationInfo> {
62  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
75static 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.
112static std::optional<driver::Driver::CompilationDiagnosticReport>
113generateReproducerForInvocationArguments(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
141std::string GetExecutablePath(const char *Argv0, bool CanonicalPrefixes);
142
143static 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
157int 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