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