Skip to content

Commit 8eb5843

Browse files
committed
On nightly, dump ICE backtraces to disk
Implement rust-lang/compiler-team#578. When an ICE is encountered on nightly releases, the new rustc panic handler will also write the contents of the backtrace to disk. If any `delay_span_bug`s are encountered, their backtrace is also added to the file. The platform and rustc version will also be collected.
1 parent 77e24f9 commit 8eb5843

File tree

30 files changed

+272
-54
lines changed

30 files changed

+272
-54
lines changed

Diff for: Cargo.lock

+28
Original file line numberDiff line numberDiff line change
@@ -3488,6 +3488,7 @@ dependencies = [
34883488
"rustc_trait_selection",
34893489
"rustc_ty_utils",
34903490
"serde_json",
3491+
"time",
34913492
"tracing",
34923493
"windows",
34933494
]
@@ -5142,6 +5143,33 @@ dependencies = [
51425143
name = "tier-check"
51435144
version = "0.1.0"
51445145

5146+
[[package]]
5147+
name = "time"
5148+
version = "0.3.22"
5149+
source = "registry+https://github.com/rust-lang/crates.io-index"
5150+
checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd"
5151+
dependencies = [
5152+
"itoa",
5153+
"serde",
5154+
"time-core",
5155+
"time-macros",
5156+
]
5157+
5158+
[[package]]
5159+
name = "time-core"
5160+
version = "0.1.1"
5161+
source = "registry+https://github.com/rust-lang/crates.io-index"
5162+
checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
5163+
5164+
[[package]]
5165+
name = "time-macros"
5166+
version = "0.2.9"
5167+
source = "registry+https://github.com/rust-lang/crates.io-index"
5168+
checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b"
5169+
dependencies = [
5170+
"time-core",
5171+
]
5172+
51455173
[[package]]
51465174
name = "tinystr"
51475175
version = "0.7.1"

Diff for: compiler/rustc_codegen_ssa/src/back/write.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ pub struct CodegenContext<B: WriteBackendMethods> {
362362

363363
impl<B: WriteBackendMethods> CodegenContext<B> {
364364
pub fn create_diag_handler(&self) -> Handler {
365-
Handler::with_emitter(true, None, Box::new(self.diag_emitter.clone()))
365+
Handler::with_emitter(true, None, Box::new(self.diag_emitter.clone()), None)
366366
}
367367

368368
pub fn config(&self, kind: ModuleKind) -> &ModuleConfig {

Diff for: compiler/rustc_driver_impl/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ edition = "2021"
66
[lib]
77

88
[dependencies]
9+
time = { version = "0.3", default-features = false, features = ["formatting", ] }
910
tracing = { version = "0.1.35" }
1011
serde_json = "1.0.59"
1112
rustc_log = { path = "../rustc_log" }

Diff for: compiler/rustc_driver_impl/messages.ftl

+4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ driver_impl_ice_bug_report = we would appreciate a bug report: {$bug_report_url}
33
driver_impl_ice_exclude_cargo_defaults = some of the compiler flags provided by cargo are hidden
44
55
driver_impl_ice_flags = compiler flags: {$flags}
6+
driver_impl_ice_path = please attach the file at `{$path}` to your bug report
7+
driver_impl_ice_path_error = the ICE couldn't be written to `{$path}`: {$error}
8+
driver_impl_ice_path_error_env = the environment variable `RUSTC_ICE` is set to `{$env_var}`
69
driver_impl_ice_version = rustc {$version} running on {$triple}
10+
711
driver_impl_rlink_empty_version_number = The input does not contain version number
812
913
driver_impl_rlink_encoding_version_mismatch = .rlink file was produced with encoding version `{$version_array}`, but the current version is `{$rlink_version}`

Diff for: compiler/rustc_driver_impl/src/lib.rs

+67-13
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
#![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
88
#![feature(lazy_cell)]
99
#![feature(decl_macro)]
10+
#![feature(ice_to_disk)]
11+
#![feature(let_chains)]
1012
#![recursion_limit = "256"]
1113
#![allow(rustc::potential_query_instability)]
1214
#![deny(rustc::untranslatable_diagnostic)]
@@ -57,8 +59,11 @@ use std::panic::{self, catch_unwind};
5759
use std::path::PathBuf;
5860
use std::process::{self, Command, Stdio};
5961
use std::str;
62+
use std::sync::atomic::{AtomicBool, Ordering};
6063
use std::sync::OnceLock;
61-
use std::time::Instant;
64+
use std::time::{Instant, SystemTime};
65+
use time::format_description::well_known::Rfc3339;
66+
use time::OffsetDateTime;
6267

6368
#[allow(unused_macros)]
6469
macro do_not_use_print($($t:tt)*) {
@@ -294,6 +299,7 @@ fn run_compiler(
294299
input: Input::File(PathBuf::new()),
295300
output_file: ofile,
296301
output_dir: odir,
302+
ice_file: ice_path().clone(),
297303
file_loader,
298304
locale_resources: DEFAULT_LOCALE_RESOURCES,
299305
lint_caps: Default::default(),
@@ -1292,9 +1298,29 @@ pub fn catch_with_exit_code(f: impl FnOnce() -> interface::Result<()>) -> i32 {
12921298
}
12931299
}
12941300

1295-
/// Stores the default panic hook, from before [`install_ice_hook`] was called.
1296-
static DEFAULT_HOOK: OnceLock<Box<dyn Fn(&panic::PanicInfo<'_>) + Sync + Send + 'static>> =
1297-
OnceLock::new();
1301+
pub static ICE_PATH: OnceLock<Option<PathBuf>> = OnceLock::new();
1302+
1303+
pub fn ice_path() -> &'static Option<PathBuf> {
1304+
ICE_PATH.get_or_init(|| {
1305+
if !rustc_feature::UnstableFeatures::from_environment(None).is_nightly_build() {
1306+
return None;
1307+
}
1308+
if let Ok("0") = std::env::var("RUST_BACKTRACE").as_deref() {
1309+
return None;
1310+
}
1311+
let mut path = match std::env::var("RUSTC_ICE").as_deref() {
1312+
// Explicitly opting out of writing ICEs to disk.
1313+
Ok("0") => return None,
1314+
Ok(s) => PathBuf::from(s),
1315+
Err(_) => std::env::current_dir().unwrap_or_default(),
1316+
};
1317+
let now: OffsetDateTime = SystemTime::now().into();
1318+
let file_now = now.format(&Rfc3339).unwrap_or(String::new());
1319+
let pid = std::process::id();
1320+
path.push(format!("rustc-ice-{file_now}-{pid}.txt"));
1321+
Some(path)
1322+
})
1323+
}
12981324

12991325
/// Installs a panic hook that will print the ICE message on unexpected panics.
13001326
///
@@ -1318,8 +1344,6 @@ pub fn install_ice_hook(bug_report_url: &'static str, extra_info: fn(&Handler))
13181344
std::env::set_var("RUST_BACKTRACE", "full");
13191345
}
13201346

1321-
let default_hook = DEFAULT_HOOK.get_or_init(panic::take_hook);
1322-
13231347
panic::set_hook(Box::new(move |info| {
13241348
// If the error was caused by a broken pipe then this is not a bug.
13251349
// Write the error and return immediately. See #98700.
@@ -1336,7 +1360,7 @@ pub fn install_ice_hook(bug_report_url: &'static str, extra_info: fn(&Handler))
13361360
// Invoke the default handler, which prints the actual panic message and optionally a backtrace
13371361
// Don't do this for delayed bugs, which already emit their own more useful backtrace.
13381362
if !info.payload().is::<rustc_errors::DelayedBugPanic>() {
1339-
(*default_hook)(info);
1363+
std::panic_hook_with_disk_dump(info, ice_path().as_deref());
13401364

13411365
// Separate the output with an empty line
13421366
eprintln!();
@@ -1368,7 +1392,7 @@ pub fn report_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str, extra_info:
13681392
false,
13691393
TerminalUrl::No,
13701394
));
1371-
let handler = rustc_errors::Handler::with_emitter(true, None, emitter);
1395+
let handler = rustc_errors::Handler::with_emitter(true, None, emitter, None);
13721396

13731397
// a .span_bug or .bug call has already printed what
13741398
// it wants to print.
@@ -1379,10 +1403,40 @@ pub fn report_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str, extra_info:
13791403
}
13801404

13811405
handler.emit_note(session_diagnostics::IceBugReport { bug_report_url });
1382-
handler.emit_note(session_diagnostics::IceVersion {
1383-
version: util::version_str!().unwrap_or("unknown_version"),
1384-
triple: config::host_triple(),
1385-
});
1406+
1407+
let version = util::version_str!().unwrap_or("unknown_version");
1408+
let triple = config::host_triple();
1409+
1410+
static FIRST_PANIC: AtomicBool = AtomicBool::new(true);
1411+
1412+
let file = if let Some(path) = ice_path().as_ref() {
1413+
// Create the ICE dump target file.
1414+
match crate::fs::File::options().create(true).append(true).open(&path) {
1415+
Ok(mut file) => {
1416+
handler
1417+
.emit_note(session_diagnostics::IcePath { path: path.display().to_string() });
1418+
if FIRST_PANIC.swap(false, Ordering::SeqCst) {
1419+
let _ = write!(file, "\n\nrustc version: {version}\nplatform: {triple}");
1420+
}
1421+
Some(file)
1422+
}
1423+
Err(err) => {
1424+
// The path ICE couldn't be written to disk, provide feedback to the user as to why.
1425+
handler.emit_warning(session_diagnostics::IcePathError {
1426+
path: path.display().to_string(),
1427+
error: err.to_string(),
1428+
env_var: std::env::var("RUSTC_ICE")
1429+
.ok()
1430+
.map(|env_var| session_diagnostics::IcePathErrorEnv { env_var }),
1431+
});
1432+
handler.emit_note(session_diagnostics::IceVersion { version, triple });
1433+
None
1434+
}
1435+
}
1436+
} else {
1437+
handler.emit_note(session_diagnostics::IceVersion { version, triple });
1438+
None
1439+
};
13861440

13871441
if let Some((flags, excluded_cargo_defaults)) = extra_compiler_flags() {
13881442
handler.emit_note(session_diagnostics::IceFlags { flags: flags.join(" ") });
@@ -1396,7 +1450,7 @@ pub fn report_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str, extra_info:
13961450

13971451
let num_frames = if backtrace { None } else { Some(2) };
13981452

1399-
interface::try_print_query_stack(&handler, num_frames);
1453+
interface::try_print_query_stack(&handler, num_frames, file);
14001454

14011455
// We don't trust this callback not to panic itself, so run it at the end after we're sure we've
14021456
// printed all the relevant info.

Diff for: compiler/rustc_driver_impl/src/session_diagnostics.rs

+22-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use rustc_macros::Diagnostic;
1+
use rustc_macros::{Diagnostic, Subdiagnostic};
22

33
#[derive(Diagnostic)]
44
#[diag(driver_impl_rlink_unable_to_read)]
@@ -56,6 +56,27 @@ pub(crate) struct IceVersion<'a> {
5656
pub triple: &'a str,
5757
}
5858

59+
#[derive(Diagnostic)]
60+
#[diag(driver_impl_ice_path)]
61+
pub(crate) struct IcePath {
62+
pub path: String,
63+
}
64+
65+
#[derive(Diagnostic)]
66+
#[diag(driver_impl_ice_path_error)]
67+
pub(crate) struct IcePathError {
68+
pub path: String,
69+
pub error: String,
70+
#[subdiagnostic]
71+
pub env_var: Option<IcePathErrorEnv>,
72+
}
73+
74+
#[derive(Subdiagnostic)]
75+
#[note(driver_impl_ice_path_error_env)]
76+
pub(crate) struct IcePathErrorEnv {
77+
pub env_var: String,
78+
}
79+
5980
#[derive(Diagnostic)]
6081
#[diag(driver_impl_ice_flags)]
6182
pub(crate) struct IceFlags {

Diff for: compiler/rustc_error_messages/src/lib.rs

+7
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,13 @@ impl DiagnosticMessage {
354354
}
355355
}
356356
}
357+
358+
pub fn as_str(&self) -> Option<&str> {
359+
match self {
360+
DiagnosticMessage::Eager(s) | DiagnosticMessage::Str(s) => Some(s),
361+
DiagnosticMessage::FluentIdentifier(_, _) => None,
362+
}
363+
}
357364
}
358365

359366
impl From<String> for DiagnosticMessage {

Diff for: compiler/rustc_errors/src/json/tests.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ fn test_positions(code: &str, span: (u32, u32), expected_output: SpanTestData) {
6464
);
6565

6666
let span = Span::with_root_ctxt(BytePos(span.0), BytePos(span.1));
67-
let handler = Handler::with_emitter(true, None, Box::new(je));
67+
let handler = Handler::with_emitter(true, None, Box::new(je), None);
6868
handler.span_err(span, "foo");
6969

7070
let bytes = output.lock().unwrap();

Diff for: compiler/rustc_errors/src/lib.rs

+28-3
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,10 @@ use std::borrow::Cow;
4747
use std::error::Report;
4848
use std::fmt;
4949
use std::hash::Hash;
50+
use std::io::Write;
5051
use std::num::NonZeroUsize;
5152
use std::panic;
52-
use std::path::Path;
53+
use std::path::{Path, PathBuf};
5354

5455
use termcolor::{Color, ColorSpec};
5556

@@ -461,6 +462,10 @@ struct HandlerInner {
461462
///
462463
/// [RFC-2383]: https://rust-lang.github.io/rfcs/2383-lint-reasons.html
463464
fulfilled_expectations: FxHashSet<LintExpectationId>,
465+
466+
/// The file where the ICE information is stored. This allows delayed_span_bug backtraces to be
467+
/// stored along side the main panic backtrace.
468+
ice_file: Option<PathBuf>,
464469
}
465470

466471
/// A key denoting where from a diagnostic was stashed.
@@ -550,13 +555,15 @@ impl Handler {
550555
sm: Option<Lrc<SourceMap>>,
551556
fluent_bundle: Option<Lrc<FluentBundle>>,
552557
fallback_bundle: LazyFallbackBundle,
558+
ice_file: Option<PathBuf>,
553559
) -> Self {
554560
Self::with_tty_emitter_and_flags(
555561
color_config,
556562
sm,
557563
fluent_bundle,
558564
fallback_bundle,
559565
HandlerFlags { can_emit_warnings, treat_err_as_bug, ..Default::default() },
566+
ice_file,
560567
)
561568
}
562569

@@ -566,6 +573,7 @@ impl Handler {
566573
fluent_bundle: Option<Lrc<FluentBundle>>,
567574
fallback_bundle: LazyFallbackBundle,
568575
flags: HandlerFlags,
576+
ice_file: Option<PathBuf>,
569577
) -> Self {
570578
let emitter = Box::new(EmitterWriter::stderr(
571579
color_config,
@@ -579,23 +587,26 @@ impl Handler {
579587
flags.track_diagnostics,
580588
TerminalUrl::No,
581589
));
582-
Self::with_emitter_and_flags(emitter, flags)
590+
Self::with_emitter_and_flags(emitter, flags, ice_file)
583591
}
584592

585593
pub fn with_emitter(
586594
can_emit_warnings: bool,
587595
treat_err_as_bug: Option<NonZeroUsize>,
588596
emitter: Box<dyn Emitter + sync::Send>,
597+
ice_file: Option<PathBuf>,
589598
) -> Self {
590599
Handler::with_emitter_and_flags(
591600
emitter,
592601
HandlerFlags { can_emit_warnings, treat_err_as_bug, ..Default::default() },
602+
ice_file,
593603
)
594604
}
595605

596606
pub fn with_emitter_and_flags(
597607
emitter: Box<dyn Emitter + sync::Send>,
598608
flags: HandlerFlags,
609+
ice_file: Option<PathBuf>,
599610
) -> Self {
600611
Self {
601612
flags,
@@ -618,6 +629,7 @@ impl Handler {
618629
check_unstable_expect_diagnostics: false,
619630
unstable_expect_diagnostics: Vec::new(),
620631
fulfilled_expectations: Default::default(),
632+
ice_file,
621633
}),
622634
}
623635
}
@@ -1657,8 +1669,21 @@ impl HandlerInner {
16571669
explanation: impl Into<DiagnosticMessage> + Copy,
16581670
) {
16591671
let mut no_bugs = true;
1672+
// If backtraces are enabled, also print the query stack
1673+
let backtrace = std::env::var_os("RUST_BACKTRACE").map_or(true, |x| &x != "0");
16601674
for bug in bugs {
1661-
let mut bug = bug.decorate();
1675+
if let Some(file) = self.ice_file.as_ref()
1676+
&& let Ok(mut out) = std::fs::File::options().append(true).open(file)
1677+
{
1678+
let _ = write!(
1679+
&mut out,
1680+
"\n\ndelayed span bug: {}\n{}",
1681+
bug.inner.styled_message().iter().filter_map(|(msg, _)| msg.as_str()).collect::<String>(),
1682+
&bug.note
1683+
);
1684+
}
1685+
let mut bug =
1686+
if backtrace || self.ice_file.is_none() { bug.decorate() } else { bug.inner };
16621687

16631688
if no_bugs {
16641689
// Put the overall explanation before the `DelayedBug`s, to

Diff for: compiler/rustc_expand/src/tests.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ fn test_harness(file_text: &str, span_labels: Vec<SpanLabel>, expected_output: &
161161
false,
162162
TerminalUrl::No,
163163
);
164-
let handler = Handler::with_emitter(true, None, Box::new(emitter));
164+
let handler = Handler::with_emitter(true, None, Box::new(emitter), None);
165165
#[allow(rustc::untranslatable_diagnostic)]
166166
handler.span_err(msp, "foo");
167167

0 commit comments

Comments
 (0)