Added SCOPE_FAIL and SCOPE_SUCCESS macros in non-portable C++.
authorDaniel Marinescu <danielm@fb.com>
Thu, 14 Nov 2013 04:41:24 +0000 (20:41 -0800)
committerPeter Griess <pgriess@fb.com>
Tue, 26 Nov 2013 15:05:17 +0000 (07:05 -0800)
Summary:
Added SCOPE_FAIL and SCOPE_SUCCESS macros in non-portable C++. The macros are similar to D's scope(failure) and scope(success).
Currently the supported platforms are GCC and MSVC. For all others, std::uncaught_exception() is used, which will fail if the macros are used in a destructor called during stack unwinding.
@override-unit-failures

Test Plan:
1. Added new unit test to ScopeGuardTest.cpp.
2. Ran fbconfig -r folly && fbmake dbg
3. Ran _build/dbg/folly/test/scope_guard_test to make sure my unit test was running and passing.

Reviewed By: andrei.alexandrescu@fb.com

FB internal diff: D1033621

folly/ScopeGuard.h
folly/detail/UncaughtExceptionCounter.h [new file with mode: 0644]
folly/experimental/exception_tracer/ExceptionAbi.h
folly/test/ScopeGuardTest.cpp

index a3050a84701973961268462f1901198140eea349..bb9ad86728857679a0e4616ddfc70501af8da209 100644 (file)
@@ -22,6 +22,7 @@
 #include <new>
 
 #include "folly/Preprocessor.h"
