Skip to main content

windows_thread_ambient_sys/
error_mode.rs

1// Copyright (c) Mike Grier.
2
3//! The thread error mode aspect.
4//!
5//! The thread error mode decides whether a hard device error -- the classic
6//! absent-removable-drive case -- raises a modal dialog or fails the call. A
7//! thread-pool worker's mode is `0`, meaning the critical-error handler is
8//! enabled, so a blocking call remoted onto shared infrastructure can put a
9//! dialog on a thread the whole process depends on.
10//!
11//! # This aspect is both capturable and declarable, deliberately
12//!
13//! Unlike the others it appears in both halves of the crate's decomposition. It
14//! is readable, so a caller may capture the submitting thread's value and
15//! transplant it; and it is the aspect consumers most often want to *override*
16//! with a policy of their own. Offering only one of those would bake one
17//! consumer's answer into a platform layer: a consumer running on shared threads
18//! will force the dialog-suppressing bits, while a consumer owning a private
19//! thread, where a modal dialog is its own problem and nobody else's, is
20//! entitled to the opposite choice.
21//!
22//! # Why the alignment bit is not representable
23//!
24//! [`ThreadErrorMode`] can hold only the three bits `SetThreadErrorMode`
25//! accepts. `SEM_NOALIGNMENTFAULTEXCEPT` is excluded because it is *rejected*
26//! per-thread -- and, measured, an invalid bit fails the **whole** call rather
27//! than being dropped from it. A type that could represent it would let a caller
28//! combine it with valid bits and silently lose the entire change, so this is a
29//! case for a type that cannot express the invalid state rather than a runtime
30//! check nobody expected to fail. See
31//! [`windows-platform-probes`](../../windows-platform-probes/DESIGN-NOTES.md),
32//! which pins the measurement as a test.
33//!
34//! # Example
35//!
36//! ```
37//! use windows_thread_ambient_sys::ThreadErrorMode;
38//!
39//! let entry = ThreadErrorMode::capture()?;
40//!
41//! let mode = ThreadErrorMode::FAIL_CRITICAL_ERRORS
42//!     .union(ThreadErrorMode::NO_OPEN_FILE_ERROR_BOX);
43//! let guard = mode.apply()?;
44//! assert!(ThreadErrorMode::capture()?.contains(ThreadErrorMode::FAIL_CRITICAL_ERRORS));
45//!
46//! // Release explicitly. Dropping the guard also restores, but discards any
47//! // failure to do so, because a destructor has no caller to report to.
48//! guard.release()?;
49//! assert_eq!(ThreadErrorMode::capture()?, entry);
50//! # Ok::<(), Box<dyn std::error::Error>>(())
51//! ```
52
53use std::fmt;
54use std::io;
55
56use windows_sys::Win32::System::Diagnostics::Debug::{
57    GetThreadErrorMode, SEM_FAILCRITICALERRORS, SEM_NOGPFAULTERRORBOX, SEM_NOOPENFILEERRORBOX,
58    SetThreadErrorMode, THREAD_ERROR_MODE,
59};
60
61/// Every bit this crate will place in a [`ThreadErrorMode`].
62const SUPPORTED: THREAD_ERROR_MODE =
63    SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX;
64
65/// A thread error mode, restricted to the bits Windows accepts per thread.
66///
67/// Construct one from the associated constants and [`union`](Self::union), or
68/// from a raw value with [`from_bits`](Self::from_bits).
69#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
70pub struct ThreadErrorMode(THREAD_ERROR_MODE);
71
72impl ThreadErrorMode {
73    /// No bits set: hard errors raise the system's dialogs.
74    pub const NONE: Self = Self(0);
75
76    /// Fail calls that hit a critical error instead of raising a dialog.
77    pub const FAIL_CRITICAL_ERRORS: Self = Self(SEM_FAILCRITICALERRORS);
78
79    /// Suppress the general-protection-fault error box.
80    pub const NO_GP_FAULT_ERROR_BOX: Self = Self(SEM_NOGPFAULTERRORBOX);
81
82    /// Suppress the file-open error box.
83    pub const NO_OPEN_FILE_ERROR_BOX: Self = Self(SEM_NOOPENFILEERRORBOX);
84
85    /// The raw value, as Win32 represents it.
86    #[must_use]
87    pub const fn bits(self) -> THREAD_ERROR_MODE {
88        self.0
89    }
90
91    /// Build a mode from a raw value.
92    ///
93    /// # Errors
94    ///
95    /// Returns [`UnsupportedBits`] if `bits` contains anything
96    /// `SetThreadErrorMode` does not accept. This is a rejection rather than a
97    /// mask: silently dropping a bit would report installing a value that was
98    /// not installed, which is the failure this type exists to prevent.
99    ///
100    /// # Example
101    ///
102    /// ```
103    /// use windows_thread_ambient_sys::ThreadErrorMode;
104    ///
105    /// assert_eq!(
106    ///     ThreadErrorMode::from_bits(0x0001),
107    ///     Ok(ThreadErrorMode::FAIL_CRITICAL_ERRORS)
108    /// );
109    ///
110    /// // 0x0004 is SEM_NOALIGNMENTFAULTEXCEPT, which cannot be set per thread.
111    /// // It is refused even beside a valid bit, because Windows would install
112    /// // neither: an invalid bit fails the whole call.
113    /// let refused = ThreadErrorMode::from_bits(0x0001 | 0x0004)
114    ///     .expect_err("the alignment bit is not settable per thread");
115    /// assert_eq!(refused.bits(), 0x0004);
116    /// ```
117    pub const fn from_bits(bits: THREAD_ERROR_MODE) -> Result<Self, UnsupportedBits> {
118        let unsupported = bits & !SUPPORTED;
119        if unsupported == 0 {
120            Ok(Self(bits))
121        } else {
122            Err(UnsupportedBits { bits: unsupported })
123        }
124    }
125
126    /// Both modes' bits.
127    #[must_use]
128    pub const fn union(self, other: Self) -> Self {
129        Self(self.0 | other.0)
130    }
131
132    /// Whether every bit of `other` is set.
133    #[must_use]
134    pub const fn contains(self, other: Self) -> bool {
135        self.0 & other.0 == other.0
136    }
137
138    /// Whether no bits are set.
139    #[must_use]
140    pub const fn is_empty(self) -> bool {
141        self.0 == 0
142    }
143
144    /// Read the calling thread's current mode.
145    ///
146    /// # Errors
147    ///
148    /// Returns [`UnsupportedBits`] if Windows reports a bit this type cannot
149    /// hold. Measured, that does not happen: the thread error mode is
150    /// independent storage rather than a view of the process error mode, so a
151    /// process-scope bit such as `SEM_NOALIGNMENTFAULTEXCEPT` does not show
152    /// through here. The result is still surfaced rather than assumed away,
153    /// because a type unable to represent a state the platform can produce would
154    /// be a bug, and this is where that would first be observable.
155    pub fn capture() -> Result<Self, UnsupportedBits> {
156        // SAFETY: the call takes no arguments and has no preconditions.
157        Self::from_bits(unsafe { GetThreadErrorMode() })
158    }
159
160    /// Install this mode on the calling thread until the guard is released.
161    ///
162    /// # Errors
163    ///
164    /// Returns [`ApplyError`] if Windows refuses the value, in which case
165    /// nothing was installed and the thread is untouched.
166    pub fn apply(self) -> Result<ErrorModeGuard, ApplyError> {
167        let mut previous: THREAD_ERROR_MODE = 0;
168        // SAFETY: `previous` is a valid writable destination, and `self.0`
169        // cannot contain a bit the call rejects.
170        let ok = unsafe { SetThreadErrorMode(self.0, &mut previous) };
171        if ok == 0 {
172            return Err(ApplyError {
173                requested: self,
174                source: io::Error::last_os_error(),
175            });
176        }
177        Ok(ErrorModeGuard {
178            previous,
179            released: false,
180        })
181    }
182}
183
184impl fmt::Display for ThreadErrorMode {
185    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186        write!(f, "0x{:04X}", self.0)
187    }
188}
189
190/// A raw value contained bits the per-thread error mode does not accept.
191#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
192pub struct UnsupportedBits {
193    bits: THREAD_ERROR_MODE,
194}
195
196impl UnsupportedBits {
197    /// Just the offending bits.
198    #[must_use]
199    pub const fn bits(self) -> THREAD_ERROR_MODE {
200        self.bits
201    }
202}
203
204impl fmt::Display for UnsupportedBits {
205    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206        write!(
207            f,
208            "0x{:04X} cannot be set on a thread; SetThreadErrorMode rejects it \
209             and would install none of the accompanying bits either",
210            self.bits
211        )
212    }
213}
214
215impl std::error::Error for UnsupportedBits {}
216
217/// Windows refused to install a thread error mode.
218#[derive(Debug)]
219pub struct ApplyError {
220    requested: ThreadErrorMode,
221    source: io::Error,
222}
223
224impl ApplyError {
225    /// The mode that could not be installed.
226    #[must_use]
227    pub const fn requested(&self) -> ThreadErrorMode {
228        self.requested
229    }
230
231    /// The underlying Win32 code, if there was one.
232    #[must_use]
233    pub fn raw_os_error(&self) -> Option<i32> {
234        self.source.raw_os_error()
235    }
236}
237
238impl fmt::Display for ApplyError {
239    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240        write!(
241            f,
242            "could not install thread error mode {}: {}",
243            self.requested, self.source
244        )
245    }
246}
247
248impl std::error::Error for ApplyError {
249    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
250        Some(&self.source)
251    }
252}
253
254/// Windows refused to restore the thread's entry error mode.
255#[derive(Debug)]
256pub struct RestoreError {
257    unrestored: THREAD_ERROR_MODE,
258    source: io::Error,
259}
260
261impl RestoreError {
262    /// The value the thread should have been returned to.
263    #[must_use]
264    pub const fn unrestored_bits(&self) -> THREAD_ERROR_MODE {
265        self.unrestored
266    }
267
268    /// The underlying Win32 code, if there was one.
269    #[must_use]
270    pub fn raw_os_error(&self) -> Option<i32> {
271        self.source.raw_os_error()
272    }
273}
274
275impl fmt::Display for RestoreError {
276    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277        write!(
278            f,
279            "could not restore thread error mode 0x{:04X}; the thread is left \
280             contaminated: {}",
281            self.unrestored, self.source
282        )
283    }
284}
285
286impl std::error::Error for RestoreError {
287    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
288        Some(&self.source)
289    }
290}
291
292/// Holds an installed error mode until it is released.
293///
294/// # Release explicitly on the ordinary path
295///
296/// [`release`](Self::release) reports whether the thread was actually restored.
297/// Dropping the guard instead restores on a best-effort basis and **discards**
298/// any failure, because a destructor has no caller to report to. That is the
299/// right behaviour while unwinding, where no report could be delivered anyway,
300/// and the wrong behaviour on the ordinary path -- so the ordinary path calls
301/// `release`.
302///
303/// Restoration failure is not fatal here. Contrast impersonation, whose restore
304/// failure is fail-fast because returning a shared worker under an unknown
305/// identity is a process-wide security failure; leaving a thread with the wrong
306/// error mode is a real contamination but not that, and imposing the strictest
307/// aspect's semantics on every aspect is precisely what this crate's composite
308/// exists to avoid.
309#[must_use = "dropping the guard restores the error mode but discards any failure to do so"]
310#[derive(Debug)]
311pub struct ErrorModeGuard {
312    previous: THREAD_ERROR_MODE,
313    released: bool,
314}
315
316impl ErrorModeGuard {
317    /// The mode this thread had before the guard was installed.
318    ///
319    /// # Errors
320    ///
321    /// Returns [`UnsupportedBits`] in the same unreachable-by-measurement case
322    /// as [`ThreadErrorMode::capture`]. The guard itself keeps the raw value, so
323    /// restoration round-trips exactly whatever Windows reported, whether or not
324    /// this crate's type can name it.
325    pub const fn previous(&self) -> Result<ThreadErrorMode, UnsupportedBits> {
326        ThreadErrorMode::from_bits(self.previous)
327    }
328
329    /// Restore the thread's entry mode, reporting whether it worked.
330    ///
331    /// # Errors
332    ///
333    /// Returns [`RestoreError`] if Windows refused, leaving the thread
334    /// contaminated with whatever was installed.
335    pub fn release(mut self) -> Result<(), RestoreError> {
336        self.released = true;
337        Self::restore(self.previous)
338    }
339
340    fn restore(previous: THREAD_ERROR_MODE) -> Result<(), RestoreError> {
341        let mut ignored: THREAD_ERROR_MODE = 0;
342        // SAFETY: `ignored` is a valid writable destination, and `previous` is a
343        // value Windows itself reported for this thread.
344        let ok = unsafe { SetThreadErrorMode(previous, &mut ignored) };
345        if ok == 0 {
346            return Err(RestoreError {
347                unrestored: previous,
348                source: io::Error::last_os_error(),
349            });
350        }
351        Ok(())
352    }
353}
354
355impl Drop for ErrorModeGuard {
356    fn drop(&mut self) {
357        if !self.released {
358            // Best effort by design: see the type's documentation.
359            let _ = Self::restore(self.previous);
360        }
361    }
362}
363
364#[cfg(test)]
365mod tests;