tan  0.0.1
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/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 
28 using namespace clang;
29 
30 namespace {
31 
32 struct UnsavedFileHash {
33  std::string Name;
34  std::string MD5;
35 };
36 
37 struct 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 
49 LLVM_YAML_IS_SEQUENCE_VECTOR(UnsavedFileHash)
50 
51 namespace llvm {
52 namespace yaml {
53 
54 template <> 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 
61 template <> 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 
75 static 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.
112 static std::optional<driver::Driver::CompilationDiagnosticReport>
113 generateReproducerForInvocationArguments(ArrayRef<const char *> Argv, 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(*C, *Cmd, generateReproducerMetaInfo(Info), &Report);
131  return Report;
132  }
133  }
134  }
135 
136  return std::nullopt;
137 }
138 
139 std::string GetExecutablePath(const char *Argv0, bool CanonicalPrefixes);
140 
141 static void printReproducerInformation(llvm::raw_ostream &OS,
142  const 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 
155 int cc1gen_reproducer_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
156  if (Argv.size() < 1) {
157  llvm::errs() << "error: missing invocation file\n";
158  return 1;
159  }
160  // Parse the invocation descriptor.
161  StringRef Input = Argv[0];
162  llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Buffer = llvm::MemoryBuffer::getFile(Input, /*IsText=*/true);
163  if (!Buffer) {
164  llvm::errs() << "error: failed to read " << Input << ": " << Buffer.getError().message() << "\n";
165  return 1;
166  }
167  llvm::yaml::Input YAML(Buffer.get()->getBuffer());
168  ClangInvocationInfo InvocationInfo;
169  YAML >> InvocationInfo;
170  if (Argv.size() > 1 && Argv[1] == StringRef("-v"))
171  InvocationInfo.Dump = true;
172 
173  // Create an invocation that will produce the reproducer.
174  std::vector<const char *> DriverArgs;
175  for (const auto &Arg : InvocationInfo.Arguments)
176  DriverArgs.push_back(Arg.c_str());
177  std::string Path = GetExecutablePath(Argv0, /*CanonicalPrefixes=*/true);
178  DriverArgs[0] = Path.c_str();
179  std::optional<driver::Driver::CompilationDiagnosticReport> Report =
180  generateReproducerForInvocationArguments(DriverArgs, InvocationInfo);
181 
182  // Emit the information about the reproduce files to stdout.
183  int Result = 1;
184  if (Report) {
185  printReproducerInformation(llvm::outs(), InvocationInfo, *Report);
186  Result = 0;
187  }
188 
189  // Remove the input file.
190  llvm::sys::fs::remove(Input);
191  return Result;
192 }