Clang Project

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