Index: clang/include/clang/Driver/Driver.h =================================================================== --- clang/include/clang/Driver/Driver.h +++ clang/include/clang/Driver/Driver.h @@ -18,6 +18,7 @@ #include "clang/Driver/ToolChain.h" #include "clang/Driver/Types.h" #include "clang/Driver/Util.h" +#include "llvm/ADT/Optional.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/Option/Arg.h" @@ -466,6 +467,12 @@ StringRef AdditionalInformation = "", CompilationDiagnosticReport *GeneratedReport = nullptr); + llvm::Optional + generateReproducerFile(Compilation &C, + const Command *FailingCommand = nullptr, + StringRef AdditionalInformation = "", + CompilationDiagnosticReport *Report = nullptr); + /// @} /// @name Helper Methods /// @{ Index: clang/include/clang/Driver/Options.td =================================================================== --- clang/include/clang/Driver/Options.td +++ clang/include/clang/Driver/Options.td @@ -1384,11 +1384,14 @@ MarshallingInfoFlag>; def fconstexpr_backtrace_limit_EQ : Joined<["-"], "fconstexpr-backtrace-limit=">, Group; -def fno_crash_diagnostics : Flag<["-"], "fno-crash-diagnostics">, Group, Flags<[NoArgumentUnused, CoreOption]>, - HelpText<"Disable auto-generation of preprocessed source files and a script for reproduction during a clang crash">; 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, on-crash (default), on-error, always)">; +def fno_crash_diagnostics : Flag<["-"], "fno-crash-diagnostics">, Group, Flags<[NoArgumentUnused, CoreOption]>, + Alias, AliasArgs<["off"]>, + HelpText<"Disable auto-generation of preprocessed source files and a script for reproduction during a clang crash">; 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 @@ -1420,28 +1420,17 @@ return {std::move(*TarWriterOrErr), TmpName.c_str()}; } -// 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) { - if (C.getArgs().hasArg(options::OPT_fno_crash_diagnostics)) - return; - - // Don't try to generate diagnostics for link or dsymutil jobs. - if (FailingCommand.getCreator().isLinkJob() || - FailingCommand.getCreator().isDsymutilJob()) - return; - - // Print the version of the compiler. - PrintVersion(C, llvm::errs()); - +llvm::Optional +Driver::generateReproducerFile(Compilation &C, const Command *FailingCommand, + StringRef AdditionalInformation, + CompilationDiagnosticReport *Report) { // Suppress driver output and emit preprocessor output to temp file. CCGenDiagnostics = true; // Save the original job command(s). - Command Cmd = FailingCommand; + std::unique_ptr Cmd; + if (FailingCommand) + Cmd = std::make_unique(*FailingCommand); // Keep track of whether we produce any errors while trying to produce // preprocessed sources. @@ -1480,7 +1469,7 @@ Diag(clang::diag::note_drv_command_failed_diag_msg) << "Error generating preprocessed source(s) - " "no preprocessable inputs."; - return; + return {}; } // Don't attempt to generate preprocessed files if multiple -arch options are @@ -1496,7 +1485,7 @@ Diag(clang::diag::note_drv_command_failed_diag_msg) << "Error generating preprocessed source(s) - cannot generate " "preprocessed source with multiple -arch options."; - return; + return {}; } // Construct the list of abstract actions to perform for this compilation. On @@ -1513,7 +1502,7 @@ if (Trap.hasErrorOccurred()) { Diag(clang::diag::note_drv_command_failed_diag_msg) << "Error generating preprocessed source(s)."; - return; + return {}; } // Generate preprocessed output. @@ -1524,14 +1513,14 @@ if (!FailingCommands.empty()) { Diag(clang::diag::note_drv_command_failed_diag_msg) << "Error generating preprocessed source(s)."; - return; + return {}; } const ArgStringList &TempFiles = C.getTempFiles(); if (TempFiles.empty()) { Diag(clang::diag::note_drv_command_failed_diag_msg) << "Error generating preprocessed source(s)."; - return; + return {}; } std::unique_ptr TarWriter; @@ -1540,7 +1529,7 @@ if (!TarWriter) { Diag(clang::diag::note_drv_command_failed_diag_msg) << "Error generating preprocessed source(s). - cannot open tar file"; - return; + return {}; } std::function WriteFileToTar = @@ -1601,22 +1590,22 @@ ScriptOS << "# Crash reproducer for " << getClangFullVersion() << "\n" << "# Driver args: "; printArgList(ScriptOS, C.getInputArgs()); - ScriptOS << "# Original command: "; - Cmd.Print(ScriptOS, "\n", /*Quote=*/true); - Cmd.Print(ScriptOS, "\n", /*Quote=*/true, &CrashInfo); + if (Cmd) { + ScriptOS << "# Original command: "; + Cmd->Print(ScriptOS, "\n", /*Quote=*/true); + Cmd->Print(ScriptOS, "\n", /*Quote=*/true, &CrashInfo); + } else { + for (auto Cmd : C.getJobs()) { + ScriptOS << "# " << Cmd.getCreator().getName(); + Cmd.Print(ScriptOS, "\n", /*Quote=*/true, &CrashInfo); + } + } if (!AdditionalInformation.empty()) ScriptOS << "\n# Additional information: " << AdditionalInformation << "\n"; if (Report) Report->TemporaryFiles.push_back(std::string(Script.str())); TarWriter->append("repro.sh", Storage); - Diag(clang::diag::note_drv_command_failed_diag_msg) - << "\n********************\n\n" - "PLEASE ATTACH THE FOLLOWING FILES TO THE BUG REPORT:\n" - "Preprocessed source(s) and associated run script(s) are located at:"; - - 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; @@ -1634,6 +1623,36 @@ } } + 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) { + if (C.getArgs().hasArg(options::OPT_fno_crash_diagnostics)) + return; + + // Don't try to generate diagnostics for link or dsymutil jobs. + if (FailingCommand.getCreator().isLinkJob() || + FailingCommand.getCreator().isDsymutilJob()) + return; + + // Print the version of the compiler. + PrintVersion(C, llvm::errs()); + + Diag(clang::diag::note_drv_command_failed_diag_msg) + << "\n********************\n\n" + "PLEASE ATTACH THE FOLLOWING FILES TO THE BUG REPORT:\n" + "Preprocessed source(s) and associated run script(s) are located at:"; + + llvm::Optional ReproFileName = + generateReproducerFile(C, &FailingCommand, AdditionalInformation, Report); + if (ReproFileName) + Diag(clang::diag::note_drv_command_failed_diag_msg) << *ReproFileName; + Diag(clang::diag::note_drv_command_failed_diag_msg) << "\n\n********************"; } Index: clang/test/Driver/emit-reproducer.c =================================================================== --- /dev/null +++ clang/test/Driver/emit-reproducer.c @@ -0,0 +1,40 @@ +// RUN: rm -rf %t +// RUN: mkdir %t + +// 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 + +// Check that reproducers aren't created when -femit-reproducer isn't specified +// and the compiler didn't crash. +// 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 + +// 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 "*/input/emit-reproducer.c" > %t.c +// RUN: diff %t.c %s +// RUN: rm -f %t/*.tar + +// TAR-LAYOUT: {{.*}}/input/emit-reproducer.c +// TAR-LAYOUT-NEXT: {{.*}}/repro.sh +// TAR-LAYOUT-NEXT: {{.*}}/tmp/emit-reproducer-{{.*}}.c + +#ifndef BODY +#define BODY +#endif + +int main() { + BODY; +} \ No newline at end of file Index: clang/tools/driver/driver.cpp =================================================================== --- clang/tools/driver/driver.cpp +++ clang/tools/driver/driver.cpp @@ -482,6 +482,24 @@ } std::unique_ptr C(TheDriver.BuildCompilation(Args)); + + enum ReproLevel { + Off, + OnCrash, + OnError, + Always, + }; + + ReproLevel ReproLvl = OnCrash; + bool ReproEmitted = false; + if (Arg *A = C->getArgs().getLastArg(options::OPT_femit_reproducer)) + ReproLvl = llvm::StringSwitch(A->getValue()) + .Case("off", Off) + .Case("on-crash", OnCrash) + .Case("on-error", OnError) + .Case("always", Always) + .Default(OnCrash); + int Res = 1; bool IsCrash = false; if (C && !C->containsError()) { @@ -526,13 +544,28 @@ // https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xcu_chap02.html IsCrash |= CommandRes > 128; #endif - if (IsCrash) { + if (IsCrash && ReproLvl != Off) { TheDriver.generateCompilationDiagnostics(*C, *FailingCommand); + ReproEmitted = true; break; + } else if (ReproLvl >= OnError) { + llvm::Optional ReproFile = + TheDriver.generateReproducerFile(*C); + if (!ReproFile) + break; + llvm::errs() << "Reproducer file emitted in: " << *ReproFile << '\n'; + ReproEmitted = true; } } } + if (ReproLvl == Always && !ReproEmitted) { + llvm::Optional ReproFile = + TheDriver.generateReproducerFile(*C); + if (ReproFile) + llvm::errs() << "Reproducer file emitted in: " << *ReproFile << '\n'; + } + Diags.getClient()->finish(); if (!UseNewCC1Process && IsCrash) {