Skip to content

Commit 684c080

Browse files
committed
[Clang] Extend -gen-reproducer flag
-gen-reproducer causes crash reproduction to be emitted even when clang didn't crash, and now can optionally take an argument of never, on-crash (default), on-error and always. Differential revision: https://reviews.llvm.org/D120201
1 parent af43094 commit 684c080

File tree

5 files changed

+104
-31
lines changed

5 files changed

+104
-31
lines changed

clang/include/clang/Driver/Driver.h

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -276,11 +276,6 @@ class Driver {
276276
unsigned ProbePrecompiled : 1;
277277

278278
public:
279-
/// Force clang to emit reproducer for driver invocation. This is enabled
280-
/// indirectly by setting FORCE_CLANG_DIAGNOSTICS_CRASH environment variable
281-
/// or when using the -gen-reproducer driver flag.
282-
unsigned GenReproducer : 1;
283-
284279
// getFinalPhase - Determine which compilation mode we are in and record
285280
// which option we used to determine the final phase.
286281
// TODO: Much of what getFinalPhase returns are not actually true compiler
@@ -505,6 +500,32 @@ class Driver {
505500
StringRef AdditionalInformation = "",
506501
CompilationDiagnosticReport *GeneratedReport = nullptr);
507502

503+
enum class CommandStatus {
504+
Crash = 1,
505+
Error,
506+
Ok,
507+
};
508+
509+
enum class ReproLevel {
510+
Off = 0,
511+
OnCrash = static_cast<int>(CommandStatus::Crash),
512+
OnError = static_cast<int>(CommandStatus::Error),
513+
Always = static_cast<int>(CommandStatus::Ok),
514+
};
515+
516+
bool maybeGenerateCompilationDiagnostics(
517+
CommandStatus CS, ReproLevel Level, Compilation &C,
518+
const Command &FailingCommand, StringRef AdditionalInformation = "",
519+
CompilationDiagnosticReport *GeneratedReport = nullptr) {
520+
if (static_cast<int>(CS) > static_cast<int>(Level))
521+
return false;
522+
// Hack to ensure that diagnostic notes get emitted.
523+
Diags.setLastDiagnosticIgnored(false);
524+
generateCompilationDiagnostics(C, FailingCommand, AdditionalInformation,
525+
GeneratedReport);
526+
return true;
527+
}
528+
508529
/// @}
509530
/// @name Helper Methods
510531
/// @{

clang/include/clang/Driver/Options.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,10 @@ def arcmt_migrate_report_output : Separate<["-"], "arcmt-migrate-report-output">
558558
def arcmt_migrate_emit_arc_errors : Flag<["-"], "arcmt-migrate-emit-errors">,
559559
HelpText<"Emit ARC errors even if the migrator can fix them">, Flags<[CC1Option]>,
560560
MarshallingInfoFlag<FrontendOpts<"ARCMTMigrateEmitARCErrors">>;
561+
def gen_reproducer_eq: Joined<["-"], "gen-reproducer=">, Flags<[NoArgumentUnused, CoreOption]>,
562+
HelpText<"Emit reproducer on (option: off, crash (default), error, always)">;
561563
def gen_reproducer: Flag<["-"], "gen-reproducer">, InternalDebugOpt,
564+
Alias<gen_reproducer_eq>, AliasArgs<["always"]>,
562565
HelpText<"Auto-generates preprocessed source files and a reproduction script">;
563566
def gen_cdb_fragment_path: Separate<["-"], "gen-cdb-fragment-path">, InternalDebugOpt,
564567
HelpText<"Emit a compilation database fragment to the specified directory">;
@@ -1397,6 +1400,7 @@ def fexperimental_new_constant_interpreter : Flag<["-"], "fexperimental-new-cons
13971400
def fconstexpr_backtrace_limit_EQ : Joined<["-"], "fconstexpr-backtrace-limit=">,
13981401
Group<f_Group>;
13991402
def fno_crash_diagnostics : Flag<["-"], "fno-crash-diagnostics">, Group<f_clang_Group>, Flags<[NoArgumentUnused, CoreOption]>,
1403+
Alias<gen_reproducer_eq>, AliasArgs<["off"]>,
14001404
HelpText<"Disable auto-generation of preprocessed source files and a script for reproduction during a clang crash">;
14011405
def fcrash_diagnostics_dir : Joined<["-"], "fcrash-diagnostics-dir=">,
14021406
Group<f_clang_Group>, Flags<[NoArgumentUnused, CoreOption]>,

clang/lib/Driver/Driver.cpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,7 @@ Driver::Driver(StringRef ClangExecutable, StringRef TargetTriple,
198198
CCPrintOptions(false), CCPrintHeaders(false), CCLogDiagnostics(false),
199199
CCGenDiagnostics(false), CCPrintProcessStats(false),
200200
TargetTriple(TargetTriple), Saver(Alloc), CheckInputsExist(true),
201-
ProbePrecompiled(true), GenReproducer(false),
202-
SuppressMissingInputWarning(false) {
201+
ProbePrecompiled(true), SuppressMissingInputWarning(false) {
203202
// Provide a sane fallback if no VFS is specified.
204203
if (!this->VFS)
205204
this->VFS = llvm::vfs::getRealFileSystem();
@@ -1217,9 +1216,6 @@ Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
12171216
CCCPrintBindings = Args.hasArg(options::OPT_ccc_print_bindings);
12181217
if (const Arg *A = Args.getLastArg(options::OPT_ccc_gcc_name))
12191218
CCCGenericGCCName = A->getValue();
1220-
GenReproducer = Args.hasFlag(options::OPT_gen_reproducer,
1221-
options::OPT_fno_crash_diagnostics,
1222-
!!::getenv("FORCE_CLANG_DIAGNOSTICS_CRASH"));
12231219

12241220
// Process -fproc-stat-report options.
12251221
if (const Arg *A = Args.getLastArg(options::OPT_fproc_stat_report_EQ)) {

clang/test/Driver/emit-reproducer.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// RUN: rm -rf %t && mkdir %t
2+
3+
// RUN: not %clang -DFATAL %s -fcrash-diagnostics-dir=%t -gen-reproducer=off 2>&1 | FileCheck %s --check-prefix=NOT
4+
// RUN: not %clang -DFATAL %s -fcrash-diagnostics-dir=%t -fno-crash-diagnostics 2>&1 | FileCheck %s --check-prefix=NOT
5+
// RUN: not %clang -DFATAL %s -fcrash-diagnostics-dir=%t 2>&1 | FileCheck %s
6+
// RUN: not %clang -DFATAL %s -fcrash-diagnostics-dir=%t -gen-reproducer=crash 2>&1 | FileCheck %s
7+
// RUN: not %clang -DFATAL %s -fcrash-diagnostics-dir=%t -gen-reproducer=error 2>&1 | FileCheck %s
8+
// RUN: not %clang -DFATAL %s -fcrash-diagnostics-dir=%t -gen-reproducer=always 2>&1 | FileCheck %s
9+
// RUN: not %clang -DFATAL %s -fcrash-diagnostics-dir=%t -gen-reproducer 2>&1 | FileCheck %s
10+
11+
// RUN: not %clang -DERROR %s -fcrash-diagnostics-dir=%t -gen-reproducer=off 2>&1 | FileCheck %s --check-prefix=NOT
12+
// RUN: not %clang -DERROR %s -fcrash-diagnostics-dir=%t -fno-crash-diagnostics 2>&1 | FileCheck %s --check-prefix=NOT
13+
// RUN: not %clang -DERROR %s -fcrash-diagnostics-dir=%t 2>&1 | FileCheck %s --check-prefix=NOT
14+
// RUN: not %clang -DERROR %s -fcrash-diagnostics-dir=%t -gen-reproducer=crash 2>&1 | FileCheck %s --check-prefix=NOT
15+
// RUN: not %clang -DERROR %s -fcrash-diagnostics-dir=%t -gen-reproducer=error 2>&1 | FileCheck %s
16+
// RUN: not %clang -DERROR %s -fcrash-diagnostics-dir=%t -gen-reproducer=always 2>&1 | FileCheck %s
17+
// RUN: not %clang -DERROR %s -fcrash-diagnostics-dir=%t -gen-reproducer 2>&1 | FileCheck %s
18+
19+
// RUN: %clang %s -fcrash-diagnostics-dir=%t -gen-reproducer=off 2>&1 | FileCheck %s --check-prefix=NOT --allow-empty
20+
// RUN: %clang %s -fcrash-diagnostics-dir=%t -fno-crash-diagnostics 2>&1 | FileCheck %s --check-prefix=NOT --allow-empty
21+
// RUN: %clang %s -fcrash-diagnostics-dir=%t 2>&1 | FileCheck %s --check-prefix=NOT --allow-empty
22+
// RUN: %clang %s -fcrash-diagnostics-dir=%t -gen-reproducer=crash 2>&1 | FileCheck %s --check-prefix=NOT --allow-empty
23+
// RUN: %clang %s -fcrash-diagnostics-dir=%t -gen-reproducer=error 2>&1 | FileCheck %s --check-prefix=NOT --allow-empty
24+
// RUN: not %clang %s -fcrash-diagnostics-dir=%t -gen-reproducer=always 2>&1 | FileCheck %s
25+
// RUN: not %clang %s -fcrash-diagnostics-dir=%t -gen-reproducer 2>&1 | FileCheck %s
26+
27+
// RUN: not %clang $s -gen-reproducer=badvalue 2>&1 | FileCheck %s --check-prefix=BAD-VALUE
28+
// BAD-VALUE: Unknown value for -gen-reproducer=: 'badvalue'
29+
30+
// CHECK: note: diagnostic msg: {{.*}}emit-reproducer-{{.*}}.c
31+
// NOT-NOT: note: diagnostic msg: {{.*}}emit-reproducer-{{.*}}.c
32+
33+
#ifdef FATAL
34+
#pragma clang __debug crash
35+
#elif ERROR
36+
int main
37+
#else
38+
int main() {}
39+
#endif

clang/tools/driver/driver.cpp

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -482,32 +482,37 @@ int main(int Argc, const char **Argv) {
482482
}
483483

484484
std::unique_ptr<Compilation> C(TheDriver.BuildCompilation(Args));
485+
486+
Driver::ReproLevel ReproLevel = Driver::ReproLevel::OnCrash;
487+
if (Arg *A = C->getArgs().getLastArg(options::OPT_gen_reproducer_eq)) {
488+
auto Level = llvm::StringSwitch<Optional<Driver::ReproLevel>>(A->getValue())
489+
.Case("off", Driver::ReproLevel::Off)
490+
.Case("crash", Driver::ReproLevel::OnCrash)
491+
.Case("error", Driver::ReproLevel::OnError)
492+
.Case("always", Driver::ReproLevel::Always)
493+
.Default(None);
494+
if (!Level) {
495+
llvm::errs() << "Unknown value for " << A->getSpelling() << ": '"
496+
<< A->getValue() << "'\n";
497+
return 1;
498+
}
499+
ReproLevel = *Level;
500+
}
501+
if (!!::getenv("FORCE_CLANG_DIAGNOSTICS_CRASH"))
502+
ReproLevel = Driver::ReproLevel::Always;
503+
485504
int Res = 1;
486505
bool IsCrash = false;
506+
Driver::CommandStatus CommandStatus = Driver::CommandStatus::Ok;
507+
// Pretend the first command failed if ReproStatus is Always.
508+
const Command *FailingCommand = &*C->getJobs().begin();
487509
if (C && !C->containsError()) {
488510
SmallVector<std::pair<int, const Command *>, 4> FailingCommands;
489511
Res = TheDriver.ExecuteCompilation(*C, FailingCommands);
490512

491-
// Force a crash to test the diagnostics.
492-
if (TheDriver.GenReproducer) {
493-
Diags.Report(diag::err_drv_force_crash)
494-
<< !::getenv("FORCE_CLANG_DIAGNOSTICS_CRASH");
495-
496-
// Pretend that every command failed.
497-
FailingCommands.clear();
498-
for (const auto &J : C->getJobs())
499-
if (const Command *C = dyn_cast<Command>(&J))
500-
FailingCommands.push_back(std::make_pair(-1, C));
501-
502-
// Print the bug report message that would be printed if we did actually
503-
// crash, but only if we're crashing due to FORCE_CLANG_DIAGNOSTICS_CRASH.
504-
if (::getenv("FORCE_CLANG_DIAGNOSTICS_CRASH"))
505-
llvm::dbgs() << llvm::getBugReportMsg();
506-
}
507-
508513
for (const auto &P : FailingCommands) {
509514
int CommandRes = P.first;
510-
const Command *FailingCommand = P.second;
515+
FailingCommand = P.second;
511516
if (!Res)
512517
Res = CommandRes;
513518

@@ -526,13 +531,21 @@ int main(int Argc, const char **Argv) {
526531
// https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xcu_chap02.html
527532
IsCrash |= CommandRes > 128;
528533
#endif
529-
if (IsCrash) {
530-
TheDriver.generateCompilationDiagnostics(*C, *FailingCommand);
534+
CommandStatus =
535+
IsCrash ? Driver::CommandStatus::Crash : Driver::CommandStatus::Error;
536+
if (IsCrash)
531537
break;
532-
}
533538
}
534539
}
535540

541+
// Print the bug report message that would be printed if we did actually
542+
// crash, but only if we're crashing due to FORCE_CLANG_DIAGNOSTICS_CRASH.
543+
if (::getenv("FORCE_CLANG_DIAGNOSTICS_CRASH"))
544+
llvm::dbgs() << llvm::getBugReportMsg();
545+
if (TheDriver.maybeGenerateCompilationDiagnostics(CommandStatus, ReproLevel,
546+
*C, *FailingCommand))
547+
Res = 1;
548+
536549
Diags.getClient()->finish();
537550

538551
if (!UseNewCC1Process && IsCrash) {

0 commit comments

Comments
 (0)