diff --git a/clang/docs/UsersManual.rst b/clang/docs/UsersManual.rst --- a/clang/docs/UsersManual.rst +++ b/clang/docs/UsersManual.rst @@ -667,6 +667,14 @@ reproducibility of the failure. Below are the command line options to control the crash diagnostics. +.. option:: -fcrash-diagnostics= + + Valid values are: + + * ``off`` (Disable auto-generation of preprocessed source files during a clang crash.) + * ``compiler`` (Generate diagnostics for compiler crashes (default)) + * ``all`` (Generate diagnostics for all tools which support it) + .. option:: -fno-crash-diagnostics Disable auto-generation of preprocessed source files during a clang crash. diff --git a/clang/include/clang/Driver/Compilation.h b/clang/include/clang/Driver/Compilation.h --- a/clang/include/clang/Driver/Compilation.h +++ b/clang/include/clang/Driver/Compilation.h @@ -216,6 +216,7 @@ void addCommand(std::unique_ptr C) { Jobs.addJob(std::move(C)); } + llvm::opt::ArgStringList &getTempFiles() { return TempFiles; } const llvm::opt::ArgStringList &getTempFiles() const { return TempFiles; } const ArgStringMap &getResultFiles() const { return ResultFiles; } diff --git a/clang/include/clang/Driver/Driver.h b/clang/include/clang/Driver/Driver.h --- a/clang/include/clang/Driver/Driver.h +++ b/clang/include/clang/Driver/Driver.h @@ -600,6 +600,11 @@ /// Returns the default name for linked images (e.g., "a.out"). const char *getDefaultImageName() const; + // Creates a temp file with $Prefix-%%%%%%.$Suffix + const char *CreateTempFile(Compilation &C, StringRef Prefix, StringRef Suffix, + bool MultipleArchs = false, + StringRef BoundArch = {}) const; + /// GetNamedOutputPath - Return the name to use for the output of /// the action \p JA. The result is appended to the compilation's /// list of temporary or result files, as appropriate. diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1433,6 +1433,10 @@ MarshallingInfoFlag>; def fconstexpr_backtrace_limit_EQ : Joined<["-"], "fconstexpr-backtrace-limit=">, Group; +def fcrash_diagnostics_EQ : Joined<["-"], "fcrash-diagnostics=">, Group, Flags<[NoArgumentUnused, CoreOption]>, + HelpText<"Set level of crash diagnostic reporting, (option: off, compiler, all)">; +def fcrash_diagnostics : Flag<["-"], "fcrash-diagnostics">, Group, Flags<[NoArgumentUnused, CoreOption]>, + HelpText<"Enable crash diagnostic reporting (default)">, Alias, AliasArgs<["compiler"]>; 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">; diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp --- a/clang/lib/Driver/Driver.cpp +++ b/clang/lib/Driver/Driver.cpp @@ -1507,11 +1507,36 @@ 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()) + unsigned Level = 1; + if (Arg *A = C.getArgs().getLastArg(options::OPT_fcrash_diagnostics_EQ)) { + Level = llvm::StringSwitch(A->getValue()) + .Case("off", 0) + .Case("compiler", 1) + .Case("all", 2) + .Default(1); + } + if (!Level) return; + // Don't try to generate diagnostics for dsymutil jobs. + if (FailingCommand.getCreator().isDsymutilJob()) + return; + + bool IsLLD = false; + ArgStringList SavedTemps; + if (FailingCommand.getCreator().isLinkJob()) { + C.getDefaultToolChain().GetLinkerPath(&IsLLD); + if (!IsLLD || Level < 2) + return; + + // If lld crashed, we will re-run the same command with the input it used + // to have. In that case we should not remove temp files in + // initCompilationForDiagnostics yet. They will be added back and removed + // later. + SavedTemps = std::move(C.getTempFiles()); + assert(!C.getTempFiles().size()); + } + // Print the version of the compiler. PrintVersion(C, llvm::errs()); @@ -1605,6 +1630,18 @@ return; } + // If lld failed, rerun it again with --reproduce. + if (IsLLD) { + const char *TmpName = CreateTempFile(C, "linker-crash", "tar"); + Command NewLLDInvocation = Cmd; + llvm::opt::ArgStringList ArgList = NewLLDInvocation.getArguments(); + ArgList.push_back(Saver.save(Twine{"--reproduce="} + TmpName).data()); + NewLLDInvocation.replaceArguments(std::move(ArgList)); + + // Redirect stdout/stderr to /dev/null. + NewLLDInvocation.Execute({None, {""}, {""}}, nullptr, nullptr); + } + const ArgStringList &TempFiles = C.getTempFiles(); if (TempFiles.empty()) { Diag(clang::diag::note_drv_command_failed_diag_msg) @@ -1635,6 +1672,9 @@ } } + for (const char *TempFile : SavedTemps) + C.addTempFile(TempFile); + // Assume associated files are based off of the first temporary file. CrashReportInfo CrashInfo(TempFiles[0], VFS); @@ -5554,6 +5594,35 @@ return false; } +const char *Driver::CreateTempFile(Compilation &C, StringRef Prefix, + StringRef Suffix, bool MultipleArchs, + StringRef BoundArch) const { + SmallString<128> TmpName; + Arg *A = C.getArgs().getLastArg(options::OPT_fcrash_diagnostics_dir); + if (CCGenDiagnostics && A) { + SmallString<128> CrashDirectory(A->getValue()); + if (!getVFS().exists(CrashDirectory)) + llvm::sys::fs::create_directories(CrashDirectory); + llvm::sys::path::append(CrashDirectory, Prefix); + const char *Middle = !Suffix.empty() ? "-%%%%%%." : "-%%%%%%"; + std::error_code EC = llvm::sys::fs::createUniqueFile( + CrashDirectory + Middle + Suffix, TmpName); + if (EC) { + Diag(clang::diag::err_unable_to_make_temp) << EC.message(); + return ""; + } + } else { + if (MultipleArchs && !BoundArch.empty()) { + TmpName = GetTemporaryDirectory(Prefix); + llvm::sys::path::append(TmpName, + Twine(Prefix) + "-" + BoundArch + "." + Suffix); + } else { + TmpName = GetTemporaryPath(Prefix, Suffix); + } + } + return C.addTempFile(C.getArgs().MakeArgString(TmpName)); +} + const char *Driver::GetNamedOutputPath(Compilation &C, const JobAction &JA, const char *BaseInput, StringRef OrigBoundArch, bool AtTopLevel, @@ -5613,31 +5682,8 @@ CCGenDiagnostics) { StringRef Name = llvm::sys::path::filename(BaseInput); std::pair Split = Name.split('.'); - SmallString<128> TmpName; const char *Suffix = types::getTypeTempSuffix(JA.getType(), IsCLMode()); - Arg *A = C.getArgs().getLastArg(options::OPT_fcrash_diagnostics_dir); - if (CCGenDiagnostics && A) { - SmallString<128> CrashDirectory(A->getValue()); - if (!getVFS().exists(CrashDirectory)) - llvm::sys::fs::create_directories(CrashDirectory); - llvm::sys::path::append(CrashDirectory, Split.first); - const char *Middle = Suffix ? "-%%%%%%." : "-%%%%%%"; - std::error_code EC = llvm::sys::fs::createUniqueFile( - CrashDirectory + Middle + Suffix, TmpName); - if (EC) { - Diag(clang::diag::err_unable_to_make_temp) << EC.message(); - return ""; - } - } else { - if (MultipleArchs && !BoundArch.empty()) { - TmpName = GetTemporaryDirectory(Split.first); - llvm::sys::path::append(TmpName, - Split.first + "-" + BoundArch + "." + Suffix); - } else { - TmpName = GetTemporaryPath(Split.first, Suffix); - } - } - return C.addTempFile(C.getArgs().MakeArgString(TmpName)); + return CreateTempFile(C, Split.first, Suffix, MultipleArchs, BoundArch); } SmallString<128> BasePath(BaseInput); diff --git a/clang/test/Driver/crash-report.cpp b/clang/test/Driver/crash-report.cpp --- a/clang/test/Driver/crash-report.cpp +++ b/clang/test/Driver/crash-report.cpp @@ -27,6 +27,29 @@ // RUN: cat %t/crash-report-*.cpp | FileCheck --check-prefix=CHECKSRC %s // RUN: cat %t/crash-report-*.sh | FileCheck --check-prefix=CHECKSH %s +// Test manually specifying -fcrash-diagnostics[=[compiler|all]] emits +// diagnostics +// RUN: env TMPDIR=%t TEMP=%t TMP=%t RC_DEBUG_OPTIONS=1 \ +// RUN: CC_PRINT_HEADERS=1 CC_LOG_DIAGNOSTICS=1 \ +// RUN: not %clang %s @%t.rsp -DFATAL -fcrash-diagnostics 2>&1 | \ +// RUN: FileCheck %s +// RUN: cat %t/crash-report-*.cpp | FileCheck --check-prefix=CHECKSRC %s +// RUN: cat %t/crash-report-*.sh | FileCheck --check-prefix=CHECKSH %s + +// RUN: env TMPDIR=%t TEMP=%t TMP=%t RC_DEBUG_OPTIONS=1 \ +// RUN: CC_PRINT_HEADERS=1 CC_LOG_DIAGNOSTICS=1 \ +// RUN: not %clang %s @%t.rsp -DFATAL -fcrash-diagnostics=compiler 2>&1 |\ +// RUN: FileCheck %s +// RUN: cat %t/crash-report-*.cpp | FileCheck --check-prefix=CHECKSRC %s +// RUN: cat %t/crash-report-*.sh | FileCheck --check-prefix=CHECKSH %s + +// RUN: env TMPDIR=%t TEMP=%t TMP=%t RC_DEBUG_OPTIONS=1 \ +// RUN: CC_PRINT_HEADERS=1 CC_LOG_DIAGNOSTICS=1 \ +// RUN: not %clang %s @%t.rsp -DFATAL -fcrash-diagnostics=all 2>&1 | \ +// RUN: FileCheck %s +// RUN: cat %t/crash-report-*.cpp | FileCheck --check-prefix=CHECKSRC %s +// RUN: cat %t/crash-report-*.sh | FileCheck --check-prefix=CHECKSH %s + // REQUIRES: crash-recovery #ifdef PARSER diff --git a/clang/test/Driver/lit.local.cfg b/clang/test/Driver/lit.local.cfg --- a/clang/test/Driver/lit.local.cfg +++ b/clang/test/Driver/lit.local.cfg @@ -1,3 +1,5 @@ +from lit.llvm import llvm_config + config.suffixes = ['.c', '.cpp', '.h', '.m', '.mm', '.S', '.s', '.f90', '.F90', '.f95', '.cu', '.rs', '.cl', '.clcpp', '.hip', '.hlsl'] config.substitutions = list(config.substitutions) @@ -16,3 +18,6 @@ for name in driver_overwrite_env_vars: if name in config.environment: del config.environment[name] + +if llvm_config.use_lld(required=False): + config.available_features.add('lld') diff --git a/clang/test/Driver/lld-repro.c b/clang/test/Driver/lld-repro.c new file mode 100644 --- /dev/null +++ b/clang/test/Driver/lld-repro.c @@ -0,0 +1,26 @@ +// REQUIRES: lld + +// RUN: not %clang %s -fuse-ld=lld -gen-reproducer=error -fcrash-diagnostics-dir=%t -fcrash-diagnostics=all 2>&1 \ +// RUN: | FileCheck %s + +// check that we still get lld's output +// CHECK: error: undefined symbol: a + +// CHECK: Preprocessed source(s) and associated run script(s) are located at: +// CHECK-NEXT: note: diagnostic msg: {{.*}}/lld-repro-{{.*}}.c +// CHECK-NEXT: note: diagnostic msg: {{.*}}/linker-crash-{{.*}}.tar +// CHECK-NEXT: note: diagnostic msg: {{.*}}/lld-repro-{{.*}}.sh +// CHECK-NEXT: note: diagnostic msg: +// CHECK: ******************** + +// RUN: not %clang %s -fuse-ld=lld -gen-reproducer=error -fcrash-diagnostics-dir=%t -fcrash-diagnostics=compiler 2>&1 \ +// RUN: | FileCheck %s --check-prefix=NO-LINKER +// RUN: not %clang %s -fuse-ld=lld -gen-reproducer=error -fcrash-diagnostics-dir=%t 2>&1 \ +// RUN: | FileCheck %s --check-prefix=NO-LINKER + +// NO-LINKER-NOT: Preprocessed source(s) and associated run script(s) are located at: + +extern int a; +int main() { + return a; +}