Auto merge of #3472 - RalfJung:deadlock, r=RalfJung

deadlock: show backtrace for all threads

Fixes https://github.com/rust-lang/miri/issues/3424
This commit is contained in:
bors 2024-04-16 12:40:53 +00:00
commit fee494b2b2
15 changed files with 175 additions and 25 deletions

View file

@ -438,7 +438,7 @@ impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
.machine
.threads
.all_stacks()
.flatten()
.flat_map(|(_id, stack)| stack)
.map(|frame| {
frame.extra.borrow_tracker.as_ref().expect("we should have borrow tracking data")
})

View file

@ -445,11 +445,10 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
) -> &mut Vec<Frame<'mir, 'tcx, Provenance, FrameExtra<'tcx>>> {
&mut self.threads[self.active_thread].stack
}
pub fn all_stacks(
&self,
) -> impl Iterator<Item = &[Frame<'mir, 'tcx, Provenance, FrameExtra<'tcx>>]> {
self.threads.iter().map(|t| &t.stack[..])
) -> impl Iterator<Item = (ThreadId, &[Frame<'mir, 'tcx, Provenance, FrameExtra<'tcx>>])> {
self.threads.iter_enumerated().map(|(id, t)| (id, &t.stack[..]))
}
/// Create a new thread and returns its id.

View file

@ -361,9 +361,12 @@ pub fn report_error<'tcx, 'mir>(
};
let stacktrace = ecx.generate_stacktrace();
let (stacktrace, was_pruned) = prune_stacktrace(stacktrace, &ecx.machine);
let (stacktrace, mut any_pruned) = prune_stacktrace(stacktrace, &ecx.machine);
// We want to dump the allocation if this is `InvalidUninitBytes`. Since `format_error` consumes `e`, we compute the outut early.
let mut show_all_threads = false;
// We want to dump the allocation if this is `InvalidUninitBytes`.
// Since `format_interp_error` consumes `e`, we compute the outut early.
let mut extra = String::new();
match e.kind() {
UndefinedBehavior(InvalidUninitBytes(Some((alloc_id, access)))) => {
@ -375,6 +378,15 @@ pub fn report_error<'tcx, 'mir>(
.unwrap();
writeln!(extra, "{:?}", ecx.dump_alloc(*alloc_id)).unwrap();
}
MachineStop(info) => {
let info = info.downcast_ref::<TerminationInfo>().expect("invalid MachineStop payload");
match info {
TerminationInfo::Deadlock => {
show_all_threads = true;
}
_ => {}
}
}
_ => {}
}
@ -387,18 +399,39 @@ pub fn report_error<'tcx, 'mir>(
vec![],
helps,
&stacktrace,
Some(ecx.get_active_thread()),
&ecx.machine,
);
eprint!("{extra}"); // newlines are already in the string
if show_all_threads {
for (thread, stack) in ecx.machine.threads.all_stacks() {
if thread != ecx.get_active_thread() {
let stacktrace = Frame::generate_stacktrace_from_stack(stack);
let (stacktrace, was_pruned) = prune_stacktrace(stacktrace, &ecx.machine);
any_pruned |= was_pruned;
report_msg(
DiagLevel::Error,
format!("deadlock: the evaluated program deadlocked"),
vec![format!("the evaluated program deadlocked")],
vec![],
vec![],
&stacktrace,
Some(thread),
&ecx.machine,
)
}
}
}
// Include a note like `std` does when we omit frames from a backtrace
if was_pruned {
if any_pruned {
ecx.tcx.dcx().note(
"some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace",
);
}
eprint!("{extra}"); // newlines are already in the string
// Debug-dump all locals.
for (i, frame) in ecx.active_thread_stack().iter().enumerate() {
trace!("-------------------");
@ -435,6 +468,7 @@ pub fn report_leaks<'mir, 'tcx>(
vec![],
vec![],
&backtrace,
None, // we don't know the thread this is from
&ecx.machine,
);
}
@ -457,6 +491,7 @@ pub fn report_msg<'tcx>(
notes: Vec<(Option<SpanData>, String)>,
helps: Vec<(Option<SpanData>, String)>,
stacktrace: &[FrameInfo<'tcx>],
thread: Option<ThreadId>,
machine: &MiriMachine<'_, 'tcx>,
) {
let span = stacktrace.first().map_or(DUMMY_SP, |fi| fi.span);
@ -506,12 +541,13 @@ pub fn report_msg<'tcx>(
if extra_span {
write!(backtrace_title, " (of the first span)").unwrap();
}
let thread_name =
machine.threads.get_thread_display_name(machine.threads.get_active_thread_id());
if thread_name != "main" {
// Only print thread name if it is not `main`.
write!(backtrace_title, " on thread `{thread_name}`").unwrap();
};
if let Some(thread) = thread {
let thread_name = machine.threads.get_thread_display_name(thread);
if thread_name != "main" {
// Only print thread name if it is not `main`.
write!(backtrace_title, " on thread `{thread_name}`").unwrap();
};
}
write!(backtrace_title, ":").unwrap();
err.note(backtrace_title);
for (idx, frame_info) in stacktrace.iter().enumerate() {
@ -628,7 +664,16 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
_ => vec![],
};
report_msg(diag_level, title, vec![msg], notes, helps, &stacktrace, self);
report_msg(
diag_level,
title,
vec![msg],
notes,
helps,
&stacktrace,
Some(self.threads.get_active_thread_id()),
self,
);
}
}
@ -654,6 +699,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
vec![],
vec![],
&stacktrace,
Some(this.get_active_thread()),
&this.machine,
);
}

