Skip to content

Commit 2469afe

Browse files
committed
Make the BUG_REPORT_URL configurable by tools
This greatly simplifies how hard it is to set a custom bug report url; previously tools had to copy the entire hook implementation. - Switch clippy to the new hook This also adds a `extra_info` callback so clippy can include its own version number, which differs from rustc's. - Call `install_ice_hook` in rustfmt
1 parent f0be145 commit 2469afe

File tree

7 files changed

+115
-111
lines changed

7 files changed

+115
-111
lines changed

compiler/rustc_driver_impl/src/lib.rs

+57-46
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use rustc_data_structures::profiling::{
2525
use rustc_data_structures::sync::SeqCst;
2626
use rustc_errors::registry::{InvalidErrorCode, Registry};
2727
use rustc_errors::{
28-
DiagnosticMessage, ErrorGuaranteed, PResult, SubdiagnosticMessage, TerminalUrl,
28+
DiagnosticMessage, ErrorGuaranteed, Handler, PResult, SubdiagnosticMessage, TerminalUrl,
2929
};
3030
use rustc_feature::find_gated_cfg;
3131
use rustc_fluent_macro::fluent_messages;
@@ -55,7 +55,7 @@ use std::panic::{self, catch_unwind};
5555
use std::path::PathBuf;
5656
use std::process::{self, Command, Stdio};
5757
use std::str;
58-
use std::sync::LazyLock;
58+
use std::sync::OnceLock;
5959
use std::time::Instant;
6060

6161
// This import blocks the use of panicking `print` and `println` in all the code
@@ -119,7 +119,7 @@ pub const EXIT_SUCCESS: i32 = 0;
119119
/// Exit status code used for compilation failures and invalid flags.
120120
pub const EXIT_FAILURE: i32 = 1;
121121

122-
const BUG_REPORT_URL: &str = "https://github.com/rust-lang/rust/issues/new\
122+
pub const DEFAULT_BUG_REPORT_URL: &str = "https://github.com/rust-lang/rust/issues/new\
123123
?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md";
124124

125125
const ICE_REPORT_COMPILER_FLAGS: &[&str] = &["-Z", "-C", "--crate-type"];
@@ -1195,43 +1195,66 @@ pub fn catch_with_exit_code(f: impl FnOnce() -> interface::Result<()>) -> i32 {
11951195
}
11961196
}
11971197

