Fix exception_wrapper on Windows by abusing internals
authorAndrew Krieger <andrew.krieger@oculus.com>
Fri, 23 Jun 2017 20:58:11 +0000 (13:58 -0700)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Fri, 23 Jun 2017 21:05:31 +0000 (14:05 -0700)
Summary:
exception_wrapper's non-allocating variant was depending
on the exception reference being a reference to a heap object,
which is not the case on Windows. All thrown exceptions are
copied to the stack first.

So, we abuse internals of exception_ptr to get the internally
heap allocated copy of the object. What could possibly go wrong.

Reviewed By: yfeldblum, ericniebler, Orvid

Differential Revision: D5270634

fbshipit-source-id: 2983a6af9a2c3cb66cbee8a382707a76b582c489

folly/ExceptionWrapper-inl.h
folly/ExceptionWrapper.h
folly/Portability.h

index 287275aec9ed73ab53bfba9fffb8f80a42b34de7..2845c2e36c8493d63ea05fe1ef11231bcf807448 100644 (file)
@@ -18,6 +18,8 @@
  * Author: Eric Niebler <eniebler@fb.com>
  */
 
+#include <folly/Portability.h>
+
 namespace folly {
 
 template <class Fn>
@@ -90,11 +92,48 @@ inline std::exception const* exception_wrapper::as_exception_or_null_(
   return nullptr;
 }
 
+static_assert(
+    !kIsWindows || sizeof(void*) == 8,
+    "exception_wrapper is untested on 32 bit Windows.");
+static_assert(
+    !kIsWindows || (kMscVer >= 1900 && kMscVer <= 2000),
+    "exception_wrapper is untested and possibly broken on your version of "
+    "MSVC");
+
 inline std::uintptr_t exception_wrapper::ExceptionPtr::as_int_(
+    std::exception_ptr const& ptr,
     std::exception const& e) {
-  return reinterpret_cast<std::uintptr_t>(&e);
+  if (!kIsWindows) {
+    return reinterpret_cast<std::uintptr_t>(&e);
+  } else {
+    // On Windows, as of MSVC2017, all thrown exceptions are copied to the stack
+    // first. Thus, we cannot depend on exception references associated with an
+    // exception_ptr to be live for the duration of the exception_ptr. We need
+    // to directly access the heap allocated memory inside the exception_ptr.
+    //
+    // std::exception_ptr is an opaque reinterpret_cast of
+    // std::shared_ptr<__ExceptionPtr>
+    // __ExceptionPtr is a non-virtual class with two members, a union and a
+    // bool. The union contains the now-undocumented EHExceptionRecord, which
+    // contains a struct which contains a void* which points to the heap
+    // allocated exception.
+    // We derive the offset to pExceptionObject via manual means.
+    FOLLY_PACK_PUSH
+    struct Win32ExceptionPtr {
+      char offset[40];
+      void* exceptionObject;
+    } FOLLY_PACK_ATTR;
+    FOLLY_PACK_POP
+
+    auto* win32ExceptionPtr =
+        reinterpret_cast<std::shared_ptr<Win32ExceptionPtr> const*>(
+            &ptr)->get();
+    return reinterpret_cast<std::uintptr_t>(win32ExceptionPtr->exceptionObject);
+  }
 }
-inline std::uintptr_t exception_wrapper::ExceptionPtr::as_int_(AnyException e) {
+inline std::uintptr_t exception_wrapper::ExceptionPtr::as_int_(
+    std::exception_ptr const&,
+    AnyException e) {
   return reinterpret_cast<std::uintptr_t>(e.typeinfo_) + 1;
 }
 inline bool exception_wrapper::ExceptionPtr::has_exception_() const {
@@ -282,7 +321,7 @@ inline exception_wrapper::~exception_wrapper() {
 
 template <class Ex>
 inline exception_wrapper::exception_wrapper(std::exception_ptr ptr, Ex& ex)
-    : eptr_{std::move(ptr), ExceptionPtr::as_int_(ex)},
+    : eptr_{ptr, ExceptionPtr::as_int_(ptr, ex)},
       vptr_(&ExceptionPtr::ops_) {
   assert(eptr_.ptr_);
 }
index b00dc77d213a8e6e5efcf5d0187a4ccd3ca199a6..449080d745ea7b86a8cb0f77bf9f1540aacf0bfc 100644 (file)
@@ -258,8 +258,12 @@ class exception_wrapper final {
         "Surprise! std::exception and std::type_info don't have alignment "
         "greater than one. as_int_ below will not work!");
 
-    static std::uintptr_t as_int_(std::exception const& e);
-    static std::uintptr_t as_int_(AnyException e);
+    static std::uintptr_t as_int_(
+        std::exception_ptr const& ptr,
+        std::exception const& e);
+    static std::uintptr_t as_int_(
+        std::exception_ptr const& ptr,
+        AnyException e);
     bool has_exception_() const;
     std::exception const* as_exception_() const;
     std::type_info const* as_type_() const;
index 7b822cda2d60da1fe3849b32f4d7de09554d7ea8..7063e07237b3115d850206966052b625326deb95 100644 (file)
@@ -352,7 +352,9 @@ constexpr auto kIsLinux = false;
 
 #if defined(_WIN32)
 constexpr auto kIsWindows = true;
+constexpr auto kMscVer = _MSC_VER;
 #else
 constexpr auto kIsWindows = false;
+constexpr auto kMscVer = 0;
 #endif
 }