View file

@ -1,6 +1,7 @@
//@only-target-windows: Uses win32 api functions
// We are making scheduler assumptions here.
//@compile-flags: -Zmiri-preemption-rate=0
//@error-in-other-file: deadlock
// On windows, joining main is not UB, but it will block a thread forever.

View file

@ -8,7 +8,28 @@ LL | assert_eq!(WaitForSingleObject(MAIN_THREAD, INFINITE), WAIT_OBJ
= note: inside closure at RUSTLIB/core/src/macros/mod.rs:LL:CC
= note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)
error: deadlock: the evaluated program deadlocked
--> RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
|
LL | let rc = unsafe { c::WaitForSingleObject(self.handle.as_raw_handle(), c::INFINITE) };
| ^ the evaluated program deadlocked
|
= note: BACKTRACE:
= note: inside `std::sys::pal::PLATFORM::thread::Thread::join` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
= note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
note: inside `main`
--> $DIR/windows_join_main.rs:LL:CC
|
LL | / thread::spawn(|| {
LL | | unsafe {
LL | | assert_eq!(WaitForSingleObject(MAIN_THREAD, INFINITE), WAIT_OBJECT_0);
LL | | }
LL | | })
LL | | .join()
| |___________^
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error
error: aborting due to 2 previous errors

View file

@ -1,6 +1,7 @@
//@only-target-windows: Uses win32 api functions
// We are making scheduler assumptions here.
//@compile-flags: -Zmiri-preemption-rate=0
//@error-in-other-file: deadlock
// On windows, a thread joining itself is not UB, but it will deadlock.

View file

