{"id":109621,"date":"2024-04-05T07:00:00","date_gmt":"2024-04-05T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=109621"},"modified":"2024-04-05T10:25:56","modified_gmt":"2024-04-05T17:25:56","slug":"20240405-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240405-00\/?p=109621","title":{"rendered":"The case of the exception that a <CODE>catch (&#8230;)<\/CODE> didn&#8217;t catch"},"content":{"rendered":"<p>A customer thought they fixed a bug, but they were still getting crashes from it.<\/p>\n<p>According to the <code>!analyze<\/code> output, the problem was coming from this stack:<\/p>\n<pre>contoso!winrt::hresult_error::hresult_error+0x143\r\ncontoso!winrt::throw_hresult+0x132\r\ncontoso!winrt::impl::consume_LitWare_IIconProvider\r\n    &lt;winrt::LitWare::IIconProvider&gt;::LoadIcon+0x3b\r\ncontoso!winrt::Contoso::implementation::IconDataModel::\r\n    ReloadIcon$_ResumeCoro$1+0x214\r\ncontoso!winrt::impl::resume_background_callback+0x10\r\nntdll!TppSimplepExecuteCallback+0xa3\r\nntdll!TppWorkerThread+0x8f6\r\nkernel32!BaseThreadInitThunk+0x1d\r\nntdll!RtlUserThreadStart+0x28\r\n<\/pre>\n<p>This was puzzling because &#8220;We already fixed that bug!&#8221; The file version number and timestamp confirm that the code for <code>Reload\u00adIcon<\/code> catches the exception:<\/p>\n<pre>    try\r\n    {\r\n        icon = m_provider.LoadIcon(); \/\/ \u21d0 blamed frame\r\n    }\r\n    catch(...)\r\n    {\r\n        \/\/ There was a problem getting the new icon.\r\n        \/\/ Just stick with the old one.\r\n        LOG_CAUGHT_EXCEPTION();\r\n        co_return;\r\n    }\r\n<\/pre>\n<p>Let&#8217;s look at the stack at the point of the crash:<\/p>\n<pre>KERNELBASE!RaiseFailFastException+0x152\r\ncombase!RoFailFastWithErrorContextInternal2+0x4d9\r\ncontoso!wil::details::FailfastWithContextCallback+0xc1\r\ncontoso!wil::details::WilFailFast+0x47\r\ncontoso!wil::details::ReportFailure_NoReturn&lt;3&gt;+0x2df\r\ncontoso!wil::details::ReportFailure_Base&lt;3,0&gt;+0x30\r\ncontoso!wil::details::ReportFailure_CaughtExceptionCommonNoReturnBase&lt;3&gt;+0xa7\r\ncontoso!wil::details::ReportFailure_CaughtExceptionCommon+0x22\r\ncontoso!wil::details::ReportFailure_CaughtException&lt;3&gt;+0x40\r\ncontoso!wil::details::in1diag3::FailFast_CaughtException+0x13\r\ncontoso!`&lt;lambda_f370031fe3623a0b308de0bbdeb2db76&gt;::operator()'::`1'::catch$2+0x22\r\nucrtbase!_CallSettingFrame_LookupContinuationIndex+0x20\r\nucrtbase!__FrameHandler4::CxxCallCatchBlock+0x115\r\nntdll!RcFrameConsolidation+0x6\r\ncontoso!&lt;lambda_f370031fe3623a0b308de0bbdeb2db76&gt;::operator()+0x1a\r\ncontoso!std::invoke+0x24\r\ncontoso!std::_Invoker_ret&lt;void,1&gt;::_Call+0x24\r\ncontoso!std::_Func_impl_no_alloc&lt;&lt;lambda_f370031fe3623a0b308de0bbdeb2db76&gt;,\r\n    void,Concurrency::task&lt;void&gt; &gt;::_Do_call+0x28\r\ncontoso!std::_Func_class&lt;void,Concurrency::task&lt;void&gt; &gt;::operator()+0x31\r\ncontoso!Concurrency::details::_MakeTToUnitFunc::__l2::\r\n    &lt;lambda_64124396551846798083ef48cd389b4a&gt;::operator()+0x46\r\ncontoso!std::invoke+0x66\r\ncontoso!std::_Invoker_ret&lt;unsigned char,0&gt;::_Call+0x66\r\ncontoso!std::_Func_impl_no_alloc&lt;&lt;lambda_64124396551846798083ef48cd389b4a&gt;,\r\n    unsigned char,Concurrency::task&lt;void&gt; &gt;::_Do_call+0x72\r\ncontoso!std::_Func_class&lt;unsigned char,Concurrency::task&lt;void&gt; &gt;::\r\n    operator()+0x32\r\ncontoso!Concurrency::task&lt;void&gt;::_ContinuationTaskHandle&lt;void,\r\n    void,std::function&lt;void __cdecl(Concurrency::task&lt;void&gt;)&gt;,\r\n    std::integral_constant&lt;bool,1&gt;,Concurrency::details::_TypeSelectorNoAsync&gt;::\r\n    _LogWorkItemAndInvokeUserLambda&lt;std::function&lt;unsigned char __cdecl(\r\n    Concurrency::task&lt;void&gt;)&gt;,Concurrency::task&lt;void&gt; &gt;+0x8b\r\ncontoso!Concurrency::task&lt;void&gt;::_ContinuationTaskHandle&lt;void,\r\n    void,std::function&lt;void __cdecl(Concurrency::task&lt;void&gt;)&gt;,\r\n    std::integral_constant&lt;bool,1&gt;,Concurrency::details::_TypeSelectorNoAsync&gt;::\r\n    _Continue+0x8c\r\ncontoso!Concurrency::task&lt;void&gt;::_ContinuationTaskHandle&lt;void,\r\n    void,std::function&lt;void __cdecl(Concurrency::task&lt;void&gt;)&gt;,\r\n    std::integral_constant&lt;bool,1&gt;,Concurrency::details::_TypeSelectorNoAsync&gt;::\r\n    _Perform+0x8\r\ncontoso!Concurrency::details::_PPLTaskHandle&lt;unsigned char,Concurrency::task&lt;\r\n    void&gt;::_ContinuationTaskHandle&lt;void,void,std::function&lt;\r\n    void __cdecl(Concurrency::task&lt;void&gt;)&gt;,std::integral_constant&lt;bool,1&gt;,\r\n    Concurrency::details::_TypeSelectorNoAsync&gt;,\r\n    Concurrency::details::_ContinuationTaskHandleBase&gt;::invoke+0x37\r\ncontoso!Concurrency::details::_TaskProcHandle::_RunChoreBridge+0x25\r\ncontoso!Concurrency::details::_DefaultPPLTaskScheduler::_PPLTaskChore::\r\n    _Callback+0x26\r\nmsvcp140!Concurrency::details::`anonymous namespace'::\r\n    _Task_scheduler_callback+0x5d\r\nntdll!TppWorkpExecuteCallback+0x13a\r\nntdll!TppWorkerThread+0x8f6\r\nkernel32!BaseThreadInitThunk+0x1d\r\nntdll!RtlUserThreadStart+0x28\r\n<\/pre>\n<p>Hey, wait a second, this doesn&#8217;t look anything like the stack reported by <code>!analyze<\/code>! What&#8217;s going on?<\/p>\n<p>The <code>!analyze<\/code> used the stack from the first stowed exception. You can dump all of the stowed exceptions with the <code>!pde.dse<\/code> command.<\/p>\n<pre>0:076&gt; !pde.dse\r\nStowed Exception Array @ 0x000000002b1ef170\r\n\r\nStowed Exception #1 @ 0x000000001ce068e8\r\n    0x80070005 (FACILITY_WIN32 - Win32 Undecorated Error Codes):\r\n    E_ACCESSDENIED - General access denied error\r\n\r\n    Stack    : 0x2b214de0\r\n    contoso!winrt::hresult_error::hresult_error+0x143\r\n    contoso!winrt::throw_hresult+0x132\r\n    contoso!winrt::impl::consume_LitWare_IIconProvider\r\n        &lt;winrt::LitWare::IIconProvider&gt;::LoadIcon+0x3b\r\n    contoso!winrt::Contoso::implementation::IconDataModel::\r\n        ReloadIcon$_ResumeCoro$1+0x214\r\n    contoso!winrt::impl::resume_background_callback+0x10\r\n    ntdll!TppSimplepExecuteCallback+0xa3\r\n    ntdll!TppWorkerThread+0x8f6\r\n    kernel32!BaseThreadInitThunk+0x1d\r\n    ntdll!RtlUserThreadStart+0x28\r\n\r\nStowed Exception #2 @ 0x000000001ce02378\r\n    0x80070005 (FACILITY_WIN32 - Win32 Undecorated Error Codes):\r\n    E_ACCESSDENIED - General access denied error\r\n\r\n    Stack    : 0x12cda890\r\n    litware!winrt::hresult_error::hresult_error+0x12c\r\n    litware!winrt::throw_hresult+0x83\r\n    litware!winrt::LitWare::implementation::IconProvider::LoadIcon+0x90\r\n    litware!winrt::impl::produce&lt;winrt::LitWare::implementation::IconProvider,\r\n        winrt::LitWare::IIconProvider&gt;::LoadIcon+0x1b\r\n    contoso!winrt::impl::consume_LitWare_IIconProvider\r\n        &lt;winrt::LitWare::IIconProvider&gt;::LoadIcon+0x3b\r\n    contoso!winrt::Contoso::implementation::IconDataModel::\r\n        ReloadIcon$_ResumeCoro$1+0x214\r\n    contoso!winrt::impl::resume_background_callback+0x10\r\n    ntdll!TppSimplepExecuteCallback+0xa3\r\n    ntdll!TppWorkerThread+0x8f6\r\n    kernel32!BaseThreadInitThunk+0x1d\r\n    ntdll!RtlUserThreadStart+0x28\r\n\r\nStowed Exception #3 @ 0x000000001ce04fa8\r\n    0x80070005 (FACILITY_WIN32 - Win32 Undecorated Error Codes):\r\n    E_ACCESSDENIED - General access denied error\r\n\r\n    Stack    : 0x1d94b410\r\n    combase!RoOriginateError+0x51\r\n    contoso!wil::details::RaiseRoOriginateOnWilExceptions+0x137\r\n    contoso!wil::details::ReportFailure_Return&lt;1&gt;+0x1b8\r\n    contoso!wil::details::ReportFailure_Win32&lt;1&gt;+0x70\r\n    contoso!wil::details::in1diag3::Return_Win32+0x18\r\n    contoso!Internal::ContosoSettingsStorage::Save+0xdc729\r\n    contoso!Internal::ContosoSettings::SaveToDefaultLocalStorage+0xf1\r\n    contoso!Internal::ContosoSettings::Save+0x4ef\r\n    contoso!Contoso::AppSettings::save+0x4ef\r\n    contoso!std::_Func_impl_no_alloc&lt;&lt;lambda_f4300885c0b58e31cf789c4999ed9d7a&gt;,\r\n        void&gt;::_Do_call+0x2b\r\n    contoso!std::_Func_impl_no_alloc&lt;&lt;lambda_052e919cc0e5399df76dff3972c0cac1&gt;,\r\n        unsigned char&gt;::_Do_call+0x28\r\n    contoso!Concurrency::task&lt;unsigned char&gt;::_InitialTaskHandle&lt;void,\r\n        &lt;lambda_f4300885c0b58e31cf789c4999ed9d7a&gt;,\r\n        Concurrency::details::_TypeSelectorNoAsync&gt;::_Init+0xc3\r\n    contoso!Concurrency::details::_PPLTaskHandle&lt;unsigned char,\r\n        Concurrency::task&lt;unsigned char&gt;::_InitialTaskHandle&lt;void,\r\n        &lt;lambda_f4300885c0b58e31cf789c4999ed9d7a&gt;,\r\n        Concurrency::details::_TypeSelectorNoAsync&gt;,\r\n        Concurrency::details::_TaskProcHandle&gt;::invoke+0x55\r\n    contoso!Concurrency::details::_TaskProcHandle::_RunChoreBridge+0x25\r\n    contoso!Concurrency::details::_DefaultPPLTaskScheduler::_PPLTaskChore::\r\n        _Callback+0x26\r\n    msvcp140!Concurrency::details::`anonymous namespace'::\r\n        _Task_scheduler_callback+0x5d\r\n    ntdll!TppWorkpExecuteCallback+0x13a\r\n    ntdll!TppWorkerThread+0x686\r\n    kernel32!BaseThreadInitThunk+0x10\r\n    ntdll!RtlUserThreadStart+0x2b\r\n<\/pre>\n<p>Now things are starting to come together.<\/p>\n<p>The rule of thumb for throwing Windows Runtime exceptions is that before you throw the exception or return the failure <code>HRESULT<\/code>, you call <code>RoOriginateError<\/code> to capture the stack and other context. It is common when working with the Windows Runtime that the exception is caught and saved (&#8220;stowed&#8221;), usually in an <code>IAsyncAction<\/code> or similar interface, and then later, when the caller does a <code>co_await<\/code> or similar operation. the exception is rethrown.<\/p>\n<p>When the exception is rethrown, the original stack has already unwound, so there is nothing on the stack to trace. Calling <code>RoOriginateError<\/code> captures the stack at the point of failure before it&#8217;s too late. This information can then be used to &#8220;stitch together&#8221; the exception lifetime, starting with the code that threw the exception and ending with the code that tried (and failed) to catch it.<\/p>\n<p>The system does this stitching by storing error history in per-thread data, allowing components to capture that history and transfer it to another thread when a task&#8217;s error state moves between threads, and looking for errors with the same <code>HRESULT<\/code>s.\u00b9 If there is a recent captured stack for an <code>HRESULT<\/code> that matches the <code>HRESULT<\/code> of the exception that went unhandled, then the system says, &#8220;I bet these two belong together.&#8221;<\/p>\n<p>Usually, all of this stack-stitching works out well because our API design principles say that exceptions should not be thrown for recoverable errors. This means that there generally not a lot of exception traffic, so the rate of false positives is low.<\/p>\n<p>But in this case, we had a false positive: The <code>IconDataModel<\/code> called <code>IconProvider::<wbr \/>LoadIcon()<\/code>, which failed with <code>E_ACCESS\u00adDENIED<\/code>. This exception was then caught and handled. We see this from the top two stowed exceptions, using what we learned a little while ago <a title=\"The case of the invalid parameter error from MeasureOverride\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240202-00\/?p=109352\"> about stitching together multiple error stacks get a fuller picture of what led to a failure<\/a>.<\/p>\n<p>In this case, the <code>Icon\u00adProvider::<wbr \/>Load\u00adIcon()<\/code> explicitly threw an exception with <code>throw_<wbr \/>hresult<\/code> (Stowed Exception #2), which then was converted from a C++ exception to an <code>HRESULT<\/code> at the ABI boundary, and then on the other side, C++\/WinRT turned the <code>HRESULT<\/code> back into an exception and rethrew it (Stowed Exception #1). This rethrown exception was then caught by the <code>catch (...)<\/code>, and that&#8217;s the end of that exception.<\/p>\n<p>That&#8217;s not what caused us to crash.<\/p>\n<p>The current active stack shows that we raised a fail-fast exception from a lambda. The debugger tells us that it&#8217;s this lambda:<\/p>\n<pre>void ViewPreferences::SaveChanges()\r\n{\r\n    m_settings.save_async()\r\n    .then([](concurrency::task&lt;void&gt; precedingTask) {\r\n        try\r\n        {\r\n            precedingTask.get();\r\n        }\r\n        CATCH_FAIL_FAST();\r\n    });\r\n}\r\n<\/pre>\n<p>The code saves the settings and fails fast if the operation failed.<\/p>\n<p>And we see that failure in the <i>third<\/i> stack, the one with <code>Contoso\u00adSettings\u00adStorage::<wbr \/>Save<\/code>. That <code>Save<\/code> operation failed with <code>E_<wbr \/>ACCESS\u00adDENIED<\/code>, and it was logged in the failure history.<\/p>\n<p>What happened is that there were <i>two<\/i> <code>E_<wbr \/>ACCESS\u00adDENIED<\/code> errors that occurred at roughly the same time, and <code>!analyze<\/code>&#8216;s attempt to figure out which stacks belonged to which sequence was not completely successful, and it thought that the current failure matched up with the <code>m_provider.LoadIcon()<\/code> failure. But we, using our human brains, saw that the <code>m_provider.LoadIcon()<\/code> exception was handled, and the real culprit was the Stowed Exception #3.<\/p>\n<p>\u00b9 You can call the function <code>Ro\u00adTransform\u00adError<\/code> if your code receives one error code and returns a different one. This tells COM error-tracking that these two error sequences should be stitched together to form one large error sequence.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Reconstructing a false history.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-109621","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Reconstructing a false history.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109621","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=109621"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109621\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=109621"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=109621"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=109621"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}