Index: clang/include/clang/Driver/Driver.h =================================================================== --- clang/include/clang/Driver/Driver.h +++ clang/include/clang/Driver/Driver.h @@ -458,12 +458,15 @@ llvm::SmallVector TemporaryFiles; }; + llvm::Optional + generateReproFile(Compilation &C, llvm::SmallVectorImpl &Args); + /// generateCompilationDiagnostics - Generate diagnostics information /// including preprocessed source file(s). /// void generateCompilationDiagnostics( Compilation &C, const Command &FailingCommand, - StringRef AdditionalInformation = "", + SmallVectorImpl &Args, StringRef AdditionalInformation = "", CompilationDiagnosticReport *GeneratedReport = nullptr); /// @} Index: clang/include/clang/Driver/Options.td =================================================================== --- clang/include/clang/Driver/Options.td +++ clang/include/clang/Driver/Options.td @@ -1389,6 +1389,8 @@ def fcrash_diagnostics_dir : Joined<["-"], "fcrash-diagnostics-dir=">, Group, Flags<[NoArgumentUnused, CoreOption]>, HelpText<"Put crash-report files in ">, MetaVarName<"">; +def femit_reproducer : Joined<["-"], "femit-reproducer=">, Group, Flags<[NoArgumentUnused, CoreOption]>, + HelpText<"Emit reproducer on (option: off (default), on-error, always)">; def fcreate_profile : Flag<["-"], "fcreate-profile">, Group; defm cxx_exceptions: BoolFOption<"cxx-exceptions", LangOpts<"CXXExceptions">, DefaultFalse, Index: clang/lib/Driver/Driver.cpp =================================================================== --- clang/lib/Driver/Driver.cpp +++ clang/lib/Driver/Driver.cpp @@ -89,6 +89,7 @@ #include "llvm/Support/Process.h" #include "llvm/Support/Program.h" #include "llvm/Support/StringSaver.h" +#include "llvm/Support/TarWriter.h" #include "llvm/Support/VirtualFileSystem.h" #include "llvm/Support/raw_ostream.h" #include @@ -1390,12 +1391,100 @@ return false; } +static std::pair, std::string> +openReproTarFile(Driver &D, Compilation &C) { + SmallString<64> TmpName; + if (Arg *A = C.getArgs().getLastArg(options::OPT_fcrash_diagnostics_dir)) + TmpName = A->getValue(); + else + llvm::sys::path::system_temp_directory(/*ErasedOnReboot*/ true, TmpName); + + if (!D.getVFS().exists(TmpName)) + llvm::sys::fs::create_directories(TmpName); + + llvm::sys::path::append(TmpName, "%%%%%%.tar"); + std::error_code EC = llvm::sys::fs::createUniqueFile(TmpName, TmpName); + if (EC) { + D.Diag(clang::diag::err_unable_to_make_temp) << EC.message(); + return {}; + } + + auto TarWriterOrErr = + llvm::TarWriter::create(TmpName, llvm::sys::path::stem(TmpName)); + if (!TarWriterOrErr) { + D.Diag(clang::diag::err_unable_to_make_temp) << TmpName; + return {}; + } + + return {std::move(*TarWriterOrErr), TmpName.c_str()}; +} + +llvm::Optional +Driver::generateReproFile(Compilation &C, SmallVectorImpl &Args) { + std::unique_ptr TarWriter; + std::string ReproFileName; + std::tie(TarWriter, ReproFileName) = openReproTarFile(*this, C); + if (!TarWriter) + return {}; + + auto WriteFileToTar = [&TarWriter](StringRef FSPath, std::string TarPath) { + auto MBOrErr = llvm::MemoryBuffer::getFile(FSPath); + if (!MBOrErr) + return; + + TarPath += llvm::sys::path::filename(FSPath); + TarWriter->append(TarPath, MBOrErr->get()->getBuffer()); + }; + + for (auto *Input : C.getInputArgs().filtered(options::OPT_INPUT)) + WriteFileToTar(Input->getAsString(C.getInputArgs()), "inputs/"); + + // Rerun the compilation with save temps and outputs redirected to /dev/null. + SaveTemps = SaveTempsCwd; + std::unique_ptr CPtr{BuildCompilation(Args)}; + Compilation &NewC = *CPtr; + NewC.Redirect({None, {""}, {""}}); + SmallVector, 4> FailingCommands; + NewC.ExecuteJobs(NewC.getJobs(), FailingCommands); + + std::set OutputFilenames; + llvm::transform(C.getResultFiles(), + std::inserter(OutputFilenames, OutputFilenames.begin()), + [](auto ResultFile) { return ResultFile.second; }); + + for (auto ResultFile : NewC.getResultFiles()) { + WriteFileToTar(ResultFile.second, "tmp/"); + // If this file isn't produced by the original Compilation remove it. + if (OutputFilenames.find(ResultFile.second) == OutputFilenames.end()) + llvm::sys::fs::remove(ResultFile.second); + } + + std::string Storage; + llvm::raw_string_ostream OS{Storage}; + + PrintVersion(NewC, OS); + NewC.getDefaultToolChain().printVerboseInfo(OS); + TarWriter->append("version.txt", Storage); + Storage.clear(); + + OS << "# Driver args: "; + printArgList(OS, C.getInputArgs()); + for (const Command &Cmd : NewC.getJobs()) { + OS << "# " << Cmd.getSource().getClassName() << '\n'; + Cmd.Print(OS, "\n", /*Quote=*/true); + } + TarWriter->append("repro.sh", Storage); + + return ReproFileName; +} + // 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, - StringRef AdditionalInformation, CompilationDiagnosticReport *Report) { + SmallVectorImpl &Args, StringRef AdditionalInformation, + CompilationDiagnosticReport *Report) { if (C.getArgs().hasArg(options::OPT_fno_crash_diagnostics)) return; @@ -1404,6 +1493,8 @@ FailingCommand.getCreator().isDsymutilJob()) return; + Optional ReproFilename = generateReproFile(C, Args); + // Print the version of the compiler. PrintVersion(C, llvm::errs()); @@ -1554,6 +1645,9 @@ Diag(clang::diag::note_drv_command_failed_diag_msg) << Script; } + if (ReproFilename) + Diag(clang::diag::note_drv_command_failed_diag_msg) << *ReproFilename; + // On darwin, provide information about the .crash diagnostic report. if (llvm::Triple(llvm::sys::getProcessTriple()).isOSDarwin()) { SmallString<128> CrashDiagDir; Index: clang/test/Driver/reproduce.c =================================================================== --- /dev/null +++ clang/test/Driver/reproduce.c @@ -0,0 +1,40 @@ +// Check that reproducers aren't created when -femit-reproducer=off +// RUN: not %clang -DBODY=error %s -femit-reproducer=off -fcrash-diagnostics-dir=%t +// RUN: test ! -e %t/*.tar +// RUN: not %clang -DBODY=error %s -fcrash-diagnostics-dir=%t +// RUN: test ! -e %t/*.tar + +// Check that reproducers aren't created when no error occured +// RUN: %clang %s -femit-reproducer=off -fcrash-diagnostics-dir=%t +// RUN: test ! -e %t/*.tar +// RUN: %clang %s -femit-reproducer=on-error -fcrash-diagnostics-dir=%t +// RUN: test ! -e %t/*.tar + +// Check reproducers are created when an error occured +// RUN: not %clang -DBODY=error %s -femit-reproducer=on-error -fcrash-diagnostics-dir=%t +// RUN: test -e %t/*.tar +// RUN: rm -f %t/*.tar + +// Use "always" here because it produces the most amount of temp files from -save-temps +// RUN: %clang %s -femit-reproducer=always -fcrash-diagnostics-dir=%t +// RUN: tar tf %t/*.tar | sort | FileCheck %s --check-prefix=TAR-LAYOUT +// RUN: tar xOf %t/*.tar --wildcards "*/inputs/reproduce.c" > %t.c +// RUN: diff %t.c %s +// RUN: rm -f %t/*.tar + +// TAR-LAYOUT: {{.*}}/inputs/reproduce.c +// TAR-LAYOUT-NEXT: {{.*}}/repro.sh +// TAR-LAYOUT-NEXT: {{.*}}/tmp/a.out +// TAR-LAYOUT-NEXT: {{.*}}/tmp/reproduce.bc +// TAR-LAYOUT-NEXT: {{.*}}/tmp/reproduce.i +// TAR-LAYOUT-NEXT: {{.*}}/tmp/reproduce.o +// TAR-LAYOUT-NEXT: {{.*}}/tmp/reproduce.s +// TAR-LAYOUT-NEXT: {{.*}}/version.txt + +#ifndef BODY +#define BODY +#endif + +int main() { + BODY; +} Index: clang/tools/driver/cc1gen_reproducer_main.cpp =================================================================== --- clang/tools/driver/cc1gen_reproducer_main.cpp +++ clang/tools/driver/cc1gen_reproducer_main.cpp @@ -127,8 +127,9 @@ for (const auto &J : C->getJobs()) { if (const Command *Cmd = dyn_cast(&J)) { Driver::CompilationDiagnosticReport Report; + SmallVector Args{Argv.begin(), Argv.end()}; TheDriver.generateCompilationDiagnostics( - *C, *Cmd, generateReproducerMetaInfo(Info), &Report); + *C, *Cmd, Args, generateReproducerMetaInfo(Info), &Report); return Report; } } Index: clang/tools/driver/driver.cpp =================================================================== --- clang/tools/driver/driver.cpp +++ clang/tools/driver/driver.cpp @@ -482,6 +482,15 @@ } std::unique_ptr C(TheDriver.BuildCompilation(Args)); + + int ReproLevel = 0; + if (Arg *A = C->getArgs().getLastArg(options::OPT_femit_reproducer)) + ReproLevel = llvm::StringSwitch(A->getValue()) + .Case("off", 0) + .Case("on-error", 1) + .Case("always", 2) + .Default(0); + int Res = 1; bool IsCrash = false; if (C && !C->containsError()) { @@ -527,12 +536,18 @@ IsCrash |= CommandRes > 128; #endif if (IsCrash) { - TheDriver.generateCompilationDiagnostics(*C, *FailingCommand); + TheDriver.generateCompilationDiagnostics(*C, *FailingCommand, Args); + break; + } else if (ReproLevel == 1) { + TheDriver.generateReproFile(*C, Args); break; } } } + if (ReproLevel > 1) + TheDriver.generateReproFile(*C, Args); + Diags.getClient()->finish(); if (!UseNewCC1Process && IsCrash) { Index: flang/tools/flang-driver/driver.cpp =================================================================== --- flang/tools/flang-driver/driver.cpp +++ flang/tools/flang-driver/driver.cpp @@ -134,7 +134,7 @@ isCrash |= CommandRes == 3; #endif if (isCrash) { - theDriver.generateCompilationDiagnostics(*c, *failingCommand); + theDriver.generateCompilationDiagnostics(*c, *failingCommand, args); break; } }