1336815Sdim//===-- cc1gen_reproducer_main.cpp - Clang reproducer generator  ----------===//
2336815Sdim//
3353358Sdim// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4353358Sdim// See https://llvm.org/LICENSE.txt for license information.
5353358Sdim// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6336815Sdim//
7336815Sdim//===----------------------------------------------------------------------===//
8336815Sdim//
9336815Sdim// This is the entry point to the clang -cc1gen-reproducer functionality, which
10336815Sdim// generates reproducers for invocations for clang-based tools.
11336815Sdim//
12336815Sdim//===----------------------------------------------------------------------===//
13336815Sdim
14336815Sdim#include "clang/Basic/Diagnostic.h"
15336815Sdim#include "clang/Basic/LLVM.h"
16336815Sdim#include "clang/Driver/Compilation.h"
17336815Sdim#include "clang/Driver/Driver.h"
18336815Sdim#include "llvm/ADT/ArrayRef.h"
19336815Sdim#include "llvm/ADT/STLExtras.h"
20336815Sdim#include "llvm/Support/FileSystem.h"
21336815Sdim#include "llvm/Support/TargetSelect.h"
22344779Sdim#include "llvm/Support/VirtualFileSystem.h"
23336815Sdim#include "llvm/Support/YAMLTraits.h"
24336815Sdim#include "llvm/Support/raw_ostream.h"
25336815Sdim
26336815Sdimusing namespace clang;
27336815Sdim
28336815Sdimnamespace {
29336815Sdim
30336815Sdimstruct UnsavedFileHash {
31336815Sdim  std::string Name;
32336815Sdim  std::string MD5;
33336815Sdim};
34336815Sdim
35336815Sdimstruct ClangInvocationInfo {
36336815Sdim  std::string Toolchain;
37336815Sdim  std::string LibclangOperation;
38336815Sdim  std::string LibclangOptions;
39336815Sdim  std::vector<std::string> Arguments;
40336815Sdim  std::vector<std::string> InvocationArguments;
41336815Sdim  std::vector<UnsavedFileHash> UnsavedFileHashes;
42336815Sdim  bool Dump = false;
43336815Sdim};
44336815Sdim
45336815Sdim} // end anonymous namespace
46336815Sdim
47336815SdimLLVM_YAML_IS_SEQUENCE_VECTOR(UnsavedFileHash)
48336815Sdim
49336815Sdimnamespace llvm {
50336815Sdimnamespace yaml {
51336815Sdim
52336815Sdimtemplate <> struct MappingTraits<UnsavedFileHash> {
53336815Sdim  static void mapping(IO &IO, UnsavedFileHash &Info) {
54336815Sdim    IO.mapRequired("name", Info.Name);
55336815Sdim    IO.mapRequired("md5", Info.MD5);
56336815Sdim  }
57336815Sdim};
58336815Sdim
59336815Sdimtemplate <> struct MappingTraits<ClangInvocationInfo> {
60336815Sdim  static void mapping(IO &IO, ClangInvocationInfo &Info) {
61336815Sdim    IO.mapRequired("toolchain", Info.Toolchain);
62336815Sdim    IO.mapOptional("libclang.operation", Info.LibclangOperation);
63336815Sdim    IO.mapOptional("libclang.opts", Info.LibclangOptions);
64336815Sdim    IO.mapRequired("args", Info.Arguments);
65336815Sdim    IO.mapOptional("invocation-args", Info.InvocationArguments);
66336815Sdim    IO.mapOptional("unsaved_file_hashes", Info.UnsavedFileHashes);
67336815Sdim  }
68336815Sdim};
69336815Sdim
70336815Sdim} // end namespace yaml
71336815Sdim} // end namespace llvm
72336815Sdim
73336815Sdimstatic std::string generateReproducerMetaInfo(const ClangInvocationInfo &Info) {
74336815Sdim  std::string Result;
75336815Sdim  llvm::raw_string_ostream OS(Result);
76336815Sdim  OS << '{';
77336815Sdim  bool NeedComma = false;
78336815Sdim  auto EmitKey = [&](StringRef Key) {
79336815Sdim    if (NeedComma)
80336815Sdim      OS << ", ";
81336815Sdim    NeedComma = true;
82336815Sdim    OS << '"' << Key << "\": ";
83336815Sdim  };
84336815Sdim  auto EmitStringKey = [&](StringRef Key, StringRef Value) {
85336815Sdim    if (Value.empty())
86336815Sdim      return;
87336815Sdim    EmitKey(Key);
88336815Sdim    OS << '"' << Value << '"';
89336815Sdim  };
90336815Sdim  EmitStringKey("libclang.operation", Info.LibclangOperation);
91336815Sdim  EmitStringKey("libclang.opts", Info.LibclangOptions);
92336815Sdim  if (!Info.InvocationArguments.empty()) {
93336815Sdim    EmitKey("invocation-args");
94336815Sdim    OS << '[';
95336815Sdim    for (const auto &Arg : llvm::enumerate(Info.InvocationArguments)) {
96336815Sdim      if (Arg.index())
97336815Sdim        OS << ',';
98336815Sdim      OS << '"' << Arg.value() << '"';
99336815Sdim    }
100336815Sdim    OS << ']';
101336815Sdim  }
102336815Sdim  OS << '}';
103336815Sdim  // FIXME: Compare unsaved file hashes and report mismatch in the reproducer.
104336815Sdim  if (Info.Dump)
105336815Sdim    llvm::outs() << "REPRODUCER METAINFO: " << OS.str() << "\n";
106336815Sdim  return std::move(OS.str());
107336815Sdim}
108336815Sdim
109336815Sdim/// Generates a reproducer for a set of arguments from a specific invocation.
110336815Sdimstatic llvm::Optional<driver::Driver::CompilationDiagnosticReport>
111336815SdimgenerateReproducerForInvocationArguments(ArrayRef<const char *> Argv,
112336815Sdim                                         const ClangInvocationInfo &Info) {
113336815Sdim  using namespace driver;
114336815Sdim  auto TargetAndMode = ToolChain::getTargetAndModeFromProgramName(Argv[0]);
115336815Sdim
116336815Sdim  IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions;
117336815Sdim
118336815Sdim  IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
119336815Sdim  DiagnosticsEngine Diags(DiagID, &*DiagOpts, new IgnoringDiagConsumer());
120336815Sdim  ProcessWarningOptions(Diags, *DiagOpts, /*ReportDiags=*/false);
121336815Sdim  Driver TheDriver(Argv[0], llvm::sys::getDefaultTargetTriple(), Diags);
122336815Sdim  TheDriver.setTargetAndMode(TargetAndMode);
123336815Sdim
124336815Sdim  std::unique_ptr<Compilation> C(TheDriver.BuildCompilation(Argv));
125336815Sdim  if (C && !C->containsError()) {
126336815Sdim    for (const auto &J : C->getJobs()) {
127336815Sdim      if (const Command *Cmd = dyn_cast<Command>(&J)) {
128336815Sdim        Driver::CompilationDiagnosticReport Report;
129336815Sdim        TheDriver.generateCompilationDiagnostics(
130336815Sdim            *C, *Cmd, generateReproducerMetaInfo(Info), &Report);
131336815Sdim        return Report;
132336815Sdim      }
133336815Sdim    }
134336815Sdim  }
135336815Sdim
136336815Sdim  return None;
137336815Sdim}
138336815Sdim
139336815Sdimstd::string GetExecutablePath(const char *Argv0, bool CanonicalPrefixes);
140336815Sdim
141336815Sdimstatic void printReproducerInformation(
142336815Sdim    llvm::raw_ostream &OS, const ClangInvocationInfo &Info,
143336815Sdim    const driver::Driver::CompilationDiagnosticReport &Report) {
144336815Sdim  OS << "REPRODUCER:\n";
145336815Sdim  OS << "{\n";
146336815Sdim  OS << R"("files":[)";
147336815Sdim  for (const auto &File : llvm::enumerate(Report.TemporaryFiles)) {
148336815Sdim    if (File.index())
149336815Sdim      OS << ',';
150336815Sdim    OS << '"' << File.value() << '"';
151336815Sdim  }
152336815Sdim  OS << "]\n}\n";
153336815Sdim}
154336815Sdim
155336815Sdimint cc1gen_reproducer_main(ArrayRef<const char *> Argv, const char *Argv0,
156336815Sdim                           void *MainAddr) {
157336815Sdim  if (Argv.size() < 1) {
158336815Sdim    llvm::errs() << "error: missing invocation file\n";
159336815Sdim    return 1;
160336815Sdim  }
161336815Sdim  // Parse the invocation descriptor.
162336815Sdim  StringRef Input = Argv[0];
163336815Sdim  llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Buffer =
164336815Sdim      llvm::MemoryBuffer::getFile(Input);
165336815Sdim  if (!Buffer) {
166336815Sdim    llvm::errs() << "error: failed to read " << Input << ": "
167336815Sdim                 << Buffer.getError().message() << "\n";
168336815Sdim    return 1;
169336815Sdim  }
170336815Sdim  llvm::yaml::Input YAML(Buffer.get()->getBuffer());
171336815Sdim  ClangInvocationInfo InvocationInfo;
172336815Sdim  YAML >> InvocationInfo;
173336815Sdim  if (Argv.size() > 1 && Argv[1] == StringRef("-v"))
174336815Sdim    InvocationInfo.Dump = true;
175336815Sdim
176336815Sdim  // Create an invocation that will produce the reproducer.
177336815Sdim  std::vector<const char *> DriverArgs;
178336815Sdim  for (const auto &Arg : InvocationInfo.Arguments)
179336815Sdim    DriverArgs.push_back(Arg.c_str());
180336815Sdim  std::string Path = GetExecutablePath(Argv0, /*CanonicalPrefixes=*/true);
181336815Sdim  DriverArgs[0] = Path.c_str();
182336815Sdim  llvm::Optional<driver::Driver::CompilationDiagnosticReport> Report =
183336815Sdim      generateReproducerForInvocationArguments(DriverArgs, InvocationInfo);
184336815Sdim
185336815Sdim  // Emit the information about the reproduce files to stdout.
186336815Sdim  int Result = 1;
187336815Sdim  if (Report) {
188336815Sdim    printReproducerInformation(llvm::outs(), InvocationInfo, *Report);
189336815Sdim    Result = 0;
190336815Sdim  }
191336815Sdim
192336815Sdim  // Remove the input file.
193336815Sdim  llvm::sys::fs::remove(Input);
194336815Sdim  return Result;
195336815Sdim}
196