Skip to content

Commit

Permalink
On nightly, dump ICE backtraces to disk
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
estebank committed Jul 19, 2023
1 parent 77e24f9 commit 8eb5843
Show file tree
Hide file tree
Showing 30 changed files with 272 additions and 54 deletions.
28 changes: 28 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3488,6 +3488,7 @@ dependencies = [
"rustc_trait_selection",
"rustc_ty_utils",
"serde_json",
"time",
"tracing",
"windows",
]
Expand Down Expand Up @@ -5142,6 +5143,33 @@ dependencies = [
name = "tier-check"
version = "0.1.0"

[[package]]
name = "time"
version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd"
dependencies = [
"itoa",
"serde",
"time-core",
"time-macros",
]

[[package]]
name = "time-core"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"

[[package]]
name = "time-macros"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b"
dependencies = [
"time-core",
]

[[package]]
name = "tinystr"
version = "0.7.1"
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_codegen_ssa/src/back/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ pub struct CodegenContext<B: WriteBackendMethods> {

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

pub fn config(&self, kind: ModuleKind) -> &ModuleConfig {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_driver_impl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ edition = "2021"
[lib]

[dependencies]
time = { version = "0.3", default-features = false, features = ["formatting", ] }
tracing = { version = "0.1.35" }
serde_json = "1.0.59"
rustc_log = { path = "../rustc_log" }
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_driver_impl/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ driver_impl_ice_bug_report = we would appreciate a bug report: {$bug_report_url}
driver_impl_ice_exclude_cargo_defaults = some of the compiler flags provided by cargo are hidden
driver_impl_ice_flags = compiler flags: {$flags}
driver_impl_ice_path = please attach the file at `{$path}` to your bug report
driver_impl_ice_path_error = the ICE couldn't be written to `{$path}`: {$error}
driver_impl_ice_path_error_env = the environment variable `RUSTC_ICE` is set to `{$env_var}`
driver_impl_ice_version = rustc {$version} running on {$triple}
driver_impl_rlink_empty_version_number = The input does not contain version number
driver_impl_rlink_encoding_version_mismatch = .rlink file was produced with encoding version `{$version_array}`, but the current version is `{$rlink_version}`
Expand Down
80 changes: 67 additions & 13 deletions compiler/rustc_driver_impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
#![feature(lazy_cell)]
#![feature(decl_macro)]
#![feature(ice_to_disk)]
#![feature(let_chains)]
#![recursion_limit = "256"]
#![allow(rustc::potential_query_instability)]
#![deny(rustc::untranslatable_diagnostic)]
Expand Down Expand Up @@ -57,8 +59,11 @@ use std::panic::{self, catch_unwind};
use std::path::PathBuf;
use std::process::{self, Command, Stdio};
use std::str;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::OnceLock;
use std::time::Instant;
use std::time::{Instant, SystemTime};
use time::format_description::well_known::Rfc3339;
use time::OffsetDateTime;

#[allow(unused_macros)]
macro do_not_use_print($($t:tt)*) {
Expand Down Expand Up @@ -294,6 +299,7 @@ fn run_compiler(
input: Input::File(PathBuf::new()),
output_file: ofile,
output_dir: odir,
ice_file: ice_path().clone(),
file_loader,
locale_resources: DEFAULT_LOCALE_RESOURCES,
lint_caps: Default::default(),
Expand Down Expand Up @@ -1292,9 +1298,29 @@ pub fn catch_with_exit_code(f: impl FnOnce() -> interface::Result<()>) -> i32 {
}
}

/// Stores the default panic hook, from before [`install_ice_hook`] was called.
static DEFAULT_HOOK: OnceLock<Box<dyn Fn(&panic::PanicInfo<'_>) + Sync + Send + 'static>> =
OnceLock::new();
pub static ICE_PATH: OnceLock<Option<PathBuf>> = OnceLock::new();

pub fn ice_path() -> &'static Option<PathBuf> {
ICE_PATH.get_or_init(|| {
if !rustc_feature::UnstableFeatures::from_environment(None).is_nightly_build() {
return None;
}
if let Ok("0") = std::env::var("RUST_BACKTRACE").as_deref() {
return None;
}
let mut path = match std::env::var("RUSTC_ICE").as_deref() {
// Explicitly opting out of writing ICEs to disk.
Ok("0") => return None,
Ok(s) => PathBuf::from(s),
Err(_) => std::env::current_dir().unwrap_or_default(),
};
let now: OffsetDateTime = SystemTime::now().into();
let file_now = now.format(&Rfc3339).unwrap_or(String::new());
let pid = std::process::id();
path.push(format!("rustc-ice-{file_now}-{pid}.txt"));
Some(path)
})
}

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

let default_hook = DEFAULT_HOOK.get_or_init(panic::take_hook);

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

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

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

handler.emit_note(session_diagnostics::IceBugReport { bug_report_url });
handler.emit_note(session_diagnostics::IceVersion {
version: util::version_str!().unwrap_or("unknown_version"),
triple: config::host_triple(),
});

let version = util::version_str!().unwrap_or("unknown_version");
let triple = config::host_triple();

static FIRST_PANIC: AtomicBool = AtomicBool::new(true);

let file = if let Some(path) = ice_path().as_ref() {
// Create the ICE dump target file.
match crate::fs::File::options().create(true).append(true).open(&path) {
Ok(mut file) => {
handler
.emit_note(session_diagnostics::IcePath { path: path.display().to_string() });
if FIRST_PANIC.swap(false, Ordering::SeqCst) {
let _ = write!(file, "\n\nrustc version: {version}\nplatform: {triple}");
}
Some(file)
}
Err(err) => {
// The path ICE couldn't be written to disk, provide feedback to the user as to why.
handler.emit_warning(session_diagnostics::IcePathError {
path: path.display().to_string(),
error: err.to_string(),
env_var: std::env::var("RUSTC_ICE")
.ok()
.map(|env_var| session_diagnostics::IcePathErrorEnv { env_var }),
});
handler.emit_note(session_diagnostics::IceVersion { version, triple });
None
}
}
} else {
handler.emit_note(session_diagnostics::IceVersion { version, triple });
None
};

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

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

interface::try_print_query_stack(&handler, num_frames);
interface::try_print_query_stack(&handler, num_frames, file);

// We don't trust this callback not to panic itself, so run it at the end after we're sure we've
// printed all the relevant info.
Expand Down
23 changes: 22 additions & 1 deletion compiler/rustc_driver_impl/src/session_diagnostics.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use rustc_macros::Diagnostic;
use rustc_macros::{Diagnostic, Subdiagnostic};

#[derive(Diagnostic)]
#[diag(driver_impl_rlink_unable_to_read)]
Expand Down Expand Up @@ -56,6 +56,27 @@ pub(crate) struct IceVersion<'a> {
pub triple: &'a str,
}

#[derive(Diagnostic)]
#[diag(driver_impl_ice_path)]
pub(crate) struct IcePath {
pub path: String,
}

#[derive(Diagnostic)]
#[diag(driver_impl_ice_path_error)]
pub(crate) struct IcePathError {
pub path: String,
pub error: String,
#[subdiagnostic]
pub env_var: Option<IcePathErrorEnv>,
}

#[derive(Subdiagnostic)]
#[note(driver_impl_ice_path_error_env)]
pub(crate) struct IcePathErrorEnv {
pub env_var: String,
}

#[derive(Diagnostic)]
#[diag(driver_impl_ice_flags)]
pub(crate) struct IceFlags {
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_error_messages/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,13 @@ impl DiagnosticMessage {
}
}
}

pub fn as_str(&self) -> Option<&str> {
match self {
DiagnosticMessage::Eager(s) | DiagnosticMessage::Str(s) => Some(s),
DiagnosticMessage::FluentIdentifier(_, _) => None,
}
}
}

impl From<String> for DiagnosticMessage {
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_errors/src/json/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ fn test_positions(code: &str, span: (u32, u32), expected_output: SpanTestData) {
);

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

let bytes = output.lock().unwrap();
Expand Down
31 changes: 28 additions & 3 deletions compiler/rustc_errors/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@ use std::borrow::Cow;
use std::error::Report;
use std::fmt;
use std::hash::Hash;
use std::io::Write;
use std::num::NonZeroUsize;
use std::panic;
use std::path::Path;
use std::path::{Path, PathBuf};

use termcolor::{Color, ColorSpec};

Expand Down Expand Up @@ -461,6 +462,10 @@ struct HandlerInner {
///
/// [RFC-2383]: https://rust-lang.github.io/rfcs/2383-lint-reasons.html
fulfilled_expectations: FxHashSet<LintExpectationId>,

/// The file where the ICE information is stored. This allows delayed_span_bug backtraces to be
/// stored along side the main panic backtrace.
ice_file: Option<PathBuf>,
}

/// A key denoting where from a diagnostic was stashed.
Expand Down Expand Up @@ -550,13 +555,15 @@ impl Handler {
sm: Option<Lrc<SourceMap>>,
fluent_bundle: Option<Lrc<FluentBundle>>,
fallback_bundle: LazyFallbackBundle,
ice_file: Option<PathBuf>,
) -> Self {
Self::with_tty_emitter_and_flags(
color_config,
sm,
fluent_bundle,
fallback_bundle,
HandlerFlags { can_emit_warnings, treat_err_as_bug, ..Default::default() },
ice_file,
)
}

Expand All @@ -566,6 +573,7 @@ impl Handler {
fluent_bundle: Option<Lrc<FluentBundle>>,
fallback_bundle: LazyFallbackBundle,
flags: HandlerFlags,
ice_file: Option<PathBuf>,
) -> Self {
let emitter = Box::new(EmitterWriter::stderr(
color_config,
Expand All @@ -579,23 +587,26 @@ impl Handler {
flags.track_diagnostics,
TerminalUrl::No,
));
Self::with_emitter_and_flags(emitter, flags)
Self::with_emitter_and_flags(emitter, flags, ice_file)
}

pub fn with_emitter(
can_emit_warnings: bool,
treat_err_as_bug: Option<NonZeroUsize>,
emitter: Box<dyn Emitter + sync::Send>,
ice_file: Option<PathBuf>,
) -> Self {
Handler::with_emitter_and_flags(
emitter,
HandlerFlags { can_emit_warnings, treat_err_as_bug, ..Default::default() },
ice_file,
)
}

pub fn with_emitter_and_flags(
emitter: Box<dyn Emitter + sync::Send>,
flags: HandlerFlags,
ice_file: Option<PathBuf>,
) -> Self {
Self {
flags,
Expand All @@ -618,6 +629,7 @@ impl Handler {
check_unstable_expect_diagnostics: false,
unstable_expect_diagnostics: Vec::new(),
fulfilled_expectations: Default::default(),
ice_file,
}),
}
}
Expand Down Expand Up @@ -1657,8 +1669,21 @@ impl HandlerInner {
explanation: impl Into<DiagnosticMessage> + Copy,
) {
let mut no_bugs = true;
// If backtraces are enabled, also print the query stack
let backtrace = std::env::var_os("RUST_BACKTRACE").map_or(true, |x| &x != "0");
for bug in bugs {
let mut bug = bug.decorate();
if let Some(file) = self.ice_file.as_ref()
&& let Ok(mut out) = std::fs::File::options().append(true).open(file)
{
let _ = write!(
&mut out,
"\n\ndelayed span bug: {}\n{}",
bug.inner.styled_message().iter().filter_map(|(msg, _)| msg.as_str()).collect::<String>(),
&bug.note
);
}
let mut bug =
if backtrace || self.ice_file.is_none() { bug.decorate() } else { bug.inner };

if no_bugs {
// Put the overall explanation before the `DelayedBug`s, to
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_expand/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ fn test_harness(file_text: &str, span_labels: Vec<SpanLabel>, expected_output: &
false,
TerminalUrl::No,
);
let handler = Handler::with_emitter(true, None, Box::new(emitter));
let handler = Handler::with_emitter(true, None, Box::new(emitter), None);
#[allow(rustc::untranslatable_diagnostic)]
handler.span_err(msp, "foo");

Expand Down
Loading

0 comments on commit 8eb5843

Please sign in to comment.