@ -7,7 +7,29 @@ LL | assert_eq!(WaitForSingleObject(native, INFINITE), WAIT_OBJECT_0
= note: BACKTRACE on thread `unnamed-ID`:
= note: inside closure at $DIR/windows_join_self.rs:LL:CC
error: deadlock: the evaluated program deadlocked
--> RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
|
LL | let rc = unsafe { c::WaitForSingleObject(self.handle.as_raw_handle(), c::INFINITE) };
| ^ the evaluated program deadlocked
|
= note: BACKTRACE:
= note: inside `std::sys::pal::PLATFORM::thread::Thread::join` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
= note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
note: inside `main`
--> $DIR/windows_join_self.rs:LL:CC
|
LL | / thread::spawn(|| {
LL | | unsafe {
LL | | let native = GetCurrentThread();
LL | | assert_eq!(WaitForSingleObject(native, INFINITE), WAIT_OBJECT_0);
LL | | }
LL | | })
LL | | .join()
| |___________^
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error
error: aborting due to 2 previous errors

View file

@ -1,4 +1,5 @@
//@ignore-target-windows: No libc on Windows
//@error-in-other-file: deadlock
use std::cell::UnsafeCell;
use std::sync::Arc;

View file

@ -7,7 +7,26 @@ LL | assert_eq!(libc::pthread_mutex_lock(lock_copy.0.get() as *mut _
= note: BACKTRACE on thread `unnamed-ID`:
= note: inside closure at $DIR/libc_pthread_mutex_deadlock.rs:LL:CC
error: deadlock: the evaluated program deadlocked
--> RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
|
LL | let ret = libc::pthread_join(self.id, ptr::null_mut());
| ^ the evaluated program deadlocked
|
= note: BACKTRACE:
= note: inside `std::sys::pal::PLATFORM::thread::Thread::join` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
= note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
note: inside `main`
--> $DIR/libc_pthread_mutex_deadlock.rs:LL:CC
|
LL | / thread::spawn(move || {
LL | | assert_eq!(libc::pthread_mutex_lock(lock_copy.0.get() as *mut _), 0);
LL | | })
LL | | .join()
| |_______________^
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error
error: aborting due to 2 previous errors

View file

@ -1,4 +1,5 @@
//@ignore-target-windows: No libc on Windows
//@error-in-other-file: deadlock
use std::cell::UnsafeCell;
use std::sync::Arc;

View file

@ -7,7 +7,26 @@ LL | assert_eq!(libc::pthread_rwlock_wrlock(lock_copy.0.get() as *mu
= note: BACKTRACE on thread `unnamed-ID`:
= note: inside closure at $DIR/libc_pthread_rwlock_write_read_deadlock.rs:LL:CC
error: deadlock: the evaluated program deadlocked
--> RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
|
LL | let ret = libc::pthread_join(self.id, ptr::null_mut());
| ^ the evaluated program deadlocked
|
= note: BACKTRACE:
= note: inside `std::sys::pal::PLATFORM::thread::Thread::join` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
= note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
note: inside `main`
--> $DIR/libc_pthread_rwlock_write_read_deadlock.rs:LL:CC
|
LL | / thread::spawn(move || {
LL | | assert_eq!(libc::pthread_rwlock_wrlock(lock_copy.0.get() as *mut _), 0);
LL | | })
LL | | .join()
| |_______________^
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error
error: aborting due to 2 previous errors

View file

@ -1,4 +1,5 @@
//@ignore-target-windows: No libc on Windows
//@error-in-other-file: deadlock
use std::cell::UnsafeCell;
use std::sync::Arc;

View file

@ -7,7 +7,26 @@ LL | assert_eq!(libc::pthread_rwlock_wrlock(lock_copy.0.get() as *mu
= note: BACKTRACE on thread `unnamed-ID`:
= note: inside closure at $DIR/libc_pthread_rwlock_write_write_deadlock.rs:LL:CC
error: deadlock: the evaluated program deadlocked
--> RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
|
LL | let ret = libc::pthread_join(self.id, ptr::null_mut());
| ^ the evaluated program deadlocked
|
= note: BACKTRACE:
= note: inside `std::sys::pal::PLATFORM::thread::Thread::join` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
= note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
note: inside `main`
--> $DIR/libc_pthread_rwlock_write_write_deadlock.rs:LL:CC
|
LL | / thread::spawn(move || {
LL | | assert_eq!(libc::pthread_rwlock_wrlock(lock_copy.0.get() as *mut _), 0);
LL | | })
LL | | .join()
| |_______________^
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error
error: aborting due to 2 previous errors

View file

@ -15,13 +15,13 @@ note: inside `main`
LL | drop(slice1.cmp(slice2));
| ^^^^^^^^^^^^^^^^^^
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
Uninitialized memory occurred at ALLOC[0x4..0x10], in this allocation:
ALLOC (Rust heap, size: 32, align: 8) {
0x00 │ 41 42 43 44 __ __ __ __ __ __ __ __ __ __ __ __ │ ABCD░░░░░░░░░░░░
0x10 │ 00 __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ │ .░░░░░░░░░░░░░░░
}
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error

View file

@ -15,8 +15,6 @@ note: inside `main`
LL | drop(slice1.cmp(slice2));
| ^^^^^^^^^^^^^^^^^^
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
Uninitialized memory occurred at ALLOC[0x4..0x8], in this allocation:
ALLOC (Rust heap, size: 16, align: 8) {
╾42[ALLOC]<TAG> (1 ptr byte)╼ 12 13 ╾43[ALLOC]<TAG> (1 ptr byte)╼ __ __ __ __ __ __ __ __ __ __ __ __ │ ━..━░░░░░░░░░░░░
@ -28,5 +26,7 @@ ALLOC (global (static or const), size: 1, align: 1) {
00 │ .
}
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error