1use std::mem::variant_count;
2
3use rustc_abi::HasDataLayout;
4
5use crate::concurrency::thread::ThreadNotFound;
6use crate::shims::files::FdNum;
7use crate::*;
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
10pub enum PseudoHandle {
11 CurrentThread,
12 CurrentProcess,
13}
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
17pub enum Handle {
18 Null,
19 Pseudo(PseudoHandle),
20 Thread(ThreadId),
21 File(FdNum),
22 Invalid,
23}
24
25impl PseudoHandle {
26 const CURRENT_THREAD_VALUE: u32 = 0;
27 const CURRENT_PROCESS_VALUE: u32 = 1;
28
29 fn value(self) -> u32 {
30 match self {
31 Self::CurrentThread => Self::CURRENT_THREAD_VALUE,
32 Self::CurrentProcess => Self::CURRENT_PROCESS_VALUE,
33 }
34 }
35
36 fn from_value(value: u32) -> Option<Self> {
37 match value {
38 Self::CURRENT_THREAD_VALUE => Some(Self::CurrentThread),
39 Self::CURRENT_PROCESS_VALUE => Some(Self::CurrentProcess),
40 _ => None,
41 }
42 }
43}
44
45pub enum HandleError {
47 ThreadNotFound(ThreadNotFound),
49 InvalidHandle,
51}
52
53impl Handle {
54 const NULL_DISCRIMINANT: u32 = 0;
55 const PSEUDO_DISCRIMINANT: u32 = 1;
56 const THREAD_DISCRIMINANT: u32 = 2;
57 const FILE_DISCRIMINANT: u32 = 3;
58 const INVALID_DISCRIMINANT: u32 = 7;
61
62 fn discriminant(self) -> u32 {
63 match self {
64 Self::Null => Self::NULL_DISCRIMINANT,
65 Self::Pseudo(_) => Self::PSEUDO_DISCRIMINANT,
66 Self::Thread(_) => Self::THREAD_DISCRIMINANT,
67 Self::File(_) => Self::FILE_DISCRIMINANT,
68 Self::Invalid => Self::INVALID_DISCRIMINANT,
69 }
70 }
71
72 fn data(self) -> u32 {
73 match self {
74 Self::Null => 0,
75 Self::Pseudo(pseudo_handle) => pseudo_handle.value(),
76 Self::Thread(thread) => thread.to_u32(),
77 Self::File(fd) => fd.cast_unsigned(),
78 Self::Invalid => 0x1FFFFFFF,
83 }
84 }
85
86 fn packed_disc_size() -> u32 {
87 let variant_count = variant_count::<Self>();
92
93 let floor_log2 = variant_count.ilog2();
95
96 #[expect(clippy::arithmetic_side_effects)] if variant_count.is_power_of_two() { floor_log2 } else { floor_log2 + 1 }
99 }
100
101 fn to_packed(self) -> u32 {
108 let disc_size = Self::packed_disc_size();
109 let data_size = u32::BITS.strict_sub(disc_size);
110
111 let discriminant = self.discriminant();
112 let data = self.data();
113
114 assert!(discriminant < 2u32.pow(disc_size));
116
117 assert!(data < 2u32.pow(data_size));
119
120 (discriminant << data_size) | data
123 }
124
125 fn new(discriminant: u32, data: u32) -> Option<Self> {
126 match discriminant {
127 Self::NULL_DISCRIMINANT if data == 0 => Some(Self::Null),
128 Self::PSEUDO_DISCRIMINANT => Some(Self::Pseudo(PseudoHandle::from_value(data)?)),
129 Self::THREAD_DISCRIMINANT => Some(Self::Thread(ThreadId::new_unchecked(data))),
130 Self::FILE_DISCRIMINANT => {
131 assert_eq!(size_of_val(&data), size_of::<FdNum>());
133 Some(Self::File(data.cast_signed()))
134 }
135 Self::INVALID_DISCRIMINANT => Some(Self::Invalid),
136 _ => None,
137 }
138 }
139
140 fn from_packed(handle: u32) -> Option<Self> {
142 let disc_size = Self::packed_disc_size();
143 let data_size = u32::BITS.strict_sub(disc_size);
144
145 #[expect(clippy::arithmetic_side_effects)] let data_mask = 2u32.pow(data_size) - 1;
148
149 let discriminant = handle >> data_size;
151
152 let data = handle & data_mask;
154
155 Self::new(discriminant, data)
156 }
157
158 pub fn to_scalar(self, cx: &impl HasDataLayout) -> Scalar {
159 let signed_handle = self.to_packed().cast_signed();
162 Scalar::from_target_isize(signed_handle.into(), cx)
163 }
164
165 fn try_from_scalar<'tcx>(
174 handle: Scalar,
175 cx: &MiriInterpCx<'tcx>,
176 ) -> InterpResult<'tcx, Result<Self, HandleError>> {
177 let sign_extended_handle = handle.to_target_isize(cx)?;
178
179 let handle = if let Ok(signed_handle) = i32::try_from(sign_extended_handle) {
180 signed_handle.cast_unsigned()
181 } else {
182 return interp_ok(Err(HandleError::InvalidHandle));
184 };
185
186 match Self::from_packed(handle) {
187 Some(Self::Thread(thread)) => {
188 match cx.machine.threads.thread_id_try_from(thread.to_u32()) {
190 Ok(id) => interp_ok(Ok(Self::Thread(id))),
191 Err(e) => interp_ok(Err(HandleError::ThreadNotFound(e))),
192 }
193 }
194 Some(handle) => interp_ok(Ok(handle)),
195 None => interp_ok(Err(HandleError::InvalidHandle)),
196 }
197 }
198}
199
200impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
201
202#[allow(non_snake_case)]
203pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
204 #[track_caller]
207 fn read_handle(&self, handle: &OpTy<'tcx>, function_name: &str) -> InterpResult<'tcx, Handle> {
208 let this = self.eval_context_ref();
209 let handle = this.read_scalar(handle)?;
210 match Handle::try_from_scalar(handle, this)? {
211 Ok(handle) => interp_ok(handle),
212 Err(HandleError::InvalidHandle) =>
213 throw_machine_stop!(TerminationInfo::Abort(format!(
214 "invalid handle {} passed to {function_name}",
215 handle.to_target_isize(this)?,
216 ))),
217 Err(HandleError::ThreadNotFound(_)) =>
218 throw_machine_stop!(TerminationInfo::Abort(format!(
219 "invalid thread ID {} passed to {function_name}",
220 handle.to_target_isize(this)?,
221 ))),
222 }
223 }
224
225 fn invalid_handle(&mut self, function_name: &str) -> InterpResult<'tcx, !> {
226 throw_machine_stop!(TerminationInfo::Abort(format!(
227 "invalid handle passed to `{function_name}`"
228 )))
229 }
230
231 fn GetStdHandle(&mut self, which: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
232 let this = self.eval_context_mut();
233 let which = this.read_scalar(which)?.to_i32()?;
234
235 let stdin = this.eval_windows("c", "STD_INPUT_HANDLE").to_i32()?;
236 let stdout = this.eval_windows("c", "STD_OUTPUT_HANDLE").to_i32()?;
237 let stderr = this.eval_windows("c", "STD_ERROR_HANDLE").to_i32()?;
238
239 let fd_num = if which == stdin {
243 0
244 } else if which == stdout {
245 1
246 } else if which == stderr {
247 2
248 } else {
249 throw_unsup_format!("Invalid argument to `GetStdHandle`: {which}")
250 };
251 let handle = Handle::File(fd_num);
252 interp_ok(handle.to_scalar(this))
253 }
254
255 fn DuplicateHandle(
256 &mut self,
257 src_proc: &OpTy<'tcx>, src_handle: &OpTy<'tcx>, target_proc: &OpTy<'tcx>, target_handle: &OpTy<'tcx>, desired_access: &OpTy<'tcx>, inherit: &OpTy<'tcx>, options: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
265 let this = self.eval_context_mut();
267
268 let src_proc = this.read_handle(src_proc, "DuplicateHandle")?;
269 let src_handle = this.read_handle(src_handle, "DuplicateHandle")?;
270 let target_proc = this.read_handle(target_proc, "DuplicateHandle")?;
271 let target_handle_ptr = this.read_pointer(target_handle)?;
272 let _ = this.read_scalar(desired_access)?.to_u32()?;
274 let _ = this.read_scalar(inherit)?;
277 let options = this.read_scalar(options)?;
278
279 if src_proc != Handle::Pseudo(PseudoHandle::CurrentProcess) {
280 throw_unsup_format!(
281 "`DuplicateHandle` `hSourceProcessHandle` parameter is not the current process, which is unsupported"
282 );
283 }
284
285 if target_proc != Handle::Pseudo(PseudoHandle::CurrentProcess) {
286 throw_unsup_format!(
287 "`DuplicateHandle` `hSourceProcessHandle` parameter is not the current process, which is unsupported"
288 );
289 }
290
291 if this.ptr_is_null(target_handle_ptr)? {
292 throw_unsup_format!(
293 "`DuplicateHandle` `lpTargetHandle` parameter is null, which is unsupported"
294 );
295 }
296
297 if options != this.eval_windows("c", "DUPLICATE_SAME_ACCESS") {
298 throw_unsup_format!(
299 "`DuplicateHandle` `dwOptions` parameter is not `DUPLICATE_SAME_ACCESS`, which is unsupported"
300 );
301 }
302
303 let new_handle = match src_handle {
304 Handle::File(old_fd_num) => {
305 let Some(fd) = this.machine.fds.get(old_fd_num) else {
306 this.invalid_handle("DuplicateHandle")?
307 };
308 Handle::File(this.machine.fds.insert(fd))
309 }
310 Handle::Thread(_) => {
311 throw_unsup_format!(
312 "`DuplicateHandle` called on a thread handle, which is unsupported"
313 );
314 }
315 Handle::Pseudo(pseudo) => Handle::Pseudo(pseudo),
316 Handle::Null | Handle::Invalid => this.invalid_handle("DuplicateHandle")?,
317 };
318
319 let target_place = this.deref_pointer_as(target_handle, this.machine.layouts.usize)?;
320 this.write_scalar(new_handle.to_scalar(this), &target_place)?;
321
322 interp_ok(this.eval_windows("c", "TRUE"))
323 }
324
325 fn CloseHandle(&mut self, handle_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
326 let this = self.eval_context_mut();
327
328 let handle = this.read_handle(handle_op, "CloseHandle")?;
329 let ret = match handle {
330 Handle::Thread(thread) => {
331 this.detach_thread(thread, true)?;
332 this.eval_windows("c", "TRUE")
333 }
334 Handle::File(fd_num) =>
335 if let Some(fd) = this.machine.fds.remove(fd_num) {
336 let err = fd.close_ref(this.machine.communicate(), this)?;
337 if let Err(e) = err {
338 this.set_last_error(e)?;
339 this.eval_windows("c", "FALSE")
340 } else {
341 this.eval_windows("c", "TRUE")
342 }
343 } else {
344 this.invalid_handle("CloseHandle")?
345 },
346 _ => this.invalid_handle("CloseHandle")?,
347 };
348
349 interp_ok(ret)
350 }
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356
357 #[test]
358 fn test_invalid_encoding() {
359 assert_eq!(Handle::Invalid.to_packed(), u32::MAX)
361 }
362}