diff --git a/swiftwinrt/Resources/Support/Error.swift b/swiftwinrt/Resources/Support/Error.swift index 4ae678b0..bc292545 100644 --- a/swiftwinrt/Resources/Support/Error.swift +++ b/swiftwinrt/Resources/Support/Error.swift @@ -101,13 +101,7 @@ public var E_XAMLPARSEFAILED : WinSDK.HRESULT { HRESULT(bitPattern: 0x802B000A) } -private func getErrorDescription(expecting hr: HRESULT) -> String? { - var errorInfo: UnsafeMutablePointer? - guard GetRestrictedErrorInfo(&errorInfo) == S_OK else { return nil } - defer { - _ = errorInfo?.pointee.lpVtbl.pointee.Release(errorInfo) - } - +private func getErrorDescription(restrictedErrorInfo: UnsafeMutablePointer) -> String? { var errorDescription: BSTR? var restrictedDescription: BSTR? var capabilitySid: BSTR? @@ -116,16 +110,14 @@ private func getErrorDescription(expecting hr: HRESULT) -> String? { SysFreeString(restrictedDescription) SysFreeString(capabilitySid) } - var resultLocal: HRESULT = S_OK - _ = errorInfo?.pointee.lpVtbl.pointee.GetErrorDetails( - errorInfo, + var hr: HRESULT = S_OK + guard restrictedErrorInfo.pointee.lpVtbl.pointee.GetErrorDetails( + restrictedErrorInfo, &errorDescription, - &resultLocal, + &hr, &restrictedDescription, - &capabilitySid) + &capabilitySid) == S_OK else { return nil } - guard resultLocal == hr else { return nil } - // Favor restrictedDescription as this is a more user friendly message, which // is intended to be displayed to the caller to help them understand why the // api call failed. If it's not set, then fallback to the generic error message @@ -135,7 +127,7 @@ private func getErrorDescription(expecting hr: HRESULT) -> String? { } else if SysStringLen(errorDescription) > 0 { return String(decodingCString: errorDescription!, as: UTF16.self) } else { - return nil + return hrToString(hr) } } @@ -164,7 +156,21 @@ public struct Error : Swift.Error, CustomStringConvertible { public let hr: HRESULT public init(hr: HRESULT) { - self.description = getErrorDescription(expecting: hr) ?? hrToString(hr) + // RoGetMatchingRestrictedErrorInfo creates an IRestrictedErrorInfo with a generic message if it can't find one for the given HRESULT. + var restrictedErrorInfo: UnsafeMutablePointer? + if RoGetMatchingRestrictedErrorInfo(hr, &restrictedErrorInfo) == S_OK, let restrictedErrorInfo { + defer { _ = restrictedErrorInfo.pointee.lpVtbl.pointee.Release(restrictedErrorInfo) } + + // From the docs: https://learn.microsoft.com/en-us/windows/win32/api/roerrorapi/nf-roerrorapi-getrestrictederrorinfo + // > GetRestrictedErrorInfo transfers ownership of the error object to the caller and clears the error state for the thread. + // Assume that RoGetMatchingRestrictedErrorInfo also clears the error state, + // but for crash reporting purposes, it's useful to preserve the it. + _ = SetRestrictedErrorInfo(restrictedErrorInfo) + + self.description = getErrorDescription(restrictedErrorInfo: restrictedErrorInfo) ?? hrToString(hr) + } else { + self.description = hrToString(hr) + } self.hr = hr } } diff --git a/tests/test_app/main.swift b/tests/test_app/main.swift index fb37caa7..c8c1366c 100644 --- a/tests/test_app/main.swift +++ b/tests/test_app/main.swift @@ -433,6 +433,39 @@ class SwiftWinRTTests : XCTestCase { } } + public func testIRestrictedErrorInfo() { + let message = "You are doing a bad thing" + do { + let classy = Class() + try classy.fail(message) + } catch { + var restrictedErrorInfo: UnsafeMutablePointer? + guard GetRestrictedErrorInfo(&restrictedErrorInfo) == S_OK, let restrictedErrorInfo else { + XCTFail("Failed to get error info") + return + } + defer { _ = restrictedErrorInfo.pointee.lpVtbl.pointee.Release(restrictedErrorInfo) } + + var errorDescription: BSTR? + var hr: HRESULT = S_OK + var restrictedDescription: BSTR? + var capabilitySid: BSTR? + defer { + SysFreeString(errorDescription) + SysFreeString(restrictedDescription) + SysFreeString(capabilitySid) + } + guard restrictedErrorInfo.pointee.lpVtbl.pointee.GetErrorDetails( + restrictedErrorInfo, &errorDescription, &hr, &restrictedDescription, &capabilitySid) == S_OK, + let restrictedDescription else { + XCTFail("Failed to get error description") + return + } + + XCTAssertEqual(String(decodingCString: restrictedDescription, as: UTF16.self), message) + } + } + public func testNoExcept() throws { let classy = Class() classy.noexceptVoid() @@ -459,6 +492,7 @@ var tests: [XCTestCaseEntry] = [ ("testStructWithIReference", SwiftWinRTTests.testStructWithIReference), ("testUnicode", SwiftWinRTTests.testUnicode), ("testErrorInfo", SwiftWinRTTests.testErrorInfo), + ("testIRestrictedErrorInfo", SwiftWinRTTests.testIRestrictedErrorInfo), ]) ] + valueBoxingTests + eventTests + collectionTests + aggregationTests + asyncTests + memoryManagementTests + bufferTests + weakReferenceTests