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