Index: clang/include/clang/Driver/Driver.h =================================================================== --- clang/include/clang/Driver/Driver.h +++ clang/include/clang/Driver/Driver.h @@ -505,6 +505,31 @@ StringRef AdditionalInformation = "", CompilationDiagnosticReport *GeneratedReport = nullptr); + enum class CommandStatus { + Crash = 1, + Error, + Ok, + }; + + enum class ReproLevel { + Off = 0, + OnCrash = static_cast(CommandStatus::Crash), + OnError = static_cast(CommandStatus::Error), + Always = static_cast(CommandStatus::Ok), + }; + + void generateCompilationDiagnosticsIfNecessary( + CommandStatus CS, ReproLevel Level, Compilation &C, + const Command &FailingCommand, StringRef AdditionalInformation = "", + CompilationDiagnosticReport *GeneratedReport = nullptr) { + if (static_cast(CS) > static_cast(Level)) + return; + // Hack to ensure that diagnostic notes get emitted. + Diags.setLastDiagnosticIgnored(false); + generateCompilationDiagnostics(C, FailingCommand, AdditionalInformation, + GeneratedReport); + } + /// @} /// @name Helper Methods /// @{ Index: clang/include/clang/Driver/Options.td =================================================================== --- clang/include/clang/Driver/Options.td +++ clang/include/clang/Driver/Options.td @@ -1385,11 +1385,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/test/Driver/emit-reproducer.c =================================================================== --- /dev/null +++ clang/test/Driver/emit-reproducer.c @@ -0,0 +1,33 @@ +// RUN: rm -rf %t; mkdir %t + +// RUN: not %clang -DFATAL %s -fcrash-diagnostics-dir=%t -femit-reproducer=off 2>&1 | FileCheck %s --check-prefix=NOT +// RUN: not %clang -DFATAL %s -fcrash-diagnostics-dir=%t -fno-crash-diagnostics 2>&1 | FileCheck %s --check-prefix=NOT +// RUN: not %clang -DFATAL %s -fcrash-diagnostics-dir=%t 2>&1 | FileCheck %s +// RUN: not %clang -DFATAL %s -fcrash-diagnostics-dir=%t -femit-reproducer=on-crash 2>&1 | FileCheck %s +// RUN: not %clang -DFATAL %s -fcrash-diagnostics-dir=%t -femit-reproducer=on-error 2>&1 | FileCheck %s +// RUN: not %clang -DFATAL %s -fcrash-diagnostics-dir=%t -femit-reproducer=always 2>&1 | FileCheck %s + +// RUN: not %clang -DERROR %s -fcrash-diagnostics-dir=%t -femit-reproducer=off 2>&1 | FileCheck %s --check-prefix=NOT +// RUN: not %clang -DERROR %s -fcrash-diagnostics-dir=%t -fno-crash-diagnostics 2>&1 | FileCheck %s --check-prefix=NOT +// RUN: not %clang -DERROR %s -fcrash-diagnostics-dir=%t 2>&1 | FileCheck %s --check-prefix=NOT +// RUN: not %clang -DERROR %s -fcrash-diagnostics-dir=%t -femit-reproducer=on-crash 2>&1 | FileCheck %s --check-prefix=NOT +// RUN: not %clang -DERROR %s -fcrash-diagnostics-dir=%t -femit-reproducer=on-error 2>&1 | FileCheck %s +// RUN: not %clang -DERROR %s -fcrash-diagnostics-dir=%t -femit-reproducer=always 2>&1 | FileCheck %s + +// RUN: %clang %s -fcrash-diagnostics-dir=%t -femit-reproducer=off 2>&1 | FileCheck %s --check-prefix=NOT --allow-empty +// RUN: %clang %s -fcrash-diagnostics-dir=%t -fno-crash-diagnostics 2>&1 | FileCheck %s --check-prefix=NOT --allow-empty +// RUN: %clang %s -fcrash-diagnostics-dir=%t 2>&1 | FileCheck %s --check-prefix=NOT --allow-empty +// RUN: %clang %s -fcrash-diagnostics-dir=%t -femit-reproducer=on-crash 2>&1 | FileCheck %s --check-prefix=NOT --allow-empty +// RUN: %clang %s -fcrash-diagnostics-dir=%t -femit-reproducer=on-error 2>&1 | FileCheck %s --check-prefix=NOT --allow-empty +// RUN: %clang %s -fcrash-diagnostics-dir=%t -femit-reproducer=always 2>&1 | FileCheck %s + +// CHECK: note: diagnostic msg: {{.*}}emit-reproducer-{{.*}}.c +// NOT-NOT: note: diagnostic msg: {{.*}}emit-reproducer-{{.*}}.c + +#ifdef FATAL +#pragma clang __debug crash +#elif ERROR +int main +#else +int main() {} +#endif Index: clang/tools/driver/driver.cpp =================================================================== --- clang/tools/driver/driver.cpp +++ clang/tools/driver/driver.cpp @@ -482,8 +482,21 @@ } std::unique_ptr C(TheDriver.BuildCompilation(Args)); + + Driver::ReproLevel ReproLevel = Driver::ReproLevel::OnCrash; + if (Arg *A = C->getArgs().getLastArg(options::OPT_femit_reproducer)) + ReproLevel = llvm::StringSwitch(A->getValue()) + .Case("off", Driver::ReproLevel::Off) + .Case("on-crash", Driver::ReproLevel::OnCrash) + .Case("on-error", Driver::ReproLevel::OnError) + .Case("always", Driver::ReproLevel::Always) + .Default(Driver::ReproLevel::OnCrash); + int Res = 1; bool IsCrash = false; + Driver::CommandStatus CommandStatus = Driver::CommandStatus::Ok; + // Pretend the first command failed if ReproStatus is Always. + const Command *FailingCommand = &*C->getJobs().begin(); if (C && !C->containsError()) { SmallVector, 4> FailingCommands; Res = TheDriver.ExecuteCompilation(*C, FailingCommands); @@ -507,7 +520,7 @@ for (const auto &P : FailingCommands) { int CommandRes = P.first; - const Command *FailingCommand = P.second; + FailingCommand = P.second; if (!Res) Res = CommandRes; @@ -526,13 +539,16 @@ // https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xcu_chap02.html IsCrash |= CommandRes > 128; #endif - if (IsCrash) { - TheDriver.generateCompilationDiagnostics(*C, *FailingCommand); + CommandStatus = + IsCrash ? Driver::CommandStatus::Crash : Driver::CommandStatus::Error; + if (IsCrash) break; - } } } + TheDriver.generateCompilationDiagnosticsIfNecessary(CommandStatus, ReproLevel, + *C, *FailingCommand); + Diags.getClient()->finish(); if (!UseNewCC1Process && IsCrash) {