1 | use super::*; |
2 | use core::num::NonZeroI32; |
3 | |
4 | #[allow (unused_imports)] |
5 | use core::mem::size_of; |
6 | |
7 | /// An error object consists of both an error code and optional detailed error information for debugging. |
8 | /// |
9 | /// # Extended error info and the `windows_slim_errors` configuration option |
10 | /// |
11 | /// `Error` contains an [`HRESULT`] value that describes the error, as well as an optional |
12 | /// `IErrorInfo` COM object. The `IErrorInfo` object is a COM object that can provide detailed information |
13 | /// about an error, such as a text string, a `ProgID` of the originator, etc. If the error object |
14 | /// was originated in an WinRT component, then additional information such as a stack track may be |
15 | /// captured. |
16 | /// |
17 | /// However, many systems based on COM do not use `IErrorInfo`. For these systems, the optional error |
18 | /// info within `Error` has no benefits, but has substantial costs because it increases the size of |
19 | /// the `Error` object, which also increases the size of `Result<T>`. |
20 | /// |
21 | /// This error information can be disabled at compile time by setting `RUSTFLAGS=--cfg=windows_slim_errors`. |
22 | /// This removes the `IErrorInfo` support within the [`Error`] type, which has these benefits: |
23 | /// |
24 | /// * It reduces the size of [`Error`] to 4 bytes (the size of [`HRESULT`]). |
25 | /// |
26 | /// * It reduces the size of `Result<(), Error>` to 4 bytes, allowing it to be returned in a single |
27 | /// machine register. |
28 | /// |
29 | /// * The `Error` (and `Result<T, Error>`) types no longer have a [`Drop`] impl. This removes the need |
30 | /// for lifetime checking and running drop code when [`Error`] and [`Result`] go out of scope. This |
31 | /// significantly reduces code size for codebase that make extensive use of [`Error`]. |
32 | /// |
33 | /// Of course, these benefits come with a cost; you lose extended error information for those |
34 | /// COM objects that support it. |
35 | /// |
36 | /// This is controlled by a `--cfg` option rather than a Cargo feature because this compilation |
37 | /// option sets a policy that applies to an entire graph of crates. Individual crates that take a |
38 | /// dependency on the `windows-result` crate are not in a good position to decide whether they want |
39 | /// slim errors or full errors. Cargo features are meant to be additive, but specifying the size |
40 | /// and contents of `Error` is not a feature so much as a whole-program policy decision. |
41 | /// |
42 | /// # References |
43 | /// |
44 | /// * [`IErrorInfo`](https://learn.microsoft.com/en-us/windows/win32/api/oaidl/nn-oaidl-ierrorinfo) |
45 | #[derive (Clone)] |
46 | pub struct Error { |
47 | /// The `HRESULT` error code, but represented using [`NonZeroI32`]. [`NonZeroI32`] provides |
48 | /// a "niche" to the Rust compiler, which is a space-saving optimization. This allows the |
49 | /// compiler to use more compact representation for enum variants (such as [`Result`]) that |
50 | /// contain instances of [`Error`]. |
51 | code: NonZeroI32, |
52 | |
53 | /// Contains details about the error, such as error text. |
54 | info: ErrorInfo, |
55 | } |
56 | |
57 | /// We remap S_OK to this error because the S_OK representation (zero) is reserved for niche |
58 | /// optimizations. |
59 | const S_EMPTY_ERROR: NonZeroI32 = const_nonzero_i32(u32::from_be_bytes(*b"S_OK" ) as i32); |
60 | |
61 | /// Converts an HRESULT into a NonZeroI32. If the input is S_OK (zero), then this is converted to |
62 | /// S_EMPTY_ERROR. This is necessary because NonZeroI32, as the name implies, cannot represent the |
63 | /// value zero. So we remap it to a value no one should be using, during storage. |
64 | const fn const_nonzero_i32(i: i32) -> NonZeroI32 { |
65 | if let Some(nz: NonZero) = NonZeroI32::new(i) { |
66 | nz |
67 | } else { |
68 | panic!(); |
69 | } |
70 | } |
71 | |
72 | fn nonzero_hresult(hr: HRESULT) -> NonZeroI32 { |
73 | if let Some(nz: NonZero) = NonZeroI32::new(hr.0) { |
74 | nz |
75 | } else { |
76 | S_EMPTY_ERROR |
77 | } |
78 | } |
79 | |
80 | impl Error { |
81 | /// Creates an error object without any failure information. |
82 | pub const fn empty() -> Self { |
83 | Self { |
84 | code: S_EMPTY_ERROR, |
85 | info: ErrorInfo::empty(), |
86 | } |
87 | } |
88 | |
89 | /// Creates a new error object, capturing the stack and other information about the |
90 | /// point of failure. |
91 | pub fn new<T: AsRef<str>>(code: HRESULT, message: T) -> Self { |
92 | #[cfg (windows)] |
93 | { |
94 | let message: &str = message.as_ref(); |
95 | if message.is_empty() { |
96 | Self::from_hresult(code) |
97 | } else { |
98 | ErrorInfo::originate_error(code, message); |
99 | code.into() |
100 | } |
101 | } |
102 | #[cfg (not(windows))] |
103 | { |
104 | let _ = message; |
105 | Self::from_hresult(code) |
106 | } |
107 | } |
108 | |
109 | /// Creates a new error object with an error code, but without additional error information. |
110 | pub fn from_hresult(code: HRESULT) -> Self { |
111 | Self { |
112 | code: nonzero_hresult(code), |
113 | info: ErrorInfo::empty(), |
114 | } |
115 | } |
116 | |
117 | /// Creates a new `Error` from the Win32 error code returned by `GetLastError()`. |
118 | pub fn from_win32() -> Self { |
119 | #[cfg (windows)] |
120 | { |
121 | let error = unsafe { GetLastError() }; |
122 | Self::from_hresult(HRESULT::from_win32(error)) |
123 | } |
124 | #[cfg (not(windows))] |
125 | { |
126 | unimplemented!() |
127 | } |
128 | } |
129 | |
130 | /// The error code describing the error. |
131 | pub const fn code(&self) -> HRESULT { |
132 | if self.code.get() == S_EMPTY_ERROR.get() { |
133 | HRESULT(0) |
134 | } else { |
135 | HRESULT(self.code.get()) |
136 | } |
137 | } |
138 | |
139 | /// The error message describing the error. |
140 | pub fn message(&self) -> String { |
141 | if let Some(message) = self.info.message() { |
142 | return message; |
143 | } |
144 | |
145 | // Otherwise fallback to a generic error code description. |
146 | self.code().message() |
147 | } |
148 | |
149 | /// The error object describing the error. |
150 | #[cfg (windows)] |
151 | pub fn as_ptr(&self) -> *mut core::ffi::c_void { |
152 | self.info.as_ptr() |
153 | } |
154 | } |
155 | |
156 | #[cfg (feature = "std" )] |
157 | impl std::error::Error for Error {} |
158 | |
159 | impl From<Error> for HRESULT { |
160 | fn from(error: Error) -> Self { |
161 | let code: i32 = error.code(); |
162 | error.info.into_thread(); |
163 | code |
164 | } |
165 | } |
166 | |
167 | impl From<HRESULT> for Error { |
168 | fn from(code: HRESULT) -> Self { |
169 | Self { |
170 | code: nonzero_hresult(hr:code), |
171 | info: ErrorInfo::from_thread(), |
172 | } |
173 | } |
174 | } |
175 | |
176 | #[cfg (feature = "std" )] |
177 | impl From<Error> for std::io::Error { |
178 | fn from(from: Error) -> Self { |
179 | Self::from_raw_os_error(code:from.code().0) |
180 | } |
181 | } |
182 | |
183 | #[cfg (feature = "std" )] |
184 | impl From<std::io::Error> for Error { |
185 | fn from(from: std::io::Error) -> Self { |
186 | match from.raw_os_error() { |
187 | Some(status: i32) => HRESULT::from_win32(status as u32).into(), |
188 | None => HRESULT(E_UNEXPECTED).into(), |
189 | } |
190 | } |
191 | } |
192 | |
193 | impl From<alloc::string::FromUtf16Error> for Error { |
194 | fn from(_: alloc::string::FromUtf16Error) -> Self { |
195 | Self::from_hresult(code:HRESULT::from_win32(ERROR_NO_UNICODE_TRANSLATION)) |
196 | } |
197 | } |
198 | |
199 | impl From<alloc::string::FromUtf8Error> for Error { |
200 | fn from(_: alloc::string::FromUtf8Error) -> Self { |
201 | Self::from_hresult(code:HRESULT::from_win32(ERROR_NO_UNICODE_TRANSLATION)) |
202 | } |
203 | } |
204 | |
205 | impl From<core::num::TryFromIntError> for Error { |
206 | fn from(_: core::num::TryFromIntError) -> Self { |
207 | Self::from_hresult(code:HRESULT::from_win32(ERROR_INVALID_DATA)) |
208 | } |
209 | } |
210 | |
211 | impl core::fmt::Debug for Error { |
212 | fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
213 | let mut debug: DebugStruct<'_, '_> = fmt.debug_struct(name:"Error" ); |
214 | debug&mut DebugStruct<'_, '_> |
215 | .field("code" , &self.code()) |
216 | .field(name:"message" , &self.message()) |
217 | .finish() |
218 | } |
219 | } |
220 | |
221 | impl core::fmt::Display for Error { |
222 | fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
223 | let message: String = self.message(); |
224 | if message.is_empty() { |
225 | core::write!(fmt, " {}" , self.code()) |
226 | } else { |
227 | core::write!(fmt, " {} ( {})" , self.message(), self.code()) |
228 | } |
229 | } |
230 | } |
231 | |
232 | impl core::hash::Hash for Error { |
233 | fn hash<H: core::hash::Hasher>(&self, state: &mut H) { |
234 | self.code.hash(state); |
235 | // We do not hash the error info. |
236 | } |
237 | } |
238 | |
239 | // Equality tests only the HRESULT, not the error info (if any). |
240 | impl PartialEq for Error { |
241 | fn eq(&self, other: &Self) -> bool { |
242 | self.code == other.code |
243 | } |
244 | } |
245 | |
246 | impl Eq for Error {} |
247 | |
248 | impl PartialOrd for Error { |
249 | fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> { |
250 | Some(self.cmp(other)) |
251 | } |
252 | } |
253 | |
254 | impl Ord for Error { |
255 | fn cmp(&self, other: &Self) -> core::cmp::Ordering { |
256 | self.code.cmp(&other.code) |
257 | } |
258 | } |
259 | |
260 | use error_info::*; |
261 | |
262 | #[cfg (all(windows, not(windows_slim_errors)))] |
263 | mod error_info { |
264 | use super::*; |
265 | use crate::com::ComPtr; |
266 | |
267 | /// This type stores error detail, represented by a COM `IErrorInfo` object. |
268 | /// |
269 | /// # References |
270 | /// |
271 | /// * [`IErrorInfo`](https://learn.microsoft.com/en-us/windows/win32/api/oaidl/nn-oaidl-ierrorinfo) |
272 | #[derive (Clone, Default)] |
273 | pub(crate) struct ErrorInfo { |
274 | pub(super) ptr: Option<ComPtr>, |
275 | } |
276 | |
277 | impl ErrorInfo { |
278 | pub(crate) const fn empty() -> Self { |
279 | Self { ptr: None } |
280 | } |
281 | |
282 | pub(crate) fn from_thread() -> Self { |
283 | unsafe { |
284 | let mut ptr = core::mem::MaybeUninit::zeroed(); |
285 | crate::bindings::GetErrorInfo(0, ptr.as_mut_ptr() as *mut _); |
286 | Self { |
287 | ptr: ptr.assume_init(), |
288 | } |
289 | } |
290 | } |
291 | |
292 | pub(crate) fn into_thread(self) { |
293 | if let Some(ptr) = self.ptr { |
294 | unsafe { |
295 | crate::bindings::SetErrorInfo(0, ptr.as_raw()); |
296 | } |
297 | } |
298 | } |
299 | |
300 | pub(crate) fn originate_error(code: HRESULT, message: &str) { |
301 | let message: Vec<_> = message.encode_utf16().collect(); |
302 | unsafe { |
303 | RoOriginateErrorW(code.0, message.len() as u32, message.as_ptr()); |
304 | } |
305 | } |
306 | |
307 | pub(crate) fn message(&self) -> Option<String> { |
308 | use crate::bstr::BasicString; |
309 | |
310 | let ptr = self.ptr.as_ref()?; |
311 | |
312 | let mut message = BasicString::default(); |
313 | |
314 | // First attempt to retrieve the restricted error information. |
315 | if let Some(info) = ptr.cast(&IID_IRestrictedErrorInfo) { |
316 | let mut fallback = BasicString::default(); |
317 | let mut code = 0; |
318 | |
319 | unsafe { |
320 | com_call!( |
321 | IRestrictedErrorInfo_Vtbl, |
322 | info.GetErrorDetails( |
323 | &mut fallback as *mut _ as _, |
324 | &mut code, |
325 | &mut message as *mut _ as _, |
326 | &mut BasicString::default() as *mut _ as _ |
327 | ) |
328 | ); |
329 | } |
330 | |
331 | if message.is_empty() { |
332 | message = fallback |
333 | }; |
334 | } |
335 | |
336 | // Next attempt to retrieve the regular error information. |
337 | if message.is_empty() { |
338 | unsafe { |
339 | com_call!( |
340 | IErrorInfo_Vtbl, |
341 | ptr.GetDescription(&mut message as *mut _ as _) |
342 | ); |
343 | } |
344 | } |
345 | |
346 | Some(String::from_utf16_lossy(wide_trim_end(&message))) |
347 | } |
348 | |
349 | pub(crate) fn as_ptr(&self) -> *mut core::ffi::c_void { |
350 | if let Some(info) = self.ptr.as_ref() { |
351 | info.as_raw() |
352 | } else { |
353 | core::ptr::null_mut() |
354 | } |
355 | } |
356 | } |
357 | |
358 | unsafe impl Send for ErrorInfo {} |
359 | unsafe impl Sync for ErrorInfo {} |
360 | } |
361 | |
362 | #[cfg (not(all(windows, not(windows_slim_errors))))] |
363 | mod error_info { |
364 | use super::*; |
365 | |
366 | // We use this name so that the NatVis <Type> element for ErrorInfo does *not* match this type. |
367 | // This prevents the NatVis description from failing to load. |
368 | #[derive (Clone, Default)] |
369 | pub(crate) struct EmptyErrorInfo; |
370 | |
371 | pub(crate) use EmptyErrorInfo as ErrorInfo; |
372 | |
373 | impl EmptyErrorInfo { |
374 | pub(crate) const fn empty() -> Self { |
375 | Self |
376 | } |
377 | |
378 | pub(crate) fn from_thread() -> Self { |
379 | Self |
380 | } |
381 | |
382 | pub(crate) fn into_thread(self) {} |
383 | |
384 | #[cfg (windows)] |
385 | pub(crate) fn originate_error(_code: HRESULT, _message: &str) {} |
386 | |
387 | pub(crate) fn message(&self) -> Option<String> { |
388 | None |
389 | } |
390 | |
391 | #[cfg (windows)] |
392 | pub(crate) fn as_ptr(&self) -> *mut core::ffi::c_void { |
393 | core::ptr::null_mut() |
394 | } |
395 | } |
396 | } |
397 | |