| 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 | |