From a57c72d7c5d712b95aa1bad48a507f026437a1f8 Mon Sep 17 00:00:00 2001 From: Daniel Marinescu Date: Wed, 13 Nov 2013 20:41:24 -0800 Subject: [PATCH] Added SCOPE_FAIL and SCOPE_SUCCESS macros in non-portable C++. 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 | 96 ++++++++++++++++++- folly/detail/UncaughtExceptionCounter.h | 92 ++++++++++++++++++ .../exception_tracer/ExceptionAbi.h | 4 +- folly/test/ScopeGuardTest.cpp | 30 ++++++ 4 files changed, 216 insertions(+), 6 deletions(-) create mode 100644 folly/detail/UncaughtExceptionCounter.h diff --git a/folly/ScopeGuard.h b/folly/ScopeGuard.h index a3050a84..bb9ad867 100644 --- a/folly/ScopeGuard.h +++ b/folly/ScopeGuard.h @@ -22,6 +22,7 @@ #include #include "folly/Preprocessor.h" +#include "folly/detail/UncaughtExceptionCounter.h" namespace folly { @@ -84,7 +85,7 @@ class ScopeGuardImplBase { bool dismissed_; }; -template +template 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 +template ScopeGuardImpl::type> makeGuard(FunctionType&& fn) { return ScopeGuardImpl::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 +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 +ScopeGuardForNewException::type, true> +operator+(detail::ScopeGuardOnFail, FunctionType&& fn) { + return + ScopeGuardForNewException::type, true>( + std::forward(fn)); +} + +/** + * Internal use for the macro SCOPE_SUCCESS below + */ +enum class ScopeGuardOnSuccess {}; + +template +ScopeGuardForNewException::type, false> +operator+(ScopeGuardOnSuccess, FunctionType&& fn) { + return + ScopeGuardForNewException::type, false>( + std::forward(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 index 00000000..0cebac43 --- /dev/null +++ b/folly/detail/UncaughtExceptionCounter.h @@ -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 + +#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(static_cast( + static_cast(__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(static_cast( + static_cast(_getptd())) + sizeof(void*) * 28 + 0x4 * 8)); +#endif +} + +}} // namespaces + +#endif /* FOLLY_DETAIL_UNCAUGHTEXCEPTIONCOUNTER_H_ */ diff --git a/folly/experimental/exception_tracer/ExceptionAbi.h b/folly/experimental/exception_tracer/ExceptionAbi.h index 73415cfe..3fa10cc7 100644 --- a/folly/experimental/exception_tracer/ExceptionAbi.h +++ b/folly/experimental/exception_tracer/ExceptionAbi.h @@ -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 diff --git a/folly/test/ScopeGuardTest.cpp b/folly/test/ScopeGuardTest.cpp index b741e375..71a86e90 100644 --- a/folly/test/ScopeGuardTest.cpp +++ b/folly/test/ScopeGuardTest.cpp @@ -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); -- 2.34.1