Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 08ffbb8

Browse files
committed
fix windows join/detach and add tests
1 parent b6fc2fc commit 08ffbb8

17 files changed

+265
-47
lines changed

src/machine.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
418418
) -> InterpResult<'tcx> {
419419
EnvVars::init(this, config)?;
420420
Evaluator::init_extern_statics(this)?;
421+
ThreadManager::init(this);
421422
Ok(())
422423
}
423424

src/shims/time.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,4 +233,30 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
233233

234234
Ok(0)
235235
}
236+
237+
#[allow(non_snake_case)]
238+
fn Sleep(&mut self, timeout: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> {
239+
let this = self.eval_context_mut();
240+
241+
this.check_no_isolation("`Sleep`")?;
242+
243+
let timeout_ms = this.read_scalar(timeout)?.to_u32()?;
244+
245+
let duration = Duration::from_millis(timeout_ms.into());
246+
let timeout_time = Time::Monotonic(Instant::now().checked_add(duration).unwrap());
247+
248+
let active_thread = this.get_active_thread();
249+
this.block_thread(active_thread);
250+
251+
this.register_timeout_callback(
252+
active_thread,
253+
timeout_time,
254+
Box::new(move |ecx| {
255+
ecx.unblock_thread(active_thread);
256+
Ok(())
257+
}),
258+
);
259+
260+
Ok(())
261+
}
236262
}

src/shims/unix/thread.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
3737
}
3838

3939
let thread_id = this.read_scalar(thread)?.to_machine_usize(this)?;
40-
this.join_thread(thread_id.try_into().expect("thread ID should fit in u32"))?;
40+
this.join_thread_exclusive(thread_id.try_into().expect("thread ID should fit in u32"))?;
4141

4242
Ok(0)
4343
}
@@ -46,7 +46,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
4646
let this = self.eval_context_mut();
4747

4848
let thread_id = this.read_scalar(thread)?.to_machine_usize(this)?;
49-
this.detach_thread(thread_id.try_into().expect("thread ID should fit in u32"))?;
49+
this.detach_thread(thread_id.try_into().expect("thread ID should fit in u32"), false)?;
5050

5151
Ok(0)
5252
}

src/shims/windows/dlsym.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use rustc_target::spec::abi::Abi;
55
use log::trace;
66

77
use crate::helpers::check_arg_count;
8-
use crate::shims::windows::handle::Handle;
8+
use crate::shims::windows::handle::{EvalContextExt as _, Handle};
99
use crate::*;
1010

1111
#[derive(Debug, Copy, Clone)]
@@ -118,7 +118,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
118118
match Handle::from_scalar(this.read_scalar(handle)?.check_init()?, this)? {
119119
Some(Handle::Thread(thread)) => thread,
120120
Some(Handle::CurrentThread) => this.get_active_thread(),
121-
_ => throw_ub_format!("invalid handle"),
121+
_ => this.invalid_handle("SetThreadDescription")?,
122122
};
123123

124124
this.set_thread_name_wide(thread, name);

src/shims/windows/foreign_items.rs

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -228,24 +228,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
228228
let [timeout] =
229229
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
230230

231-
this.check_no_isolation("`Sleep`")?;
232-
233-
let timeout_ms = this.read_scalar(timeout)?.to_u32()?;
234-
235-
let duration = Duration::from_millis(timeout_ms as u64);
236-
let timeout_time = Time::Monotonic(Instant::now().checked_add(duration).unwrap());
237-
238-
let active_thread = this.get_active_thread();
239-
this.block_thread(active_thread);
240-
241-
this.register_timeout_callback(
242-
active_thread,
243-
timeout_time,
244-
Box::new(move |ecx| {
245-
ecx.unblock_thread(active_thread);
246-
Ok(())
247-
}),
248-
);
231+
this.Sleep(timeout)?;
249232
}
250233

251234
// Synchronization primitives

src/shims/windows/handle.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,16 +146,19 @@ impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tc
146146

147147
#[allow(non_snake_case)]
148148
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
149+
fn invalid_handle(&mut self, function_name: &str) -> InterpResult<'tcx, !> {
150+
throw_machine_stop!(TerminationInfo::Abort(format!(
151+
"invalid handle passed to `{function_name}`"
152+
)))
153+
}
154+
149155
fn CloseHandle(&mut self, handle_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> {
150156
let this = self.eval_context_mut();
151157

152158
match Handle::from_scalar(this.read_scalar(handle_op)?.check_init()?, this)? {
153-
Some(Handle::Thread(thread)) => this.detach_thread(thread)?,
154-
_ =>
155-
throw_machine_stop!(TerminationInfo::Abort(
156-
"invalid handle passed to `CloseHandle`".into()
157-
)),
158-
};
159+
Some(Handle::Thread(thread)) => this.detach_thread(thread, true)?,
160+
_ => this.invalid_handle("CloseHandle")?,
161+
}
159162

160163
Ok(())
161164
}

