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>(
170 handle: Scalar,
171 cx: &MiriInterpCx<'tcx>,
172 ) -> InterpResult<'tcx, Result<Self, HandleError>> {
173 let sign_extended_handle = handle.to_target_isize(cx)?;
174
175 let handle = if let Ok(signed_handle) = i32::try_from(sign_extended_handle) {
176 signed_handle.cast_unsigned()
177 } else {
178 return interp_ok(Err(HandleError::InvalidHandle));
180 };
181
182 match Self::from_packed(handle) {
183 Some(Self::Thread(thread)) => {
184 match cx.machine.threads.thread_id_try_from(thread.to_u32()) {
186 Ok(id) => interp_ok(Ok(Self::Thread(id))),
187 Err(e) => interp_ok(Err(HandleError::ThreadNotFound(e))),
188 }
189 }
190 Some(handle) => interp_ok(Ok(handle)),
191 None => interp_ok(Err(HandleError::InvalidHandle)),
192 }
193 }
194}
195
196impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
197
198#[allow(non_snake_case)]
199pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
200 #[track_caller]
203 fn read_handle(&self, handle: &OpTy<'tcx>, function_name: &str) -> InterpResult<'tcx, Handle> {
204 let this = self.eval_context_ref();
205 let handle = this.read_scalar(handle)?;
206 match Handle::try_from_scalar(handle, this)? {
207 Ok(handle) => interp_ok(handle),
208 Err(HandleError::InvalidHandle) =>
209 throw_machine_stop!(TerminationInfo::Abort(format!(
210 "invalid handle {} passed to {function_name}",
211 handle.to_target_isize(this)?,
212 ))),
213 Err(HandleError::ThreadNotFound(_)) =>
214 throw_machine_stop!(TerminationInfo::Abort(format!(
215 "invalid thread ID {} passed to {function_name}",
216 handle.to_target_isize(this)?,
217 ))),
218 }
219 }
220
221 fn invalid_handle(&mut self, function_name: &str) -> InterpResult<'tcx, !> {
222 throw_machine_stop!(TerminationInfo::Abort(format!(
223 "invalid handle passed to `{function_name}`"
224 )))
225 }
226
227 fn GetStdHandle(&mut self, which: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
228 let this = self.eval_context_mut();
229 let which = this.read_scalar(which)?.to_i32()?;
230
231 let stdin = this.eval_windows("c", "STD_INPUT_HANDLE").to_i32()?;
232 let stdout = this.eval_windows("c", "STD_OUTPUT_HANDLE").to_i32()?;
233 let stderr = this.eval_windows("c", "STD_ERROR_HANDLE").to_i32()?;
234
235 let fd_num = if which == stdin {
239 0
240 } else if which == stdout {
241 1
242 } else if which == stderr {
243 2
244 } else {
245 throw_unsup_format!("Invalid argument to `GetStdHandle`: {which}")
246 };
247 let handle = Handle::File(fd_num);
248 interp_ok(handle.to_scalar(this))
249 }
250
251 fn DuplicateHandle(
252 &mut self,
253 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> {
261 let this = self.eval_context_mut();
263
264 let src_proc = this.read_handle(src_proc, "DuplicateHandle")?;
265 let src_handle = this.read_handle(src_handle, "DuplicateHandle")?;
266 let target_proc = this.read_handle(target_proc, "DuplicateHandle")?;
267 let target_handle_ptr = this.read_pointer(target_handle)?;
268 let _ = this.read_scalar(desired_access)?.to_u32()?;
270 let _ = this.read_scalar(inherit)?;
273 let options = this.read_scalar(options)?;
274
275 if src_proc != Handle::Pseudo(PseudoHandle::CurrentProcess) {
276 throw_unsup_format!(
277 "`DuplicateHandle` `hSourceProcessHandle` parameter is not the current process, which is unsupported"
278 );
279 }
280
281 if target_proc != Handle::Pseudo(PseudoHandle::CurrentProcess) {
282 throw_unsup_format!(
283 "`DuplicateHandle` `hSourceProcessHandle` parameter is not the current process, which is unsupported"
284 );
285 }
286
287 if this.ptr_is_null(target_handle_ptr)? {
288 throw_unsup_format!(
289 "`DuplicateHandle` `lpTargetHandle` parameter is null, which is unsupported"
290 );
291 }
292
293 if options != this.eval_windows("c", "DUPLICATE_SAME_ACCESS") {
294 throw_unsup_format!(
295 "`DuplicateHandle` `dwOptions` parameter is not `DUPLICATE_SAME_ACCESS`, which is unsupported"
296 );
297 }
298
299 let new_handle = match src_handle {
300 Handle::File(old_fd_num) => {
301 let Some(fd) = this.machine.fds.get(old_fd_num) else {
302 this.invalid_handle("DuplicateHandle")?
303 };
304 Handle::File(this.machine.fds.insert(fd))
305 }
306 Handle::Thread(_) => {
307 throw_unsup_format!(
308 "`DuplicateHandle` called on a thread handle, which is unsupported"
309 );
310 }
311 Handle::Pseudo(pseudo) => Handle::Pseudo(pseudo),
312 Handle::Null | Handle::Invalid => this.invalid_handle("DuplicateHandle")?,
313 };
314
315 let target_place = this.deref_pointer_as(target_handle, this.machine.layouts.usize)?;
316 this.write_scalar(new_handle.to_scalar(this), &target_place)?;
317
318 interp_ok(this.eval_windows("c", "TRUE"))
319 }
320
321 fn CloseHandle(&mut self, handle_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
322 let this = self.eval_context_mut();
323
324 let handle = this.read_handle(handle_op, "CloseHandle")?;
325 let ret = match handle {
326 Handle::Thread(thread) => {
327 this.detach_thread(thread, true)?;
328 this.eval_windows("c", "TRUE")
329 }
330 Handle::File(fd_num) =>
331 if let Some(fd) = this.machine.fds.remove(fd_num) {
332 let err = fd.close_ref(this.machine.communicate(), this)?;
333 if let Err(e) = err {
334 this.set_last_error(e)?;
335 this.eval_windows("c", "FALSE")
336 } else {
337 this.eval_windows("c", "TRUE")
338 }
339 } else {
340 this.invalid_handle("CloseHandle")?
341 },
342 _ => this.invalid_handle("CloseHandle")?,
343 };
344
345 interp_ok(ret)
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352
353 #[test]
354 fn test_invalid_encoding() {
355 assert_eq!(Handle::Invalid.to_packed(), u32::MAX)
357 }
358}