Skip to content

Commit

Permalink
[Clang] Extend -gen-reproducer flag
Browse files Browse the repository at this point in the history
`-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
  • Loading branch information
abrachet committed May 31, 2022
1 parent 80c4cf6 commit 7d76d60
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 32 deletions.
35 changes: 30 additions & 5 deletions clang/include/clang/Driver/Driver.h
Expand Up @@ -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"
Expand Down Expand Up @@ -276,11 +277,6 @@ class Driver {
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
Expand Down Expand Up @@ -505,6 +501,35 @@ class Driver {
StringRef AdditionalInformation = "",
CompilationDiagnosticReport *GeneratedReport = nullptr);

enum class CommandStatus {
Crash = 1,
Error,
Ok,
};

enum class ReproLevel {
Off = 0,
OnCrash = static_cast<int>(CommandStatus::Crash),
OnError = static_cast<int>(CommandStatus::Error),
Always = static_cast<int>(CommandStatus::Ok),
};

bool maybeGenerateCompilationDiagnostics(
CommandStatus CS, ReproLevel Level, Compilation &C,
const Command &FailingCommand, StringRef AdditionalInformation = "",
CompilationDiagnosticReport *GeneratedReport = nullptr) {
if (static_cast<int>(CS) > static_cast<int>(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
/// @{
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Driver/Options.td
Expand Up @@ -558,7 +558,10 @@ def arcmt_migrate_report_output : Separate<["-"], "arcmt-migrate-report-output">
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<FrontendOpts<"ARCMTMigrateEmitARCErrors">>;
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<gen_reproducer_eq>, 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">;
Expand Down Expand Up @@ -1397,6 +1400,7 @@ def fexperimental_new_constant_interpreter : Flag<["-"], "fexperimental-new-cons
def fconstexpr_backtrace_limit_EQ : Joined<["-"], "fconstexpr-backtrace-limit=">,
Group<f_Group>;
def fno_crash_diagnostics : Flag<["-"], "fno-crash-diagnostics">, Group<f_clang_Group>, Flags<[NoArgumentUnused, CoreOption]>,
Alias<gen_reproducer_eq>, 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<f_clang_Group>, Flags<[NoArgumentUnused, CoreOption]>,
Expand Down
6 changes: 1 addition & 5 deletions clang/lib/Driver/Driver.cpp
Expand Up @@ -198,8 +198,7 @@ Driver::Driver(StringRef ClangExecutable, StringRef TargetTriple,
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();
Expand Down Expand Up @@ -1217,9 +1216,6 @@ Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
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)) {
Expand Down
2 changes: 1 addition & 1 deletion clang/test/Driver/crash-report-crashfile.m
Expand Up @@ -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
Expand Down
41 changes: 41 additions & 0 deletions 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
55 changes: 34 additions & 21 deletions clang/tools/driver/driver.cpp
Expand Up @@ -482,32 +482,37 @@ int main(int Argc, const char **Argv) {
}

std::unique_ptr<Compilation> 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<Optional<Driver::ReproLevel>>(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<std::pair<int, const Command *>, 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<Command>(&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;

Expand All @@ -526,13 +531,21 @@ int main(int Argc, const char **Argv) {
// 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) {
Expand Down

0 comments on commit 7d76d60

Please sign in to comment.