Index: include/clang/Driver/Driver.h =================================================================== --- include/clang/Driver/Driver.h +++ include/clang/Driver/Driver.h @@ -388,11 +388,19 @@ int ExecuteCompilation(Compilation &C, SmallVectorImpl< std::pair > &FailingCommands); - /// generateCompilationDiagnostics - Generate diagnostics information + /// Contains the files in the compilation diagnostic report generated by + /// generateCompilationDiagnostics. + struct CompilationDiagnosticReport { + llvm::SmallVector TemporaryFiles; + }; + + /// generateCompilationDiagnostics - Generate diagnostics information /// including preprocessed source file(s). - /// - void generateCompilationDiagnostics(Compilation &C, - const Command &FailingCommand); + /// + void generateCompilationDiagnostics( + Compilation &C, const Command &FailingCommand, + StringRef AdditionalInformation = "", + CompilationDiagnosticReport *GeneratedReport = nullptr); /// @} /// @name Helper Methods Index: lib/Driver/Driver.cpp =================================================================== --- lib/Driver/Driver.cpp +++ lib/Driver/Driver.cpp @@ -851,8 +851,9 @@ // When clang crashes, produce diagnostic information including the fully // preprocessed source file(s). Request that the developer attach the // diagnostic information to a bug report. -void Driver::generateCompilationDiagnostics(Compilation &C, - const Command &FailingCommand) { +void Driver::generateCompilationDiagnostics( + Compilation &C, const Command &FailingCommand, + StringRef AdditionalInformation, CompilationDiagnosticReport *Report) { if (C.getArgs().hasArg(options::OPT_fno_crash_diagnostics)) return; @@ -978,6 +979,8 @@ SmallString<128> ReproCrashFilename; for (const char *TempFile : TempFiles) { Diag(clang::diag::note_drv_command_failed_diag_msg) << TempFile; + if (Report) + Report->TemporaryFiles.push_back(TempFile); if (ReproCrashFilename.empty()) { ReproCrashFilename = TempFile; llvm::sys::path::replace_extension(ReproCrashFilename, ".crash"); @@ -1006,6 +1009,11 @@ ScriptOS << "# Original command: "; Cmd.Print(ScriptOS, "\n", /*Quote=*/true); Cmd.Print(ScriptOS, "\n", /*Quote=*/true, &CrashInfo); + if (!AdditionalInformation.empty()) + ScriptOS << "\n# Additional information: " << AdditionalInformation + << "\n"; + if (Report) + Report->TemporaryFiles.push_back(Script); Diag(clang::diag::note_drv_command_failed_diag_msg) << Script; } Index: test/Index/create-libclang-completion-reproducer.c =================================================================== --- /dev/null +++ test/Index/create-libclang-completion-reproducer.c @@ -0,0 +1,14 @@ +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: env CINDEXTEST_INVOCATION_EMISSION_PATH=%t not c-index-test -code-completion-at=%s:10:1 "-remap-file=%s,%S/Inputs/record-parsing-invocation-remap.c" %s +// RUN: %clang -cc1gen-reproducer %t/libclang-* -v | FileCheck %s + +// Invocation file must be removed by clang: +// RUN: ls %t | count 0 + +// CHECK: REPRODUCER METAINFO: {"libclang.operation": "complete", "libclang.opts": "1", "invocation-args": ["-code-completion-at={{.*}}create-libclang-completion-reproducer.c:10:1"]} + +// CHECK: REPRODUCER: +// CHECK-NEXT: { +// CHECK-NEXT: "files":["{{.*}}.c","{{.*}}.sh"] +// CHECK-NEXT: } Index: test/Index/create-libclang-parsing-reproducer.c =================================================================== --- /dev/null +++ test/Index/create-libclang-parsing-reproducer.c @@ -0,0 +1,21 @@ +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: env CINDEXTEST_INVOCATION_EMISSION_PATH=%t not c-index-test -test-load-source all %s +// RUN: %clang -cc1gen-reproducer %t/libclang-* -v | FileCheck %s + +// Invocation file must be removed by clang: +// RUN: ls %t | count 0 + +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: env CINDEXTEST_INVOCATION_EMISSION_PATH=%t not c-index-test -test-load-source all "-remap-file=%s,%S/Inputs/record-parsing-invocation-remap.c" %s +// RUN: %clang -cc1gen-reproducer %t/libclang-* -v | FileCheck %s + +#pragma clang __debug parser_crash + +// CHECK: REPRODUCER METAINFO: {"libclang.operation": "parse", "libclang.opts": "1"} + +// CHECK: REPRODUCER: +// CHECK-NEXT: { +// CHECK-NEXT: "files":["{{.*}}.c","{{.*}}.sh"] +// CHECK-NEXT: } Index: tools/driver/CMakeLists.txt =================================================================== --- tools/driver/CMakeLists.txt +++ tools/driver/CMakeLists.txt @@ -32,6 +32,7 @@ driver.cpp cc1_main.cpp cc1as_main.cpp + cc1gen_reproducer_main.cpp DEPENDS ${tablegen_deps} Index: tools/driver/cc1gen_reproducer_main.cpp =================================================================== --- /dev/null +++ tools/driver/cc1gen_reproducer_main.cpp @@ -0,0 +1,196 @@ +//===-- cc1gen_reproducer_main.cpp - Clang reproducer generator ----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This is the entry point to the clang -cc1gen-reproducer functionality, which +// generates reproducers for invocations for clang-based tools. +// +//===----------------------------------------------------------------------===// + +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/VirtualFileSystem.h" +#include "clang/Driver/Compilation.h" +#include "clang/Driver/Driver.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/TargetSelect.h" +#include "llvm/Support/YAMLTraits.h" +#include "llvm/Support/raw_ostream.h" + +using namespace clang; + +namespace { + +struct UnsavedFileHash { + std::string Name; + std::string MD5; +}; + +struct ClangInvocationInfo { + std::string Toolchain; + std::string LibclangOperation; + std::string LibclangOptions; + std::vector Arguments; + std::vector InvocationArguments; + std::vector UnsavedFileHashes; + bool Dump = false; +}; + +} // end anonymous namespace + +LLVM_YAML_IS_SEQUENCE_VECTOR(UnsavedFileHash) + +namespace llvm { +namespace yaml { + +template <> struct MappingTraits { + static void mapping(IO &IO, UnsavedFileHash &Info) { + IO.mapRequired("name", Info.Name); + IO.mapRequired("md5", Info.MD5); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, ClangInvocationInfo &Info) { + IO.mapRequired("toolchain", Info.Toolchain); + IO.mapOptional("libclang.operation", Info.LibclangOperation); + IO.mapOptional("libclang.opts", Info.LibclangOptions); + IO.mapRequired("args", Info.Arguments); + IO.mapOptional("invocation-args", Info.InvocationArguments); + IO.mapOptional("unsaved_file_hashes", Info.UnsavedFileHashes); + } +}; + +} // end namespace yaml +} // end namespace llvm + +static std::string generateReproducerMetaInfo(const ClangInvocationInfo &Info) { + std::string Result; + llvm::raw_string_ostream OS(Result); + OS << '{'; + bool NeedComma = false; + auto EmitKey = [&](StringRef Key) { + if (NeedComma) + OS << ", "; + NeedComma = true; + OS << '"' << Key << "\": "; + }; + auto EmitStringKey = [&](StringRef Key, StringRef Value) { + if (Value.empty()) + return; + EmitKey(Key); + OS << '"' << Value << '"'; + }; + EmitStringKey("libclang.operation", Info.LibclangOperation); + EmitStringKey("libclang.opts", Info.LibclangOptions); + if (!Info.InvocationArguments.empty()) { + EmitKey("invocation-args"); + OS << '['; + for (const auto &Arg : llvm::enumerate(Info.InvocationArguments)) { + if (Arg.index()) + OS << ','; + OS << '"' << Arg.value() << '"'; + } + OS << ']'; + } + OS << '}'; + // FIXME: Compare unsaved file hashes and report mismatch in the reproducer. + if (Info.Dump) + llvm::outs() << "REPRODUCER METAINFO: " << OS.str() << "\n"; + return std::move(OS.str()); +} + +/// Generates a reproducer for a set of arguments from a specific invocation. +static llvm::Optional +generateReproducerForInvocationArguments(ArrayRef Argv, + const ClangInvocationInfo &Info) { + using namespace driver; + auto TargetAndMode = ToolChain::getTargetAndModeFromProgramName(Argv[0]); + + IntrusiveRefCntPtr DiagOpts = new DiagnosticOptions; + + IntrusiveRefCntPtr DiagID(new DiagnosticIDs()); + DiagnosticsEngine Diags(DiagID, &*DiagOpts, new IgnoringDiagConsumer()); + ProcessWarningOptions(Diags, *DiagOpts, /*ReportDiags=*/false); + Driver TheDriver(Argv[0], llvm::sys::getDefaultTargetTriple(), Diags); + TheDriver.setTargetAndMode(TargetAndMode); + + std::unique_ptr C(TheDriver.BuildCompilation(Argv)); + if (C && !C->containsError()) { + for (const auto &J : C->getJobs()) { + if (const Command *Cmd = dyn_cast(&J)) { + Driver::CompilationDiagnosticReport Report; + TheDriver.generateCompilationDiagnostics( + *C, *Cmd, generateReproducerMetaInfo(Info), &Report); + return Report; + } + } + } + + return None; +} + +std::string GetExecutablePath(const char *Argv0, bool CanonicalPrefixes); + +static void printReproducerInformation( + llvm::raw_ostream &OS, const ClangInvocationInfo &Info, + const driver::Driver::CompilationDiagnosticReport &Report) { + OS << "REPRODUCER:\n"; + OS << "{\n"; + OS << R"("files":[)"; + for (const auto &File : llvm::enumerate(Report.TemporaryFiles)) { + if (File.index()) + OS << ','; + OS << '"' << File.value() << '"'; + } + OS << "]\n}\n"; +} + +int cc1gen_reproducer_main(ArrayRef Argv, const char *Argv0, + void *MainAddr) { + if (Argv.size() < 1) { + llvm::errs() << "error: missing invocation file\n"; + return 1; + } + // Parse the invocation descriptor. + StringRef Input = Argv[0]; + llvm::ErrorOr> Buffer = + llvm::MemoryBuffer::getFile(Input); + if (!Buffer) { + llvm::errs() << "error: failed to read " << Input << ": " + << Buffer.getError().message() << "\n"; + return 1; + } + llvm::yaml::Input YAML(Buffer.get()->getBuffer()); + ClangInvocationInfo InvocationInfo; + YAML >> InvocationInfo; + if (Argv.size() > 1 && Argv[1] == StringRef("-v")) + InvocationInfo.Dump = true; + + // Create an invocation that will produce the reproducer. + std::vector DriverArgs; + for (const auto &Arg : InvocationInfo.Arguments) + DriverArgs.push_back(Arg.c_str()); + std::string Path = GetExecutablePath(Argv0, /*CanonicalPrefixes=*/true); + DriverArgs[0] = Path.c_str(); + llvm::Optional Report = + generateReproducerForInvocationArguments(DriverArgs, InvocationInfo); + + // Emit the information about the reproduce files to stdout. + int Result = 1; + if (Report) { + printReproducerInformation(llvm::outs(), InvocationInfo, *Report); + Result = 0; + } + + // Remove the input file. + llvm::sys::fs::remove(Input); + return Result; +} Index: tools/driver/driver.cpp =================================================================== --- tools/driver/driver.cpp +++ tools/driver/driver.cpp @@ -205,6 +205,8 @@ void *MainAddr); extern int cc1as_main(ArrayRef Argv, const char *Argv0, void *MainAddr); +extern int cc1gen_reproducer_main(ArrayRef Argv, + const char *Argv0, void *MainAddr); static void insertTargetAndModeArgs(const ParsedClangName &NameParts, SmallVectorImpl &ArgVector, @@ -309,6 +311,8 @@ return cc1_main(argv.slice(2), argv[0], GetExecutablePathVP); if (Tool == "as") return cc1as_main(argv.slice(2), argv[0], GetExecutablePathVP); + if (Tool == "gen-reproducer") + return cc1gen_reproducer_main(argv.slice(2), argv[0], GetExecutablePathVP); // Reject unknown tools. llvm::errs() << "error: unknown integrated tool '" << Tool << "'\n";