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