+#include "folly/detail/UncaughtExceptionCounter.h"
 
 namespace folly {
 
@@ -84,7 +85,7 @@ class ScopeGuardImplBase {
   bool dismissed_;
 };
 
-template<typename FunctionType>
+template <typename FunctionType>
 class ScopeGuardImpl : public ScopeGuardImplBase {
  public:
   explicit ScopeGuardImpl(const FunctionType& fn)
@@ -94,8 +95,8 @@ class ScopeGuardImpl : public ScopeGuardImplBase {
     : function_(std::move(fn)) {}
 
   ScopeGuardImpl(ScopeGuardImpl&& other)
-    : ScopeGuardImplBase(std::move(other)),
-      function_(std::move(other.function_)) {
+    : ScopeGuardImplBase(std::move(other))
+    , function_(std::move(other.function_)) {
   }
 
   ~ScopeGuardImpl() noexcept {
@@ -112,7 +113,7 @@ class ScopeGuardImpl : public ScopeGuardImplBase {
   FunctionType function_;
 };
 
-template<typename FunctionType>
+template <typename FunctionType>
 ScopeGuardImpl<typename std::decay<FunctionType>::type>
 makeGuard(FunctionType&& fn) {
   return ScopeGuardImpl<typename std::decay<FunctionType>::type>(
@@ -125,6 +126,82 @@ makeGuard(FunctionType&& fn) {
 typedef ScopeGuardImplBase&& ScopeGuard;
 
 namespace detail {
+
+#if defined(FOLLY_EXCEPTION_COUNT_USE_CXA_GET_GLOBALS) || \
+    defined(FOLLY_EXCEPTION_COUNT_USE_GETPTD)
+
+/**
+ * ScopeGuard used for executing a function when leaving the current scope
+ * depending on the presence of a new uncaught exception.
+ *
+ * If the executeOnException template parameter is true, the function is
+ * executed if a new uncaught exception is present at the end of the scope.
+ * If the parameter is false, then the function is executed if no new uncaught
+ * exceptions are present at the end of the scope.
+ *
+ * Used to implement SCOPE_FAIL and SCOPE_SUCCES below.
+ */
+template <typename FunctionType, bool executeOnException>
+class ScopeGuardForNewException {
+ public:
+  explicit ScopeGuardForNewException(const FunctionType& fn)
+      : function_(fn) {
+  }
+
+  explicit ScopeGuardForNewException(FunctionType&& fn)
+      : function_(std::move(fn)) {
+  }
+
+  ScopeGuardForNewException(ScopeGuardForNewException&& other)
+      : function_(std::move(other.function_))
+      , exceptionCounter_(std::move(other.exceptionCounter_)) {
+  }
+
+  ~ScopeGuardForNewException() noexcept {
+    if (executeOnException == exceptionCounter_.isNewUncaughtException()) {
+      execute();
+    }
+  }
+
+ private:
+  ScopeGuardForNewException(const ScopeGuardForNewException& other) = delete;
+
+  void* operator new(size_t) = delete;
+
+  void execute() noexcept { function_(); }
+
+  FunctionType function_;
+  UncaughtExceptionCounter exceptionCounter_;
+};
+
+/**
+ * Internal use for the macro SCOPE_FAIL below
+ */
+enum class ScopeGuardOnFail {};
+
+template <typename FunctionType>
+ScopeGuardForNewException<typename std::decay<FunctionType>::type, true>
+operator+(detail::ScopeGuardOnFail, FunctionType&& fn) {
+  return
+      ScopeGuardForNewException<typename std::decay<FunctionType>::type, true>(
+      std::forward<FunctionType>(fn));
+}
+
+/**
+ * Internal use for the macro SCOPE_SUCCESS below
+ */
+enum class ScopeGuardOnSuccess {};
+
+template <typename FunctionType>
+ScopeGuardForNewException<typename std::decay<FunctionType>::type, false>
+operator+(ScopeGuardOnSuccess, FunctionType&& fn) {
+  return
+      ScopeGuardForNewException<typename std::decay<FunctionType>::type, false>(
+      std::forward<FunctionType>(fn));
+}
+
+#endif // native uncaught_exception() supported
+
 /**
  * Internal use for the macro SCOPE_EXIT below
  */
@@ -144,4 +221,15 @@ operator+(detail::ScopeGuardOnExit, FunctionType&& fn) {
   auto FB_ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) \
   = ::folly::detail::ScopeGuardOnExit() + [&]
 
+#if defined(FOLLY_EXCEPTION_COUNT_USE_CXA_GET_GLOBALS) || \
+    defined(FOLLY_EXCEPTION_COUNT_USE_GETPTD)
+#define SCOPE_FAIL \
+  auto FB_ANONYMOUS_VARIABLE(SCOPE_FAIL_STATE) \
+  = ::folly::detail::ScopeGuardOnFail() + [&]
+
+#define SCOPE_SUCCESS \
+  auto FB_ANONYMOUS_VARIABLE(SCOPE_SUCCESS_STATE) \
+  = ::folly::detail::ScopeGuardOnSuccess() + [&]
+#endif // native uncaught_exception() supported
+
 #endif // FOLLY_SCOPEGUARD_H_
diff --git a/folly/detail/UncaughtExceptionCounter.h b/folly/detail/UncaughtExceptionCounter.h
new file mode 100644 (file)
index 0000000..0cebac4
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FOLLY_DETAIL_UNCAUGHTEXCEPTIONCOUNTER_H_
+#define FOLLY_DETAIL_UNCAUGHTEXCEPTIONCOUNTER_H_
+
+#include <exception>
+
+#if defined(__GNUG__) || defined(__CLANG__)
+#define FOLLY_EXCEPTION_COUNT_USE_CXA_GET_GLOBALS
+namespace __cxxabiv1 {
+// forward declaration (originally defined in unwind-cxx.h from from libstdc++)
+struct __cxa_eh_globals;
+// declared in cxxabi.h from libstdc++-v3
+extern "C" __cxa_eh_globals* __cxa_get_globals() noexcept;
+}
+#elif defined(_MSC_VER) && (_MSC_VER >= 1400) // MSVC++ 8.0 or greater
+#define FOLLY_EXCEPTION_COUNT_USE_GETPTD
+// forward declaration (originally defined in mtdll.h from MSVCRT)
+struct _tiddata;
+extern "C" _tiddata* _getptd(); // declared in mtdll.h from MSVCRT
+#else
+// Raise an error when trying to use this on unsupported platforms.
+#error "Unsupported platform, don't include this header."
+#endif
+
+
+namespace folly { namespace detail {
+
+/**
+ * Used to check if a new uncaught exception was thrown by monitoring the
+ * number of uncaught exceptions.
+ *
+ * Usage:
+ *  - create a new UncaughtExceptionCounter object
+ *  - call isNewUncaughtException() on the new object to check if a new
+ *    uncaught exception was thrown since the object was created
+ */
+class UncaughtExceptionCounter {
+ public:
+  UncaughtExceptionCounter()
+    : exceptionCount_(getUncaughtExceptionCount()) {}
+
+  UncaughtExceptionCounter(const UncaughtExceptionCounter& other)
+    : exceptionCount_(other.exceptionCount_) {}
+
+  bool isNewUncaughtException() noexcept {
+    return getUncaughtExceptionCount() > exceptionCount_;
+  }
+
+ private:
+  int getUncaughtExceptionCount() noexcept;
+
+  int exceptionCount_;
+};
+
+/**
+ * Returns the number of uncaught exceptions.
+ *
+ * This function is based on Evgeny Panasyuk's implementation from here:
+ * http://fburl.com/15190026
+ */
+inline int UncaughtExceptionCounter::getUncaughtExceptionCount() noexcept {
+#if defined(FOLLY_EXCEPTION_COUNT_USE_CXA_GET_GLOBALS)
+  // __cxa_get_globals returns a __cxa_eh_globals* (defined in unwind-cxx.h).
+  // The offset below returns __cxa_eh_globals::uncaughtExceptions.
+  return *(reinterpret_cast<unsigned int*>(static_cast<char*>(
+      static_cast<void*>(__cxxabiv1::__cxa_get_globals())) + sizeof(void*)));
+#elif defined(FOLLY_EXCEPTION_COUNT_USE_GETPTD)
+  // _getptd() returns a _tiddata* (defined in mtdll.h).
+  // The offset below returns _tiddata::_ProcessingThrow.
+  return *(reinterpret_cast<int*>(static_cast<char*>(
+      static_cast<void*>(_getptd())) + sizeof(void*) * 28 + 0x4 * 8));
+#endif
+}
+
+}} // namespaces
+
+#endif /* FOLLY_DETAIL_UNCAUGHTEXCEPTIONCOUNTER_H_ */
index 73415cfec00a2d369a7ab44c9def43e4862f4789..3fa10cc7b79b27c2e2f3f6c74ebcd53af75b15a8 100644 (file)
@@ -51,8 +51,8 @@ struct __cxa_eh_globals {
 };
 
 extern "C" {
-__cxa_eh_globals* __cxa_get_globals(void);
-__cxa_eh_globals* __cxa_get_globals_fast(void);
+__cxa_eh_globals* __cxa_get_globals(void) noexcept;
+__cxa_eh_globals* __cxa_get_globals_fast(void) noexcept;
 }
 
 }  // namespace __cxxabiv1
index b741e37503536cfbc7977e316b1f4e607b82cd16..71a86e901403406ad54da9bec8fc9fe2fe2a487a 100644 (file)
@@ -258,6 +258,36 @@ TEST(ScopeGuard, TEST_SCOPE_FAILURE2) {
   }
 }
 
+void testScopeFailAndScopeSuccess(ErrorBehavior error, bool expectFail) {
+  bool scopeFailExecuted = false;
+  bool scopeSuccessExecuted = false;
+
+  try {
+    SCOPE_FAIL { scopeFailExecuted = true; };
+    SCOPE_SUCCESS { scopeSuccessExecuted = true; };
+
+    try {
+      if (error == ErrorBehavior::HANDLED_ERROR) {
+        throw std::runtime_error("throwing an expected error");
+      } else if (error == ErrorBehavior::UNHANDLED_ERROR) {
+        throw "never throw raw strings";
+      }
+    } catch (const std::runtime_error&) {
+    }
+  } catch (...) {
+    // Outer catch to swallow the error for the UNHANDLED_ERROR behavior
+  }
+
+  EXPECT_EQ(expectFail, scopeFailExecuted);
+  EXPECT_EQ(!expectFail, scopeSuccessExecuted);
+}
+
+TEST(ScopeGuard, TEST_SCOPE_FAIL_AND_SCOPE_SUCCESS) {
+  testScopeFailAndScopeSuccess(ErrorBehavior::SUCCESS, false);
+  testScopeFailAndScopeSuccess(ErrorBehavior::HANDLED_ERROR, false);
+  testScopeFailAndScopeSuccess(ErrorBehavior::UNHANDLED_ERROR, true);
+}
+
 int main(int argc, char** argv) {
   testing::InitGoogleTest(&argc, argv);
   google::ParseCommandLineFlags(&argc, &argv, true);