Repository: microsoft/wil (C++)

Issue:
wil::com_task does not propagate thread-local restricted error information across thread boundaries
I have a crash that I'm debugging and it has been difficult to debug because the UI thread doesn't have the original stowed exception information.  The first RoOriginateError call on the UI thread is in the `winrt::terminate` handler when we are already going down a fatal code path.  After trying lots of different approaches I finally thought to check for stowed exceptions on every thread in the process and sure enough one of the background threads has the "real" original exception.

The program in question is roughly as follows:
```cpp
wil::com_task<Foo> DoThingAsync()
{
  co_await winrt::resume_background();
  THROW_HR(E_FAIL); // oopsies
  co_return foo;
}

winrt::fire_and_forget UpdateUI()
{
  try
  {
    auto result = co_await DoThingAsync();
  }
  catch (winrt::hresult_error&)
  { /* handle */ }
}
```

My true bug is that we catch `winrt::hresult_error` but not `wil::ResultException` so the catch block doesn't catch and the exception hits the `winrt::fire_and_forget` noexcept boundary.  That crashes the process.  Now that I understand the problem it is easy enough to fix.

I am filing this issue because it was overly difficult to figure this out based on a crash dump.  I think the situation could be improved if `wil::com_task` were to call [GetRestrictedErrorInfo](https://learn.microsoft.com/en-us/windows/win32/api/roerrorapi/nf-roerrorapi-getrestrictederrorinfo) on the background thread and if it is non-null propagate it to the UI thread with a call to `SetRestrictedErrorInfo`.  The auto-originate would have then seen the original and let it remain active.  Failure analysis would have also pointed me far more directly at the true problem.



Fix (patch):
diff --git a/include/wil/coroutine.h b/include/wil/coroutine.h
index 14fcb7a0..00a87b35 100644
--- a/include/wil/coroutine.h
+++ b/include/wil/coroutine.h
@@ -151,7 +151,7 @@
 #if defined(_RESUMABLE_FUNCTIONS_SUPPORTED)
 #include <experimental/coroutine>
 #define __WI_COROUTINE_NAMESPACE ::std::experimental
-#elif defined(__cpp_lib_coroutine)
+#elif defined(__cpp_impl_coroutine)
 #include <coroutine>
 #define __WI_COROUTINE_NAMESPACE ::std
 #else
@@ -161,6 +161,7 @@
 
 #include <atomic>
 #include <exception>
+#include <utility>
 #include <wil/wistd_memory.h>
 #include <wil/wistd_type_traits.h>
 #include <wil/result_macros.h>
@@ -190,6 +191,16 @@ namespace wil
 /// @cond
 namespace wil::details::coro
 {
+    // task and com_task are convertable to each other.  However, not
+    // all consumers of this header have COM enabled.  Support for saving
+    // COM thread-local error information and restoring it on the resuming
+    // thread is enabled using these function pointers.  If COM is not
+    // available then they are null and do not get called.  If COM is
+    // enabled then they are filled in with valid pointers and get used.
+    __declspec(selectany) void*(__stdcall* g_pfnCaptureRestrictedErrorInformation)() WI_PFN_NOEXCEPT = nullptr;
+    __declspec(selectany) void(__stdcall* g_pfnRestoreRestrictedErrorInformation)(void* restricted_error) WI_PFN_NOEXCEPT = nullptr;
+    __declspec(selectany) void(__stdcall* g_pfnDestroyRestrictedErrorInformation)(void* restricted_error) WI_PFN_NOEXCEPT = nullptr;
+
     template<typename T>
     struct task_promise;
 
@@ -245,6 +256,16 @@ namespace wil::details::coro
             std::exception_ptr error;
         } result;
 
+        // The restricted error information is lit up when COM headers are
+        // included.  If COM is not available then this will remain null.
+        // This error information is thread-local so we must save it on suspend
+        // and restore it on resume so that it propagates to the correct
+        // thread.  It will then be available if the exception proves fatal.
+        //
+        // This object is non-copyable so we do not need to worry about
+        // supporting AddRef on the restricted error information.
+        void* restricted_error{ nullptr };
+
         // emplace_value will be called with
         //
         // * no parameters (void category)
@@ -264,6 +285,12 @@ namespace wil::details::coro
 
         void unhandled_exception() noexcept
         {
+            if (g_pfnCaptureRestrictedErrorInformation)
+            {
+                WI_ASSERT(restricted_error == nullptr);
+                restricted_error = g_pfnCaptureRestrictedErrorInformation();
+            }
+
             WI_ASSERT(status == result_status::empty);
             new (wistd::addressof(result.error)) std::exception_ptr(std::current_exception());
             status = result_status::error;
@@ -275,7 +302,12 @@ namespace wil::details::coro
             {
                 return result.wrap.get_value();
             }
+
             WI_ASSERT(status == result_status::error);
+            if (restricted_error && g_pfnRestoreRestrictedErrorInformation)
+            {
+                g_pfnRestoreRestrictedErrorInformation(restricted_error);
+            }
             std::rethrow_exception(wistd::exchange(result.error, {}));
         }
 
@@ -285,6 +317,12 @@ namespace wil::details::coro
 
         ~result_holder() noexcept(false)
         {
+            if (restricted_error && g_pfnDestroyRestrictedErrorInformation)
+            {
+                g_pfnDestroyRestrictedErrorInformation(restricted_error);
+                restricted_error = nullptr;
+            }
+
             switch (status)
             {
             case result_status::value:
@@ -663,9 +701,27 @@ namespace wil::details::coro
 #define __WIL_COROUTINE_NON_AGILE_INCLUDED
 #include <ctxtcall.h>
 #include <wil/com.h>
+#include <roerrorapi.h>
 
 namespace wil::details::coro
 {
+    inline void* __stdcall CaptureRestrictedErrorInformation() noexcept
+    {
+        IRestrictedErrorInfo* restrictedError = nullptr;
+        (void)GetRestrictedErrorInfo(&restrictedError);
+        return restrictedError; // the returned object includes a strong reference
+    }
+
+    inline void __stdcall RestoreRestrictedErrorInformation(_In_ void* restricted_error) noexcept
+    {
+        (void)SetRestrictedErrorInfo(static_cast<IRestrictedErrorInfo*>(restricted_error));
+    }
+
+    inline void __stdcall DestroyRestrictedErrorInformation(_In_ void* restricted_error) noexcept
+    {
+        static_cast<IUnknown*>(restricted_error)->Release();
+    }
+
     struct apartment_info
     {
         APTTYPE aptType;
@@ -824,4 +880,15 @@ namespace wil::details::coro
         return com_awaiter<T>{ wistd::move(promise) };
     }
 }
+
+// This section is lit up when COM headers are available.  Initialize the global function
+// pointers such that error information can be saved and restored across thread boundaries.
+WI_HEADER_INITITALIZATION_FUNCTION(CoroutineRestrictedErrorInitialize, []
+{
+    ::wil::details::coro::g_pfnCaptureRestrictedErrorInformation = ::wil::details::coro::CaptureRestrictedErrorInformation;
+    ::wil::details::coro::g_pfnRestoreRestrictedErrorInformation = ::wil::details::coro::RestoreRestrictedErrorInformation;
+    ::wil::details::coro::g_pfnDestroyRestrictedErrorInformation = ::wil::details::coro::DestroyRestrictedErrorInformation;
+    return 1;
+})
+
 #endif // __WIL_COROUTINE_NON_AGILE_INCLUDED
diff --git a/include/wil/cppwinrt_helpers.h b/include/wil/cppwinrt_helpers.h
index b60ec7d4..fc99db87 100644
--- a/include/wil/cppwinrt_helpers.h
+++ b/include/wil/cppwinrt_helpers.h
@@ -44,16 +44,18 @@ namespace wil::details
 {
     template<typename T = void> using coroutine_handle = std::experimental::coroutine_handle<T>;
 }
-#elif defined(__cpp_lib_coroutine) && (__cpp_lib_coroutine >= 201902L)
+#elif defined(__cpp_impl_coroutine)
 #include <coroutine>
+#if (__cpp_lib_coroutine >= 201902L)
 namespace wil::details
 {
     template<typename T = void> using coroutine_handle = std::coroutine_handle<T>;
 }
-#endif
+#endif // __cpp_lib_coroutine
+#endif // __cpp_impl_coroutine
 /// @endcond
 
-#if defined(_RESUMABLE_FUNCTIONS_SUPPORTED) || (defined(__cpp_lib_coroutine) && (__cpp_lib_coroutine >= 201902L))
+#if defined(_RESUMABLE_FUNCTIONS_SUPPORTED) || (defined(__cpp_impl_coroutine) && defined(__cpp_lib_coroutine) && (__cpp_lib_coroutine >= 201902L))
 /// @cond
 namespace wil::details
 {