1198-
static DEFAULT_HOOK: LazyLock<Box<dyn Fn(&panic::PanicInfo<'_>) + Sync + Send + 'static>> =
1199-
LazyLock::new(|| {
1200-
let hook = panic::take_hook();
1201-
panic::set_hook(Box::new(|info| {
1202-
// If the error was caused by a broken pipe then this is not a bug.
1203-
// Write the error and return immediately. See #98700.
1204-
#[cfg(windows)]
1205-
if let Some(msg) = info.payload().downcast_ref::<String>() {
1206-
if msg.starts_with("failed printing to stdout: ") && msg.ends_with("(os error 232)")
1207-
{
1208-
early_error_no_abort(ErrorOutputType::default(), &msg);
1209-
return;
1210-
}
1211-
};
1198+
/// Stores the default panic hook, from before [`install_ice_hook`] was called.
1199+
static DEFAULT_HOOK: OnceLock<Box<dyn Fn(&panic::PanicInfo<'_>) + Sync + Send + 'static>> =
1200+
OnceLock::new();
1201+
1202+
/// Installs a panic hook that will print the ICE message on unexpected panics.
1203+
///
1204+
/// The hook is intended to be useable even by external tools. You can pass a custom
1205+
/// `bug_report_url`, or report arbitrary info in `extra_info`. Note that `extra_info` is called in
1206+
/// a context where *the thread is currently panicking*, so it must not panic or the process will
1207+
/// abort.
1208+
///
1209+
/// If you have no extra info to report, pass the empty closure `|_| ()` as the argument to
1210+
/// extra_info.
1211+
///
1212+
/// A custom rustc driver can skip calling this to set up a custom ICE hook.
1213+
pub fn install_ice_hook(bug_report_url: &'static str, extra_info: fn(&Handler)) {
1214+
// If the user has not explicitly overridden "RUST_BACKTRACE", then produce
1215+
// full backtraces. When a compiler ICE happens, we want to gather
1216+
// as much information as possible to present in the issue opened
1217+
// by the user. Compiler developers and other rustc users can
1218+
// opt in to less-verbose backtraces by manually setting "RUST_BACKTRACE"
1219+
// (e.g. `RUST_BACKTRACE=1`)
1220+
if std::env::var("RUST_BACKTRACE").is_err() {
1221+
std::env::set_var("RUST_BACKTRACE", "full");
1222+
}
12121223

1213-
// Invoke the default handler, which prints the actual panic message and optionally a backtrace
1214-
// Don't do this for delayed bugs, which already emit their own more useful backtrace.
1215-
if !info.payload().is::<rustc_errors::DelayedBugPanic>() {
1216-
(*DEFAULT_HOOK)(info);
1224+
let default_hook = DEFAULT_HOOK.get_or_init(panic::take_hook);
12171225

1218-
// Separate the output with an empty line
1219-
eprintln!();
1226+
panic::set_hook(Box::new(move |info| {
1227+
// If the error was caused by a broken pipe then this is not a bug.
1228+
// Write the error and return immediately. See #98700.
1229+
#[cfg(windows)]
1230+
if let Some(msg) = info.payload().downcast_ref::<String>() {
1231+
if msg.starts_with("failed printing to stdout: ") && msg.ends_with("(os error 232)") {
1232+
early_error_no_abort(ErrorOutputType::default(), &msg);
1233+
return;
12201234
}
1235+
};
12211236

1222-
// Print the ICE message
1223-
report_ice(info, BUG_REPORT_URL);
1224-
}));
1225-
hook
1226-
});
1237+
// Invoke the default handler, which prints the actual panic message and optionally a backtrace
1238+
// Don't do this for delayed bugs, which already emit their own more useful backtrace.
1239+
if !info.payload().is::<rustc_errors::DelayedBugPanic>() {
1240+
(*default_hook)(info);
1241+
1242+
// Separate the output with an empty line
1243+
eprintln!();
1244+
}
1245+
1246+
// Print the ICE message
1247+
report_ice(info, bug_report_url, extra_info);
1248+
}));
1249+
}
12271250

12281251
/// Prints the ICE message, including query stack, but without backtrace.
12291252
///
12301253
/// The message will point the user at `bug_report_url` to report the ICE.
12311254
///
12321255
/// When `install_ice_hook` is called, this function will be called as the panic
12331256
/// hook.
1234-
pub fn report_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) {
1257+
pub fn report_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str, extra_info: fn(&Handler)) {
12351258
let fallback_bundle =
12361259
rustc_errors::fallback_fluent_bundle(crate::DEFAULT_LOCALE_RESOURCES.to_vec(), false);
12371260
let emitter = Box::new(rustc_errors::emitter::EmitterWriter::stderr(
@@ -1276,29 +1299,17 @@ pub fn report_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) {
12761299

12771300
interface::try_print_query_stack(&handler, num_frames);
12781301

1302+
// We don't trust this callback not to panic itself, so run it at the end after we're sure we've
1303+
// printed all the relevant info.
1304+
extra_info(&handler);
1305+
12791306
#[cfg(windows)]
12801307
if env::var("RUSTC_BREAK_ON_ICE").is_ok() {
12811308
// Trigger a debugger if we crashed during bootstrap
12821309
unsafe { windows::Win32::System::Diagnostics::Debug::DebugBreak() };
12831310
}
12841311
}
12851312

1286-
/// Installs a panic hook that will print the ICE message on unexpected panics.
1287-
///
1288-
/// A custom rustc driver can skip calling this to set up a custom ICE hook.
1289-
pub fn install_ice_hook() {
1290-
// If the user has not explicitly overridden "RUST_BACKTRACE", then produce
1291-
// full backtraces. When a compiler ICE happens, we want to gather
1292-
// as much information as possible to present in the issue opened
1293-
// by the user. Compiler developers and other rustc users can
1294-
// opt in to less-verbose backtraces by manually setting "RUST_BACKTRACE"
1295-
// (e.g. `RUST_BACKTRACE=1`)
1296-
if std::env::var("RUST_BACKTRACE").is_err() {
1297-
std::env::set_var("RUST_BACKTRACE", "full");
1298-
}
1299-
LazyLock::force(&DEFAULT_HOOK);
1300-
}
1301-
13021313
/// This allows tools to enable rust logging without having to magically match rustc's
13031314
/// tracing crate version.
13041315
pub fn init_rustc_env_logger() {
@@ -1369,7 +1380,7 @@ pub fn main() -> ! {
13691380
init_rustc_env_logger();
13701381
signal_handler::install();
13711382
let mut callbacks = TimePassesCallbacks::default();
1372-
install_ice_hook();
1383+
install_ice_hook(DEFAULT_BUG_REPORT_URL, |_| ());
13731384
let exit_code = catch_with_exit_code(|| {
13741385
let args = env::args_os()
13751386
.enumerate()

src/librustdoc/lib.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,11 @@ pub fn main() {
154154
}
155155
}
156156

157-
rustc_driver::install_ice_hook();
157+
rustc_driver::install_ice_hook(
158+
"https://github.com/rust-lang/rust/issues/new\
159+
?labels=C-bug%2C+I-ICE%2C+T-rustdoc&template=ice.md",
160+
|_| (),
161+
);
158162

159163
// When using CI artifacts with `download-rustc`, tracing is unconditionally built
160164
// with `--features=static_max_level_info`, which disables almost all rustdoc logging. To avoid

src/tools/clippy/src/driver.rs

+9-61
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
// FIXME: switch to something more ergonomic here, once available.
1212
// (Currently there is no way to opt into sysroot crates without `extern crate`.)
1313
extern crate rustc_driver;
14-
extern crate rustc_errors;
1514
extern crate rustc_interface;
1615
extern crate rustc_session;
1716
extern crate rustc_span;
@@ -20,13 +19,10 @@ use rustc_interface::interface;
2019
use rustc_session::parse::ParseSess;
2120
use rustc_span::symbol::Symbol;
2221

23-
use std::borrow::Cow;
2422
use std::env;
2523
use std::ops::Deref;
26-
use std::panic;
2724
use std::path::Path;
2825
use std::process::exit;
29-
use std::sync::LazyLock;
3026

3127
/// If a command-line option matches `find_arg`, then apply the predicate `pred` on its value. If
3228
/// true, then return it. The parameter is assumed to be either `--arg=value` or `--arg value`.
@@ -198,66 +194,18 @@ You can use tool lints to allow or deny lints from your code, eg.:
198194

199195
const BUG_REPORT_URL: &str = "https://github.com/rust-lang/rust-clippy/issues/new";
200196

201-
type PanicCallback = dyn Fn(&panic::PanicInfo<'_>) + Sync + Send + 'static;
202-
static ICE_HOOK: LazyLock<Box<PanicCallback>> = LazyLock::new(|| {
203-
let hook = panic::take_hook();
204-
panic::set_hook(Box::new(|info| report_clippy_ice(info, BUG_REPORT_URL)));
205-
hook
206-
});
207-
208-
fn report_clippy_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) {
209-
// Invoke our ICE handler, which prints the actual panic message and optionally a backtrace
210-
(*ICE_HOOK)(info);
211-
212-
// Separate the output with an empty line
213-
eprintln!();
214-
215-
let fallback_bundle = rustc_errors::fallback_fluent_bundle(rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(), false);
216-
let emitter = Box::new(rustc_errors::emitter::EmitterWriter::stderr(
217-
rustc_errors::ColorConfig::Auto,
218-
None,
219-
None,
220-
fallback_bundle,
221-
false,
222-
false,
223-
None,
224-
false,
225-
false,
226-
rustc_errors::TerminalUrl::No,
227-
));
228-
let handler = rustc_errors::Handler::with_emitter(true, None, emitter);
229-
230-
// a .span_bug or .bug call has already printed what
231-
// it wants to print.
232-
if !info.payload().is::<rustc_errors::ExplicitBug>() {
233-
let mut d = rustc_errors::Diagnostic::new(rustc_errors::Level::Bug, "unexpected panic");
234-
handler.emit_diagnostic(&mut d);
235-
}
236-
237-
let version_info = rustc_tools_util::get_version_info!();
238-
239-
let xs: Vec<Cow<'static, str>> = vec![
240-
"the compiler unexpectedly panicked. this is a bug.".into(),
241-
format!("we would appreciate a bug report: {bug_report_url}").into(),
242-
format!("Clippy version: {version_info}").into(),
243-
];
244-
245-
for note in &xs {
246-
handler.note_without_error(note.as_ref());
247-
}
248-
249-
// If backtraces are enabled, also print the query stack
250-
let backtrace = env::var_os("RUST_BACKTRACE").map_or(false, |x| &x != "0");
251-
252-
let num_frames = if backtrace { None } else { Some(2) };
253-
254-
interface::try_print_query_stack(&handler, num_frames);
255-
}
256-
257197
#[allow(clippy::too_many_lines)]
258198
pub fn main() {
259199
rustc_driver::init_rustc_env_logger();
260-
LazyLock::force(&ICE_HOOK);
200+
201+
rustc_driver::install_ice_hook(BUG_REPORT_URL, |handler| {
202+
// FIXME: this macro calls unwrap internally but is called in a panicking context! It's not
203+
// as simple as moving the call from the hook to main, because `install_ice_hook` doesn't
204+
// accept a generic closure.
205+
let version_info = rustc_tools_util::get_version_info!();
206+
handler.note_without_error(format!("Clippy version: {version_info}"));
207+
});
208+
261209
exit(rustc_driver::catch_with_exit_code(move || {
262210
let mut orig_args: Vec<String> = env::args().collect();
263211
let has_sysroot_arg = arg_value(&orig_args, "--sysroot", |_| true).is_some();

src/tools/miri/src/bin/miri.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -286,11 +286,10 @@ fn main() {
286286
// (`install_ice_hook` might change `RUST_BACKTRACE`.)
287287
let env_snapshot = env::vars_os().collect::<Vec<_>>();
288288

289-
// Earliest rustc setup.
290-
rustc_driver::install_ice_hook();
291-
292289
// If the environment asks us to actually be rustc, then do that.
293290
if let Some(crate_kind) = env::var_os("MIRI_BE_RUSTC") {
291+
// Earliest rustc setup.
292+
rustc_driver::install_ice_hook(rustc_driver::DEFAULT_BUG_REPORT_URL, |_| ());
294293
rustc_driver::init_rustc_env_logger();
295294

296295
let target_crate = if crate_kind == "target" {
@@ -309,6 +308,9 @@ fn main() {
309308
)
310309
}
311310

311+
// Add an ICE bug report hook.
312+
rustc_driver::install_ice_hook("https://github.com/rust-lang/miri/issues/new", |_| ());
313+
312314
// Init loggers the Miri way.
313315
init_early_loggers();
314316

src/tools/rustfmt/src/bin/main.rs

+9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#![feature(rustc_private)]
2+
13
use anyhow::{format_err, Result};
24

35
use io::Error as IoError;
@@ -19,7 +21,14 @@ use crate::rustfmt::{
1921
FormatReportFormatterBuilder, Input, Session, Verbosity,
2022
};
2123

24+
const BUG_REPORT_URL: &str = "https://github.com/rust-lang/rustfmt/issues/new?labels=bug";
25+
26+
// N.B. these crates are loaded from the sysroot, so they need extern crate.
27+
extern crate rustc_driver;
28+
2229
fn main() {
30+
rustc_driver::install_ice_hook(BUG_REPORT_URL, |_| ());
31+
2332
env_logger::Builder::from_env("RUSTFMT_LOG").init();
2433
let opts = make_opts();
2534

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// compile-flags: -Ztreat-err-as-bug
2+
// failure-status: 101
3+
// error-pattern: aborting due to
4+
// error-pattern: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-rustdoc&template=ice.md
5+
6+
// normalize-stderr-test "note: compiler flags.*\n\n" -> ""
7+
// normalize-stderr-test "note: rustc.*running on.*" -> "note: rustc {version} running on {platform}"
8+
// normalize-stderr-test "thread.*panicked at .*, compiler.*" -> "thread panicked at 'aborting due to `-Z treat-err-as-bug`'"
9+
// normalize-stderr-test "\s*\d{1,}: .*\n" -> ""
10+
// normalize-stderr-test "\s at .*\n" -> ""
11+
// normalize-stderr-test ".*note: Some details are omitted.*\n" -> ""
12+
13+
fn wrong()
14+
//~^ ERROR expected one of
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
error: expected one of `->`, `where`, or `{`, found `<eof>`
2+
--> $DIR/ice-bug-report-url.rs:13:10
3+
|
4+
LL | fn wrong()
5+
| ^ expected one of `->`, `where`, or `{`
6+
7+
thread panicked at 'aborting due to `-Z treat-err-as-bug`'
8+
stack backtrace:
9+
error: the compiler unexpectedly panicked. this is a bug.
10+
11+
note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-rustdoc&template=ice.md
12+
13+
note: rustc {version} running on {platform}
14+
15+
query stack during panic:
16+
end of query stack

0 commit comments

Comments
 (0)