miri/shims/windows/
handle.rs

1use std::mem::variant_count;
2
3use rustc_abi::HasDataLayout;
4
5use crate::concurrency::thread::ThreadNotFound;
6use crate::*;
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
9pub enum PseudoHandle {
10    CurrentThread,
11}
12
13/// Miri representation of a Windows `HANDLE`
14#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
15pub enum Handle {
16    Null,
17    Pseudo(PseudoHandle),
18    Thread(ThreadId),
19}
20
21impl PseudoHandle {
22    const CURRENT_THREAD_VALUE: u32 = 0;
23
24    fn value(self) -> u32 {
25        match self {
26            Self::CurrentThread => Self::CURRENT_THREAD_VALUE,
27        }
28    }
29
30    fn from_value(value: u32) -> Option<Self> {
31        match value {
32            Self::CURRENT_THREAD_VALUE => Some(Self::CurrentThread),
33            _ => None,
34        }
35    }
36}
37
38/// Errors that can occur when constructing a [`Handle`] from a Scalar.
39pub enum HandleError {
40    /// There is no thread with the given ID.
41    ThreadNotFound(ThreadNotFound),
42    /// Can't convert scalar to handle because it is structurally invalid.
43    InvalidHandle,
44}
45
46impl Handle {
47    const NULL_DISCRIMINANT: u32 = 0;
48    const PSEUDO_DISCRIMINANT: u32 = 1;
49    const THREAD_DISCRIMINANT: u32 = 2;
50
51    fn discriminant(self) -> u32 {
52        match self {
53            Self::Null => Self::NULL_DISCRIMINANT,
54            Self::Pseudo(_) => Self::PSEUDO_DISCRIMINANT,
55            Self::Thread(_) => Self::THREAD_DISCRIMINANT,
56        }
57    }
58
59    fn data(self) -> u32 {
60        match self {
61            Self::Null => 0,
62            Self::Pseudo(pseudo_handle) => pseudo_handle.value(),
63            Self::Thread(thread) => thread.to_u32(),
64        }
65    }
66
67    fn packed_disc_size() -> u32 {
68        // ceil(log2(x)) is how many bits it takes to store x numbers
69        let variant_count = variant_count::<Self>();
70
71        // however, std's ilog2 is floor(log2(x))
72        let floor_log2 = variant_count.ilog2();
73
74        // we need to add one for non powers of two to compensate for the difference
75        #[expect(clippy::arithmetic_side_effects)] // cannot overflow
76        if variant_count.is_power_of_two() { floor_log2 } else { floor_log2 + 1 }
77    }
78
79    /// Converts a handle into its machine representation.
80    ///
81    /// The upper [`Self::packed_disc_size()`] bits are used to store a discriminant corresponding to the handle variant.
82    /// The remaining bits are used for the variant's field.
83    ///
84    /// None of this layout is guaranteed to applications by Windows or Miri.
85    fn to_packed(self) -> u32 {
86        let disc_size = Self::packed_disc_size();
87        let data_size = u32::BITS.strict_sub(disc_size);
88
89        let discriminant = self.discriminant();
90        let data = self.data();
91
92        // make sure the discriminant fits into `disc_size` bits
93        assert!(discriminant < 2u32.pow(disc_size));
94
95        // make sure the data fits into `data_size` bits
96        assert!(data < 2u32.pow(data_size));
97
98        // packs the data into the lower `data_size` bits
99        // and packs the discriminant right above the data
100        (discriminant << data_size) | data
101    }
102
103    fn new(discriminant: u32, data: u32) -> Option<Self> {
104        match discriminant {
105            Self::NULL_DISCRIMINANT if data == 0 => Some(Self::Null),
106            Self::PSEUDO_DISCRIMINANT => Some(Self::Pseudo(PseudoHandle::from_value(data)?)),
107            Self::THREAD_DISCRIMINANT => Some(Self::Thread(ThreadId::new_unchecked(data))),
108            _ => None,
109        }
110    }
111
112    /// see docs for `to_packed`
113    fn from_packed(handle: u32) -> Option<Self> {
114        let disc_size = Self::packed_disc_size();
115        let data_size = u32::BITS.strict_sub(disc_size);
116
117        // the lower `data_size` bits of this mask are 1
118        #[expect(clippy::arithmetic_side_effects)] // cannot overflow
119        let data_mask = 2u32.pow(data_size) - 1;
120
121        // the discriminant is stored right above the lower `data_size` bits
122        let discriminant = handle >> data_size;
123
124        // the data is stored in the lower `data_size` bits
125        let data = handle & data_mask;
126
127        Self::new(discriminant, data)
128    }
129
130    pub fn to_scalar(self, cx: &impl HasDataLayout) -> Scalar {
131        // 64-bit handles are sign extended 32-bit handles
132        // see https://docs.microsoft.com/en-us/windows/win32/winprog64/interprocess-communication
133        #[expect(clippy::cast_possible_wrap)] // we want it to wrap
134        let signed_handle = self.to_packed() as i32;
135        Scalar::from_target_isize(signed_handle.into(), cx)
136    }
137
138    /// Convert a scalar into a structured `Handle`.
139    /// Structurally invalid handles return [`HandleError::InvalidHandle`].
140    /// If the handle is structurally valid but semantically invalid, e.g. a for non-existent thread
141    /// ID, returns [`HandleError::ThreadNotFound`].
142    pub fn try_from_scalar<'tcx>(
143        handle: Scalar,
144        cx: &MiriInterpCx<'tcx>,
145    ) -> InterpResult<'tcx, Result<Self, HandleError>> {
146        let sign_extended_handle = handle.to_target_isize(cx)?;
147
148        #[expect(clippy::cast_sign_loss)] // we want to lose the sign
149        let handle = if let Ok(signed_handle) = i32::try_from(sign_extended_handle) {
150            signed_handle as u32
151        } else {
152            // if a handle doesn't fit in an i32, it isn't valid.
153            return interp_ok(Err(HandleError::InvalidHandle));
154        };
155
156        match Self::from_packed(handle) {
157            Some(Self::Thread(thread)) => {
158                // validate the thread id
159                match cx.machine.threads.thread_id_try_from(thread.to_u32()) {
160                    Ok(id) => interp_ok(Ok(Self::Thread(id))),
161                    Err(e) => interp_ok(Err(HandleError::ThreadNotFound(e))),
162                }
163            }
164            Some(handle) => interp_ok(Ok(handle)),
165            None => interp_ok(Err(HandleError::InvalidHandle)),
166        }
167    }
168}
169
170impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
171
172#[allow(non_snake_case)]
173pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
174    fn invalid_handle(&mut self, function_name: &str) -> InterpResult<'tcx, !> {
175        throw_machine_stop!(TerminationInfo::Abort(format!(
176            "invalid handle passed to `{function_name}`"
177        )))
178    }
179
180    fn CloseHandle(&mut self, handle_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
181        let this = self.eval_context_mut();
182
183        let handle = this.read_scalar(handle_op)?;
184        let ret = match Handle::try_from_scalar(handle, this)? {
185            Ok(Handle::Thread(thread)) => {
186                this.detach_thread(thread, /*allow_terminated_joined*/ true)?;
187                this.eval_windows("c", "TRUE")
188            }
189            _ => this.invalid_handle("CloseHandle")?,
190        };
191
192        interp_ok(ret)
193    }
194}