src/shims/windows/thread.rs

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
1-
use std::time::{Duration, Instant};
2-
31
use rustc_middle::ty::layout::LayoutOf;
42
use rustc_target::spec::abi::Abi;
53

6-
use crate::thread::Time;
74
use crate::*;
8-
use shims::windows::handle::Handle;
5+
use shims::windows::handle::{EvalContextExt as _, Handle};
96

107
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
118

@@ -59,25 +56,17 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
5956

6057
let thread = match Handle::from_scalar(this.read_scalar(handle)?.check_init()?, this)? {
6158
Some(Handle::Thread(thread)) => thread,
62-
Some(Handle::CurrentThread) => throw_ub_format!("trying to wait on itself"),
63-
_ => throw_ub_format!("invalid handle"),
59+
// Unlike on posix, joining the current thread is not UB on windows.
60+
// It will just deadlock.
61+
Some(Handle::CurrentThread) => this.get_active_thread(),
62+
_ => this.invalid_handle("WaitForSingleObject")?,
6463
};
6564

6665
if this.read_scalar(timeout)?.to_u32()? != this.eval_windows("c", "INFINITE")?.to_u32()? {
67-
this.check_no_isolation("`WaitForSingleObject` with non-infinite timeout")?;
66+
throw_unsup_format!("`WaitForSingleObject` with non-infinite timeout");
6867
}
6968

70-
let timeout_ms = this.read_scalar(timeout)?.to_u32()?;
71-
72-
let timeout_time = if timeout_ms == this.eval_windows("c", "INFINITE")?.to_u32()? {
73-
None
74-
} else {
75-
let duration = Duration::from_millis(timeout_ms as u64);
76-
77-
Some(Time::Monotonic(Instant::now().checked_add(duration).unwrap()))
78-
};
79-
80-
this.wait_on_thread(timeout_time, thread)?;
69+
this.join_thread(thread)?;
8170

8271
Ok(())
8372
}

src/thread.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,31 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
405405
Ok(())
406406
}
407407

408+
/// Mark that the active thread tries to exclusively join the thread with `joined_thread_id`.
409+
/// If the thread is already joined by another thread
410+
fn join_thread_exclusive(
411+
&mut self,
412+
joined_thread_id: ThreadId,
413+
data_race: Option<&mut data_race::GlobalState>,
414+
) -> InterpResult<'tcx> {
415+
if self.threads[joined_thread_id].join_status == ThreadJoinStatus::Joined {
416+
throw_ub_format!("trying to join an already joined thread");
417+
}
418+
419+
if joined_thread_id == self.active_thread {
420+
throw_ub_format!("trying to join itself");
421+
}
422+
423+
assert!(
424+
self.threads
425+
.iter()
426+
.all(|thread| thread.state != ThreadState::BlockedOnJoin(joined_thread_id)),
427+
"a joinable thread already has threads waiting for its termination"
428+
);
429+
430+
self.join_thread(joined_thread_id, data_race)
431+
}
432+
408433
/// Set the name of the given thread.
409434
pub fn set_thread_name(&mut self, thread: ThreadId, new_thread_name: Vec<u8>) {
410435
self.threads[thread].thread_name = Some(new_thread_name);
@@ -700,6 +725,15 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
700725
Ok(())
701726
}
702727

