diff --git a/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs b/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs index aa99a14b18e6..5a941ae9d030 100644 --- a/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs +++ b/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs @@ -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") }) diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs index 06e23188332a..e28e5f836977 100644 --- a/src/tools/miri/src/concurrency/thread.rs +++ b/src/tools/miri/src/concurrency/thread.rs @@ -445,11 +445,10 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> { ) -> &mut Vec>> { &mut self.threads[self.active_thread].stack } - pub fn all_stacks( &self, - ) -> impl Iterator>]> { - self.threads.iter().map(|t| &t.stack[..]) + ) -> impl Iterator>])> { + self.threads.iter_enumerated().map(|(id, t)| (id, &t.stack[..])) } /// Create a new thread and returns its id. diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index 30349c003a94..dfbcaaac5c6d 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -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::().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, String)>, helps: Vec<(Option, String)>, stacktrace: &[FrameInfo<'tcx>], + thread: Option, 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, ); } diff --git a/src/tools/miri/tests/fail-dep/concurrency/windows_join_main.rs b/src/tools/miri/tests/fail-dep/concurrency/windows_join_main.rs index 910e06222ee7..532bda201364 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/windows_join_main.rs +++ b/src/tools/miri/tests/fail-dep/concurrency/windows_join_main.rs @@ -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. diff --git a/src/tools/miri/tests/fail-dep/concurrency/windows_join_main.stderr b/src/tools/miri/tests/fail-dep/concurrency/windows_join_main.stderr index d9137ee74376..12f35fdeb02a 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/windows_join_main.stderr +++ b/src/tools/miri/tests/fail-dep/concurrency/windows_join_main.stderr @@ -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 diff --git a/src/tools/miri/tests/fail-dep/concurrency/windows_join_self.rs b/src/tools/miri/tests/fail-dep/concurrency/windows_join_self.rs index a7c8faf5a98b..a64265ca0ca5 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/windows_join_self.rs +++ b/src/tools/miri/tests/fail-dep/concurrency/windows_join_self.rs @@ -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. diff --git a/src/tools/miri/tests/fail-dep/concurrency/windows_join_self.stderr b/src/tools/miri/tests/fail-dep/concurrency/windows_join_self.stderr index 74699a0317ff..8d26c35de8ab 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/windows_join_self.stderr +++ b/src/tools/miri/tests/fail-dep/concurrency/windows_join_self.stderr @@ -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 diff --git a/src/tools/miri/tests/fail-dep/shims/sync/libc_pthread_mutex_deadlock.rs b/src/tools/miri/tests/fail-dep/shims/sync/libc_pthread_mutex_deadlock.rs index 6c3cb738e299..60d56d41fd98 100644 --- a/src/tools/miri/tests/fail-dep/shims/sync/libc_pthread_mutex_deadlock.rs +++ b/src/tools/miri/tests/fail-dep/shims/sync/libc_pthread_mutex_deadlock.rs @@ -1,4 +1,5 @@ //@ignore-target-windows: No libc on Windows +//@error-in-other-file: deadlock use std::cell::UnsafeCell; use std::sync::Arc; diff --git a/src/tools/miri/tests/fail-dep/shims/sync/libc_pthread_mutex_deadlock.stderr b/src/tools/miri/tests/fail-dep/shims/sync/libc_pthread_mutex_deadlock.stderr index 76b1d26bd332..987d0fc4c2d0 100644 --- a/src/tools/miri/tests/fail-dep/shims/sync/libc_pthread_mutex_deadlock.stderr +++ b/src/tools/miri/tests/fail-dep/shims/sync/libc_pthread_mutex_deadlock.stderr @@ -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 diff --git a/src/tools/miri/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_read_deadlock.rs b/src/tools/miri/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_read_deadlock.rs index 201844615e18..0f02c3231a68 100644 --- a/src/tools/miri/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_read_deadlock.rs +++ b/src/tools/miri/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_read_deadlock.rs @@ -1,4 +1,5 @@ //@ignore-target-windows: No libc on Windows +//@error-in-other-file: deadlock use std::cell::UnsafeCell; use std::sync::Arc; diff --git a/src/tools/miri/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_read_deadlock.stderr b/src/tools/miri/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_read_deadlock.stderr index 5501dab81aca..bc9b15f293ef 100644 --- a/src/tools/miri/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_read_deadlock.stderr +++ b/src/tools/miri/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_read_deadlock.stderr @@ -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 diff --git a/src/tools/miri/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_write_deadlock.rs b/src/tools/miri/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_write_deadlock.rs index b1d7e0492e5a..10be5b337523 100644 --- a/src/tools/miri/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_write_deadlock.rs +++ b/src/tools/miri/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_write_deadlock.rs @@ -1,4 +1,5 @@ //@ignore-target-windows: No libc on Windows +//@error-in-other-file: deadlock use std::cell::UnsafeCell; use std::sync::Arc; diff --git a/src/tools/miri/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_write_deadlock.stderr b/src/tools/miri/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_write_deadlock.stderr index 815d85af502c..66c142bbc5c8 100644 --- a/src/tools/miri/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_write_deadlock.stderr +++ b/src/tools/miri/tests/fail-dep/shims/sync/libc_pthread_rwlock_write_write_deadlock.stderr @@ -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 diff --git a/src/tools/miri/tests/fail/uninit/uninit_alloc_diagnostic.stderr b/src/tools/miri/tests/fail/uninit/uninit_alloc_diagnostic.stderr index cca17a07ec20..960cae901248 100644 --- a/src/tools/miri/tests/fail/uninit/uninit_alloc_diagnostic.stderr +++ b/src/tools/miri/tests/fail/uninit/uninit_alloc_diagnostic.stderr @@ -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 diff --git a/src/tools/miri/tests/fail/uninit/uninit_alloc_diagnostic_with_provenance.stderr b/src/tools/miri/tests/fail/uninit/uninit_alloc_diagnostic_with_provenance.stderr index 4dc2d27ead43..5439418f2677 100644 --- a/src/tools/miri/tests/fail/uninit/uninit_alloc_diagnostic_with_provenance.stderr +++ b/src/tools/miri/tests/fail/uninit/uninit_alloc_diagnostic_with_provenance.stderr @@ -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] (1 ptr byte)╼ 12 13 ╾43[ALLOC] (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