Skip to content

Commit

Permalink
Include thread spawn in backtrace
Browse files Browse the repository at this point in the history
  • Loading branch information
actuallyatoaster committed Dec 13, 2024
1 parent ec037c6 commit 6ea9883
Show file tree
Hide file tree
Showing 58 changed files with 1,129 additions and 8 deletions.
39 changes: 31 additions & 8 deletions src/concurrency/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,9 @@ pub struct Thread<'tcx> {
/// The join status.
join_status: ThreadJoinStatus,

// ThreadId that spawned this thread and backtrace to where this thread was spawned
thread_spawn_context: Option<(ThreadId, Vec<FrameInfo<'tcx>>)>,

/// Stack of active panic payloads for the current thread. Used for storing
/// the argument of the call to `miri_start_unwind` (the panic payload) when unwinding.
/// This is pointer-sized, and matches the `Payload` type in `src/libpanic_unwind/miri.rs`.
Expand Down Expand Up @@ -321,13 +324,18 @@ impl<'tcx> std::fmt::Debug for Thread<'tcx> {
}

impl<'tcx> Thread<'tcx> {
fn new(name: Option<&str>, on_stack_empty: Option<StackEmptyCallback<'tcx>>) -> Self {
fn new(
name: Option<&str>,
on_stack_empty: Option<StackEmptyCallback<'tcx>>,
thread_spawn_context: Option<(ThreadId, Vec<FrameInfo<'tcx>>)>,
) -> Self {
Self {
state: ThreadState::Enabled,
thread_name: name.map(|name| Vec::from(name.as_bytes())),
stack: Vec::new(),
top_user_relevant_frame: None,
join_status: ThreadJoinStatus::Joinable,
thread_spawn_context,
panic_payloads: Vec::new(),
last_error: None,
on_stack_empty,
Expand All @@ -345,6 +353,7 @@ impl VisitProvenance for Thread<'_> {
state: _,
thread_name: _,
join_status: _,
thread_spawn_context: _,
on_stack_empty: _, // we assume the closure captures no GC-relevant state
} = self;

Expand Down Expand Up @@ -475,7 +484,7 @@ impl<'tcx> Default for ThreadManager<'tcx> {
fn default() -> Self {
let mut threads = IndexVec::new();
// Create the main thread and add it to the list of threads.
threads.push(Thread::new(Some("main"), None));
threads.push(Thread::new(Some("main"), None, None));
Self {
active_thread: ThreadId::MAIN_THREAD,
threads,
Expand Down Expand Up @@ -542,9 +551,13 @@ impl<'tcx> ThreadManager<'tcx> {
}

/// Create a new thread and returns its id.
fn create_thread(&mut self, on_stack_empty: StackEmptyCallback<'tcx>) -> ThreadId {
fn create_thread(
&mut self,
on_stack_empty: StackEmptyCallback<'tcx>,
thread_spawn_context: Option<(ThreadId, Vec<FrameInfo<'tcx>>)>,
) -> ThreadId {
let new_thread_id = ThreadId::new(self.threads.len());
self.threads.push(Thread::new(None, Some(on_stack_empty)));
self.threads.push(Thread::new(None, Some(on_stack_empty), thread_spawn_context));
new_thread_id
}

Expand Down Expand Up @@ -711,6 +724,13 @@ impl<'tcx> ThreadManager<'tcx> {
self.threads[thread].thread_display_name(thread)
}

pub fn get_thread_spawn_context(
&self,
thread: ThreadId,
) -> Option<&(ThreadId, Vec<FrameInfo<'tcx>>)> {
self.threads[thread].thread_spawn_context.as_ref()
}

/// Put the thread into the blocked state.
fn block_thread(
&mut self,
Expand Down Expand Up @@ -924,10 +944,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let this = self.eval_context_mut();

// Create the new thread
let new_thread_id = this.machine.threads.create_thread({
let mut state = tls::TlsDtorsState::default();
Box::new(move |m| state.on_stack_empty(m))
});
let new_thread_id = this.machine.threads.create_thread(
{
let mut state = tls::TlsDtorsState::default();
Box::new(move |m| state.on_stack_empty(m))
},
Some((this.machine.threads.active_thread(), this.generate_stacktrace())),
);
let current_span = this.machine.current_span();
if let Some(data_race) = &mut this.machine.data_race {
data_race.thread_created(&this.machine.threads, new_thread_id, current_span);
Expand Down
30 changes: 30 additions & 0 deletions src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,36 @@ pub fn report_msg<'tcx>(
}
}

// Traverse thread spawns to display thread backtraces up to main thread
if let Some(mut thread) = thread {
let mut thread_spawn_context = machine.threads.get_thread_spawn_context(thread);

while let Some((spawning_thread, thread_spawn_backtrace)) = thread_spawn_context {
err.note(format!(
"thread `{}` was spawned by thread `{}`",
machine.threads.get_thread_display_name(thread),
machine.threads.get_thread_display_name(*spawning_thread)
));
let (pruned_stacktrace, _was_pruned) =
prune_stacktrace(thread_spawn_backtrace.to_vec(), machine);

for (idx, frame_info) in pruned_stacktrace.iter().enumerate() {
let is_local = machine.is_local(frame_info);
// No span for non-local frames except the first frame (which is the error site).
if is_local && idx > 0 {
err.subdiagnostic(frame_info.as_note(machine.tcx));
} else {
let sm = sess.source_map();
let span = sm.span_to_embeddable_string(frame_info.span);
err.note(format!("{frame_info} at {span}"));
}
}

thread = *spawning_thread;
thread_spawn_context = machine.threads.get_thread_spawn_context(thread);
}
}

err.emit();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ error: Undefined Behavior: calling a function with more arguments than it expect
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE on thread `unnamed-ID`:
= note: thread `unnamed-ID` was spawned by thread `main`
= note: inside `main` at tests/fail-dep/concurrency/libc_pthread_create_too_few_args.rs:LL:CC

error: aborting due to 1 previous error

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ error: Undefined Behavior: calling a function with fewer arguments than it requi
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE on thread `unnamed-ID`:
= note: thread `unnamed-ID` was spawned by thread `main`
= note: inside `main` at tests/fail-dep/concurrency/libc_pthread_create_too_many_args.rs:LL:CC

error: aborting due to 1 previous error

16 changes: 16 additions & 0 deletions tests/fail-dep/concurrency/libc_pthread_join_main.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,22 @@ LL | assert_eq!(libc::pthread_join(thread_id, ptr::null_mut()), 0);
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE on thread `unnamed-ID`:
= note: inside closure at tests/fail-dep/concurrency/libc_pthread_join_main.rs:LL:CC
= note: thread `unnamed-ID` was spawned by thread `main`
= note: inside `std::sys::pal::PLATFORM::thread::Thread::new` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked_::<'_, {closure@tests/fail-dep/concurrency/libc_pthread_join_main.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked::<{closure@tests/fail-dep/concurrency/libc_pthread_join_main.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_join_main.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_join_main.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
note: inside `main`
--> tests/fail-dep/concurrency/libc_pthread_join_main.rs:LL:CC
|
LL | let handle = thread::spawn(move || {
| __________________^
LL | | unsafe {
LL | | assert_eq!(libc::pthread_join(thread_id, ptr::null_mut()), 0);
LL | | }
LL | | });
| |______^

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

Expand Down
14 changes: 14 additions & 0 deletions tests/fail-dep/concurrency/libc_pthread_join_multiple.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@ LL | ... assert_eq!(libc::pthread_join(native_copy, ptr::null_mut()), 0);
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE on thread `unnamed-ID`:
= note: inside closure at tests/fail-dep/concurrency/libc_pthread_join_multiple.rs:LL:CC
= note: thread `unnamed-ID` was spawned by thread `main`
= note: inside `std::sys::pal::PLATFORM::thread::Thread::new` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked_::<'_, {closure@tests/fail-dep/concurrency/libc_pthread_join_multiple.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked::<{closure@tests/fail-dep/concurrency/libc_pthread_join_multiple.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_join_multiple.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_join_multiple.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
note: inside `main`
--> tests/fail-dep/concurrency/libc_pthread_join_multiple.rs:LL:CC
|
LL | ... let handle = thread::spawn(move || {
| ____________________^
LL | | ... assert_eq!(libc::pthread_join(native_copy, ptr::null_mut()), 0);
LL | | ... });
| |________^

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

Expand Down
17 changes: 17 additions & 0 deletions tests/fail-dep/concurrency/libc_pthread_join_self.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,23 @@ LL | assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0);
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE on thread `unnamed-ID`:
= note: inside closure at tests/fail-dep/concurrency/libc_pthread_join_self.rs:LL:CC
= note: thread `unnamed-ID` was spawned by thread `main`
= note: inside `std::sys::pal::PLATFORM::thread::Thread::new` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked_::<'_, {closure@tests/fail-dep/concurrency/libc_pthread_join_self.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked::<{closure@tests/fail-dep/concurrency/libc_pthread_join_self.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_join_self.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_join_self.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
note: inside `main`
--> tests/fail-dep/concurrency/libc_pthread_join_self.rs:LL:CC
|
LL | let handle = thread::spawn(|| {
| __________________^
LL | | unsafe {
LL | | let native: libc::pthread_t = libc::pthread_self();
LL | | assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0);
LL | | }
LL | | });
| |______^

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

Expand Down
13 changes: 13 additions & 0 deletions tests/fail-dep/concurrency/libc_pthread_mutex_deadlock.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ LL | assert_eq!(libc::pthread_mutex_lock(lock_copy.0.get() as *mut _
|
= note: BACKTRACE on thread `unnamed-ID`:
= note: inside closure at tests/fail-dep/concurrency/libc_pthread_mutex_deadlock.rs:LL:CC
= note: thread `unnamed-ID` was spawned by thread `main`
= note: inside `std::sys::pal::PLATFORM::thread::Thread::new` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked_::<'_, {closure@tests/fail-dep/concurrency/libc_pthread_mutex_deadlock.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked::<{closure@tests/fail-dep/concurrency/libc_pthread_mutex_deadlock.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_mutex_deadlock.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_mutex_deadlock.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
note: inside `main`
--> tests/fail-dep/concurrency/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 | | })
| |__________^

error: deadlock: the evaluated program deadlocked
--> RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
Expand Down
13 changes: 13 additions & 0 deletions tests/fail-dep/concurrency/libc_pthread_mutex_wrong_owner.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ LL | ...t_eq!(libc::pthread_mutex_unlock(lock_copy.0.get() as *mut _), 0);
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE on thread `unnamed-ID`:
= note: inside closure at tests/fail-dep/concurrency/libc_pthread_mutex_wrong_owner.rs:LL:CC
= note: thread `unnamed-ID` was spawned by thread `main`
= note: inside `std::sys::pal::PLATFORM::thread::Thread::new` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked_::<'_, {closure@tests/fail-dep/concurrency/libc_pthread_mutex_wrong_owner.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked::<{closure@tests/fail-dep/concurrency/libc_pthread_mutex_wrong_owner.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_mutex_wrong_owner.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_mutex_wrong_owner.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
note: inside `main`
--> tests/fail-dep/concurrency/libc_pthread_mutex_wrong_owner.rs:LL:CC
|
LL | / ... thread::spawn(move || {
LL | | ... assert_eq!(libc::pthread_mutex_unlock(lock_copy.0.get() as *mut _), 0);
LL | | ... })
| |________^

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ LL | ... assert_eq!(libc::pthread_rwlock_unlock(lock_copy.0.get() as *mut _),
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE on thread `unnamed-ID`:
= note: inside closure at tests/fail-dep/concurrency/libc_pthread_rwlock_read_wrong_owner.rs:LL:CC
= note: thread `unnamed-ID` was spawned by thread `main`
= note: inside `std::sys::pal::PLATFORM::thread::Thread::new` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked_::<'_, {closure@tests/fail-dep/concurrency/libc_pthread_rwlock_read_wrong_owner.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked::<{closure@tests/fail-dep/concurrency/libc_pthread_rwlock_read_wrong_owner.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_rwlock_read_wrong_owner.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_rwlock_read_wrong_owner.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
note: inside `main`
--> tests/fail-dep/concurrency/libc_pthread_rwlock_read_wrong_owner.rs:LL:CC
|
LL | / ... thread::spawn(move || {
LL | | ... assert_eq!(libc::pthread_rwlock_unlock(lock_copy.0.get() as *mut _), 0);
LL | | ... })
| |________^

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ LL | assert_eq!(libc::pthread_rwlock_wrlock(lock_copy.0.get() as *mu
|
= note: BACKTRACE on thread `unnamed-ID`:
= note: inside closure at tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock.rs:LL:CC
= note: thread `unnamed-ID` was spawned by thread `main`
= note: inside `std::sys::pal::PLATFORM::thread::Thread::new` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked_::<'_, {closure@tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked::<{closure@tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
note: inside `main`
--> tests/fail-dep/concurrency/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 | | })
| |__________^

error: deadlock: the evaluated program deadlocked
--> RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ LL | assert_eq!(libc::pthread_rwlock_wrlock(lock_copy.0.get() as *mu
|
= note: BACKTRACE on thread `unnamed-ID`:
= note: inside closure at tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock.rs:LL:CC
= note: thread `unnamed-ID` was spawned by thread `main`
= note: inside `std::sys::pal::PLATFORM::thread::Thread::new` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked_::<'_, {closure@tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked::<{closure@tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
note: inside `main`
--> tests/fail-dep/concurrency/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 | | })
| |__________^

error: deadlock: the evaluated program deadlocked
--> RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
Expand Down
Loading

0 comments on commit 6ea9883

Please sign in to comment.