Skip to content

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+
}

0 commit comments

Comments
 (0)