Index: clang/include/clang/Driver/Driver.h =================================================================== --- clang/include/clang/Driver/Driver.h +++ clang/include/clang/Driver/Driver.h @@ -12,6 +12,7 @@ #include "clang/Basic/Diagnostic.h" #include "clang/Basic/LLVM.h" #include "clang/Driver/Action.h" +#include "clang/Driver/DriverDiagnostic.h" #include "clang/Driver/InputInfo.h" #include "clang/Driver/Options.h" #include "clang/Driver/Phases.h" @@ -276,11 +277,6 @@ unsigned ProbePrecompiled : 1; public: - /// Force clang to emit reproducer for driver invocation. This is enabled - /// indirectly by setting FORCE_CLANG_DIAGNOSTICS_CRASH environment variable - /// or when using the -gen-reproducer driver flag. - unsigned GenReproducer : 1; - // getFinalPhase - Determine which compilation mode we are in and record // which option we used to determine the final phase. // TODO: Much of what getFinalPhase returns are not actually true compiler @@ -505,6 +501,35 @@ 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), + }; + + bool maybeGenerateCompilationDiagnostics( + CommandStatus CS, ReproLevel Level, Compilation &C, + const Command &FailingCommand, StringRef AdditionalInformation = "", + CompilationDiagnosticReport *GeneratedReport = nullptr) { + if (static_cast(CS) > static_cast(Level)) + return false; + if (CS != CommandStatus::Crash) + Diags.Report(diag::err_drv_force_crash) + << !::getenv("FORCE_CLANG_DIAGNOSTICS_CRASH"); + // Hack to ensure that diagnostic notes get emitted. + Diags.setLastDiagnosticIgnored(false); + generateCompilationDiagnostics(C, FailingCommand, AdditionalInformation, + GeneratedReport); + return true; + } + /// @} /// @name Helper Methods /// @{ Index: clang/include/clang/Driver/Options.td =================================================================== --- clang/include/clang/Driver/Options.td +++ clang/include/clang/Driver/Options.td @@ -558,7 +558,10 @@ def arcmt_migrate_emit_arc_errors : Flag<["-"], "arcmt-migrate-emit-errors">, HelpText<"Emit ARC errors even if the migrator can fix them">, Flags<[CC1Option]>, MarshallingInfoFlag>; +def gen_reproducer_eq: Joined<["-"], "gen-reproducer=">, Flags<[NoArgumentUnused, CoreOption]>, + HelpText<"Emit reproducer on (option: off, crash (default), error, always)">; def gen_reproducer: Flag<["-"], "gen-reproducer">, InternalDebugOpt, + Alias, AliasArgs<["always"]>, HelpText<"Auto-generates preprocessed source files and a reproduction script">; def gen_cdb_fragment_path: Separate<["-"], "gen-cdb-fragment-path">, InternalDebugOpt, HelpText<"Emit a compilation database fragment to the specified directory">; @@ -1397,6 +1400,7 @@ def fconstexpr_backtrace_limit_EQ : Joined<["-"], "fconstexpr-backtrace-limit=">, Group; 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 fcrash_diagnostics_dir : Joined<["-"], "fcrash-diagnostics-dir=">, Group, Flags<[NoArgumentUnused, CoreOption]>, Index: clang/lib/Driver/Driver.cpp =================================================================== --- clang/lib/Driver/Driver.cpp +++ clang/lib/Driver/Driver.cpp @@ -198,8 +198,7 @@ CCPrintOptions(false), CCPrintHeaders(false), CCLogDiagnostics(false), CCGenDiagnostics(false), CCPrintProcessStats(false), TargetTriple(TargetTriple), Saver(Alloc), CheckInputsExist(true), - ProbePrecompiled(true), GenReproducer(false), - SuppressMissingInputWarning(false) { + ProbePrecompiled(true), SuppressMissingInputWarning(false) { // Provide a sane fallback if no VFS is specified. if (!this->VFS) this->VFS = llvm::vfs::getRealFileSystem(); @@ -1217,9 +1216,6 @@ CCCPrintBindings = Args.hasArg(options::OPT_ccc_print_bindings); if (const Arg *A = Args.getLastArg(options::OPT_ccc_gcc_name)) CCCGenericGCCName = A->getValue(); - GenReproducer = Args.hasFlag(options::OPT_gen_reproducer, - options::OPT_fno_crash_diagnostics, - !!::getenv("FORCE_CLANG_DIAGNOSTICS_CRASH")); // Process -fproc-stat-report options. if (const Arg *A = Args.getLastArg(options::OPT_fproc_stat_report_EQ)) { Index: clang/test/Driver/crash-report-crashfile.m =================================================================== --- clang/test/Driver/crash-report-crashfile.m +++ clang/test/Driver/crash-report-crashfile.m @@ -17,8 +17,8 @@ @import simple; const int x = MODULE_MACRO; -// CRASH_ENV: failing because environment variable 'FORCE_CLANG_DIAGNOSTICS_CRASH' is set // CRASH_ENV: PLEASE submit a bug report to {{.*}} and include the crash backtrace, preprocessed source, and associated run script. +// CRASH_ENV: failing because environment variable 'FORCE_CLANG_DIAGNOSTICS_CRASH' is set // CRASH_ENV: Preprocessed source(s) and associated run script(s) are located at: // CRASH_ENV-NEXT: note: diagnostic msg: {{.*}}.m // CRASH_ENV-NEXT: note: diagnostic msg: {{.*}}.cache Index: clang/test/Driver/emit-reproducer.c =================================================================== --- /dev/null +++ clang/test/Driver/emit-reproducer.c @@ -0,0 +1,41 @@ +// RUN: rm -rf %t && mkdir %t + +// RUN: echo "%s -fcrash-diagnostics-dir=%t -fsyntax-only" > %t.rsp + +// RUN: not %clang -DFATAL @%t.rsp -gen-reproducer=off 2>&1 | FileCheck %s --check-prefix=NOT +// RUN: not %clang -DFATAL @%t.rsp -fno-crash-diagnostics 2>&1 | FileCheck %s --check-prefix=NOT +// RUN: not %clang -DFATAL @%t.rsp 2>&1 | FileCheck %s +// RUN: not %clang -DFATAL @%t.rsp -gen-reproducer=crash 2>&1 | FileCheck %s +// RUN: not %clang -DFATAL @%t.rsp -gen-reproducer=error 2>&1 | FileCheck %s +// RUN: not %clang -DFATAL @%t.rsp -gen-reproducer=always 2>&1 | FileCheck %s +// RUN: not %clang -DFATAL @%t.rsp -gen-reproducer 2>&1 | FileCheck %s + +// RUN: not %clang -DERROR @%t.rsp -gen-reproducer=off 2>&1 | FileCheck %s --check-prefix=NOT +// RUN: not %clang -DERROR @%t.rsp -fno-crash-diagnostics 2>&1 | FileCheck %s --check-prefix=NOT +// RUN: not %clang -DERROR @%t.rsp 2>&1 | FileCheck %s --check-prefix=NOT +// RUN: not %clang -DERROR @%t.rsp -gen-reproducer=crash 2>&1 | FileCheck %s --check-prefix=NOT +// RUN: not %clang -DERROR @%t.rsp -gen-reproducer=error 2>&1 | FileCheck %s +// RUN: not %clang -DERROR @%t.rsp -gen-reproducer=always 2>&1 | FileCheck %s +// RUN: not %clang -DERROR @%t.rsp -gen-reproducer 2>&1 | FileCheck %s + +// RUN: %clang @%t.rsp -gen-reproducer=off 2>&1 | FileCheck %s --check-prefix=NOT --allow-empty +// RUN: %clang @%t.rsp -fno-crash-diagnostics 2>&1 | FileCheck %s --check-prefix=NOT --allow-empty +// RUN: %clang @%t.rsp 2>&1 | FileCheck %s --check-prefix=NOT --allow-empty +// RUN: %clang @%t.rsp -gen-reproducer=crash 2>&1 | FileCheck %s --check-prefix=NOT --allow-empty +// RUN: %clang @%t.rsp -gen-reproducer=error 2>&1 | FileCheck %s --check-prefix=NOT --allow-empty +// RUN: not %clang @%t.rsp -gen-reproducer=always 2>&1 | FileCheck %s +// RUN: not %clang @%t.rsp -gen-reproducer 2>&1 | FileCheck %s + +// RUN: not %clang -gen-reproducer=badvalue 2>&1 | FileCheck %s --check-prefix=BAD-VALUE +// BAD-VALUE: Unknown value for -gen-reproducer=: 'badvalue' + +// 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,32 +482,37 @@ } std::unique_ptr C(TheDriver.BuildCompilation(Args)); + + Driver::ReproLevel ReproLevel = Driver::ReproLevel::OnCrash; + if (Arg *A = C->getArgs().getLastArg(options::OPT_gen_reproducer_eq)) { + auto Level = llvm::StringSwitch>(A->getValue()) + .Case("off", Driver::ReproLevel::Off) + .Case("crash", Driver::ReproLevel::OnCrash) + .Case("error", Driver::ReproLevel::OnError) + .Case("always", Driver::ReproLevel::Always) + .Default(None); + if (!Level) { + llvm::errs() << "Unknown value for " << A->getSpelling() << ": '" + << A->getValue() << "'\n"; + return 1; + } + ReproLevel = *Level; + } + if (!!::getenv("FORCE_CLANG_DIAGNOSTICS_CRASH")) + ReproLevel = Driver::ReproLevel::Always; + 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); - // Force a crash to test the diagnostics. - if (TheDriver.GenReproducer) { - Diags.Report(diag::err_drv_force_crash) - << !::getenv("FORCE_CLANG_DIAGNOSTICS_CRASH"); - - // Pretend that every command failed. - FailingCommands.clear(); - for (const auto &J : C->getJobs()) - if (const Command *C = dyn_cast(&J)) - FailingCommands.push_back(std::make_pair(-1, C)); - - // Print the bug report message that would be printed if we did actually - // crash, but only if we're crashing due to FORCE_CLANG_DIAGNOSTICS_CRASH. - if (::getenv("FORCE_CLANG_DIAGNOSTICS_CRASH")) - llvm::dbgs() << llvm::getBugReportMsg(); - } - for (const auto &P : FailingCommands) { int CommandRes = P.first; - const Command *FailingCommand = P.second; + FailingCommand = P.second; if (!Res) Res = CommandRes; @@ -526,13 +531,21 @@ // 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; - } } } + // Print the bug report message that would be printed if we did actually + // crash, but only if we're crashing due to FORCE_CLANG_DIAGNOSTICS_CRASH. + if (::getenv("FORCE_CLANG_DIAGNOSTICS_CRASH")) + llvm::dbgs() << llvm::getBugReportMsg(); + if (TheDriver.maybeGenerateCompilationDiagnostics(CommandStatus, ReproLevel, + *C, *FailingCommand)) + Res = 1; + Diags.getClient()->finish(); if (!UseNewCC1Process && IsCrash) {