How to report extra information on crashes from global state without invalidating a pointer
⚓ Rust 📅 2025-12-03 👤 surdeus 👁️ 1Hi, I'm building a compiler for my new language "SUS". Since it's a rather complex project, it will be the case that releases still contain ICEs. To streamline the error reporting process, I save the input data for the compiler to a folder in a special crash_reports directory. Also, to aid myself in debugging I have added a panic handler that prints the last 10 locations in source files that the compiler looked at last. (Tracked using span.debug() calls at various places in the codebase.)
For reporting spans, I want it to happen before the debugger triggers the "on panic" breakpoint, to be able to easily see what kind of things the compiler was working on right before the panic happened. I can only get this if I use panic::set_hook, because panic::catch_unwind only runs after my debugger breakpoint.
The issue is, I can only get the panic::set_hook version to work by storing a static *const FileData. I'm of course editing the compiler state through a &mut, and looking at [Do reference accesses really invalidate pointers?] this would invalidate that pointer. Now, it works and I've been using this method for a while, but is there any better (non-technically-UB) way? Is there some way I could implement it in an (acceptably unsafe) manner to be more similar to create_dump_on_panic?
Relevant code: (here it is, in context)
/// Set up the hook to print spans. Uses [std::panic::set_hook] instead of [std::panic::catch_unwind] because this runs before my debugger "on panic" breakpoint.
pub fn setup_span_panic_handler() {
let default_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
default_hook(info);
DEBUG_STACK.with_borrow(|history| {
if let Some(last_stack_elem) = history.debug_stack.last() {
eprintln!(
"{}",
format!(
"Panic happened in Span-guarded context {} in {}",
last_stack_elem.stage.red(),
last_stack_elem.global_obj_name.red()
)
.red()
);
let file_data = unsafe { &*last_stack_elem.file_data };
//pretty_print_span(file_data, span, label);
print_most_recent_spans(file_data, last_stack_elem);
} else {
eprintln!("{}", "No Span-guarding context".red());
}
eprintln!("{}", "Most recent available debug paths:".red());
for (ctx, d) in &history.recent_debug_options {
if let Some(ctx) = ctx {
eprintln!("{}", format!("--debug-whitelist {ctx} --debug {d}").red());
} else {
eprintln!("{}", format!("(no SpanDebugger Context) --debug {d}").red());
}
}
})
}));
}
pub fn create_dump_on_panic<R>(linker: &mut Linker, f: impl FnOnce(&mut Linker) -> R) -> R {
if crate::config::config().no_redump {
// Run without protection, don't create a dump on panic
return f(linker);
}
use std::fs;
use std::io::Write;
let panic_info = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(linker))) {
Ok(result) => return result,
Err(panic_info) => panic_info,
};
// Get ./sus_crash_dumps/{timestamp}
let cur_time = chrono::Local::now()
.format("_%Y-%m-%d_%H:%M:%S")
.to_string();
// create crash_dump files ...
}
#[derive(Debug)]
struct SpanDebuggerStackElement {
stage: &'static str,
global_obj_name: String,
debugging_enabled: bool,
span_history: CircularBuffer<SPAN_TOUCH_HISTORY_SIZE, Span>,
file_data: *const FileData,
}
thread_local! {
static DEBUG_STACK : RefCell<PerThreadDebugInfo> = const { RefCell::new(PerThreadDebugInfo{debug_stack: Vec::new(), recent_debug_options: CircularBuffer::new()}) };
static MOST_RECENT_FILE_DATA: std::sync::atomic::AtomicPtr<FileData> = const {AtomicPtr::new(std::ptr::null_mut())}
}
/// Register a [crate::file_position::Span] for printing by [SpanDebugger] on panic.
pub fn add_debug_span(sp: Span) {
// Convert to range so we don't invoke any of Span's triggers
DEBUG_STACK.with_borrow_mut(|history| {
let Some(last) = history.debug_stack.last_mut() else {
return; // Can't track Spans not in a SpanDebugger region
};
last.span_history.push_back(sp);
});
}
4 posts - 2 participants
🏷️ Rust_feed