728+
#[inline]
729+
fn join_thread_exclusive(&mut self, joined_thread_id: ThreadId) -> InterpResult<'tcx> {
730+
let this = self.eval_context_mut();
731+
this.machine
732+
.threads
733+
.join_thread_exclusive(joined_thread_id, this.machine.data_race.as_mut())?;
734+
Ok(())
735+
}
736+
703737
#[inline]
704738
fn set_active_thread(&mut self, thread_id: ThreadId) -> ThreadId {
705739
let this = self.eval_context_mut();

tests/fail/concurrency/unwind_top_of_stack.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//@ignore-windows: No libc on Windows
1+
//@ignore-target-windows: No libc on Windows
22

33
//@compile-flags: -Zmiri-disable-abi-check
44

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//@only-target-windows: Uses win32 api functions
2+
//@error-pattern: Undefined Behavior: trying to join a detached thread
3+
4+
// Joining a detached thread is undefined behavior.
5+
6+
use std::os::windows::io::{AsRawHandle, RawHandle};
7+
use std::thread;
8+
9+
extern "system" {
10+
fn CloseHandle(handle: RawHandle) -> u32;
11+
}
12+
13+
fn main() {
14+
let thread = thread::spawn(|| ());
15+
16+
unsafe {
17+
assert_ne!(CloseHandle(thread.as_raw_handle()), 0);
18+
}
19+
20+
thread.join().unwrap();
21+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
error: Undefined Behavior: trying to join a detached thread
2+
--> RUSTLIB/std/src/sys/PLATFORM/thread.rs:LL:CC
3+
|
4+
LL | let rc = unsafe { c::WaitForSingleObject(self.handle.as_raw_handle(), c::INFINITE) };
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to join a detached thread
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= note: backtrace:
10+
= note: inside `std::sys::PLATFORM::thread::Thread::join` at RUSTLIB/std/src/sys/PLATFORM/thread.rs:LL:CC
11+
= note: inside `std::thread::JoinInner::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
12+
= note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
13+
note: inside `main` at $DIR/windows_join_detached.rs:LL:CC
14+
--> $DIR/windows_join_detached.rs:LL:CC
15+
|
16+
LL | thread.join().unwrap();
17+
| ^^^^^^^^^^^^^
18+
19+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
20+
21+
error: aborting due to previous error
22+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//@only-target-windows: Uses win32 api functions
2+
// We are making scheduler assumptions here.
3+
//@compile-flags: -Zmiri-preemption-rate=0
4+
5+
// On windows, joining main is not UB, but it will block a thread forever.
6+
7+
use std::thread;
8+
9+
extern "system" {
10+
fn WaitForSingleObject(handle: usize, timeout: u32) -> u32;
11+
}
12+
13+
const INFINITE: u32 = u32::MAX;
14+
15+
// This is how miri represents the handle for thread 0.
16+
// This value can be "legitimately" obtained by using `GetCurrentThread` with `DuplicateHandle`
17+
// but miri does not implement `DuplicateHandle` yet.
18+
const MAIN_THREAD: usize = 1 << 30;
19+
20+
fn main() {
21+
thread::spawn(|| {
22+
unsafe {
23+
assert_eq!(WaitForSingleObject(MAIN_THREAD, INFINITE), 0); //~ ERROR: deadlock: the evaluated program deadlocked
24+
}
25+
})
26+
.join()
27+
.unwrap();
28+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
error: deadlock: the evaluated program deadlocked
2+
--> $DIR/windows_join_main.rs:LL:CC
3+
|
4+
LL | WaitForSingleObject(MAIN_THREAD, INFINITE);
5+
| ^ the evaluated program deadlocked
6+
|
7+
= note: inside closure at $DIR/windows_join_main.rs:LL:CC
8+
9+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
10+
11+
error: aborting due to previous error
12+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//@only-target-windows: Uses win32 api functions
2+
// We are making scheduler assumptions here.
3+
//@compile-flags: -Zmiri-preemption-rate=0
4+
5+
// On windows, a thread joining itself is not UB, but it will deadlock.
6+
7+
use std::thread;
8+
9+
extern "system" {
10+
fn GetCurrentThread() -> usize;
11+
fn WaitForSingleObject(handle: usize, timeout: u32) -> u32;
12+
}
13+
14+
const INFINITE: u32 = u32::MAX;
15+
16+
fn main() {
17+
thread::spawn(|| {
18+
unsafe {
19+
let native = GetCurrentThread();
20+
assert_eq!(WaitForSingleObject(native, INFINITE), 0); //~ ERROR: deadlock: the evaluated program deadlocked
21+
}
22+
})
23+
.join()
24+
.unwrap();
25+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
error: deadlock: the evaluated program deadlocked
2+
--> $DIR/windows_join_self.rs:LL:CC
3+
|
4+
LL | assert_eq!(WaitForSingleObject(native, INFINITE), 0);
5+
| ^ the evaluated program deadlocked
6+
|
7+
= note: inside closure at $DIR/windows_join_self.rs:LL:CC
8+
9+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
10+
11+
error: aborting due to previous error
12+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//@only-target-windows: Uses win32 api functions
2+
// We are making scheduler assumptions here.
3+
//@compile-flags: -Zmiri-preemption-rate=0
4+
5+
use std::os::windows::io::IntoRawHandle;
6+
use std::thread;
7+
8+
extern "system" {
9+
fn CloseHandle(handle: usize) -> i32;
10+
}
11+
12+
fn main() {
13+
let thread = thread::spawn(|| {}).into_raw_handle() as usize;
14+
15+
// this yield ensures that `thread` is terminated by this point
16+
thread::yield_now();
17+
18+
unsafe {
19+
assert_ne!(CloseHandle(thread), 0);
20+
}
21+
}

0 commit comments

Comments
 (0)