Index: clang/include/clang/Driver/Driver.h =================================================================== --- clang/include/clang/Driver/Driver.h +++ clang/include/clang/Driver/Driver.h @@ -458,6 +458,8 @@ llvm::SmallVector TemporaryFiles; }; + void generateReproFile(Compilation &C, llvm::SmallVectorImpl &Args); + /// generateCompilationDiagnostics - Generate diagnostics information /// including preprocessed source file(s). /// Index: clang/include/clang/Driver/Options.td =================================================================== --- clang/include/clang/Driver/Options.td +++ clang/include/clang/Driver/Options.td @@ -1389,6 +1389,10 @@ def fcrash_diagnostics_dir : Joined<["-"], "fcrash-diagnostics-dir=">, Group, Flags<[NoArgumentUnused, CoreOption]>, HelpText<"Put crash-report files in ">, MetaVarName<"">; +def freproducer_dir : Joined<["-"], "freproducer-dir=">, Group, Flags<[NoArgumentUnused, CoreOption]>, + HelpText<"Put reproducer file in ">, MetaVarName<"">; +def femit_reproducer : Joined<["-"], "femit-reproducer=">, Group, Flags<[NoArgumentUnused, CoreOption]>, + HelpText<"Emit reproducer on (option: off (default), 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,6 +1391,93 @@ return false; } +static std::pair, llvm::SmallString<64>> +openReproTarFile(Driver &D, Compilation &C) { + SmallString<64> TmpName; + if (Arg *A = C.getArgs().getLastArg(options::OPT_freproducer_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}; +} + +void Driver::generateReproFile(Compilation &C, + SmallVectorImpl &Args) { + std::unique_ptr TarWriter; + SmallString<64> 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); + + llvm::errs() << "Wrote reproducer file to: '" << ReproFileName << "'\n"; +} + // 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. 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 -freproducer-dir=%t +// RUN: not stat %t/*.tar +// RUN: not %clang -DBODY=error %s -freproducer-dir=%t +// RUN: not stat %t/*.tar + +// Check that reproducers aren't created when no error occured +// RUN: %clang %s -femit-reproducer=off -freproducer-dir=%t +// RUN: not stat %t/*.tar +// RUN: %clang %s -femit-reproducer=on-error -freproducer-dir=%t +// RUN: not stat %t/*.tar + +// Check reproducers are created when an error occured +// RUN: not %clang -DBODY=error %s -femit-reproducer=on-error -freproducer-dir=%t +// RUN: stat %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 -freproducer-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/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()) { @@ -529,10 +538,16 @@ if (IsCrash) { TheDriver.generateCompilationDiagnostics(*C, *FailingCommand); break; + } else if (ReproLevel == 1) { + TheDriver.generateReproFile(*C, Args); + break; } } } + if (ReproLevel > 1) + TheDriver.generateReproFile(*C, Args); + Diags.getClient()->finish(); if (!UseNewCC1Process && IsCrash) {