diff --git a/doc/core.qbk b/doc/core.qbk index 23e47a5..e381264 100644 --- a/doc/core.qbk +++ b/doc/core.qbk @@ -57,3 +57,4 @@ criteria for inclusion is that the utility component be: [include scoped_enum.qbk] [include swap.qbk] [include typeinfo.qbk] +[include uncaught_exceptions.qbk] diff --git a/doc/uncaught_exceptions.qbk b/doc/uncaught_exceptions.qbk new file mode 100644 index 0000000..31b75ac --- /dev/null +++ b/doc/uncaught_exceptions.qbk @@ -0,0 +1,52 @@ +[/ + / Copyright (c) 2018 Andrey Semashev + / + / Distributed under the Boost Software License, Version 1.0. (See accompanying + / file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + /] + +[section:uncaught_exceptions uncaught_exceptions] + +[simplesect Authors] + +* Andrey Semashev + +[endsimplesect] + +[section Header ] + +The header `` defines the `boost::core::uncaught_exceptions` function, +which is a more portable implementation of the same named function introduced in C++17. The function +returns the number of the currently pending exceptions. When that function returns a value greater than 0, +throwing an exception from a destructor can terminate the program. + +Unfortunately, the function cannot be implemented on every pre-C++17 compiler, although the most commonly +used compilers are supported. When the compiler does not provide the necessary functionality, +`boost::core::uncaught_exceptions` returns a non-zero value if at least one exception is pending (i.e. not +necessarily the number of pending exceptions), and `BOOST_CORE_UNCAUGHT_EXCEPTIONS_EMULATED` macro +is defined. + +[section Example] +`` +class my_class +{ +private: + const unsigned int m_exception_count; + +public: + my_class() : m_exception_count(boost::core::uncaught_exceptions()) + { + } + + ~my_class() noexcept(false) + { + if (m_exception_count == boost::core::uncaught_exceptions()) + do_something_potentially_throwing(); + } +}; +`` +[endsect] + +[endsect] + +[endsect] diff --git a/include/boost/core/uncaught_exceptions.hpp b/include/boost/core/uncaught_exceptions.hpp new file mode 100644 index 0000000..eedcf00 --- /dev/null +++ b/include/boost/core/uncaught_exceptions.hpp @@ -0,0 +1,93 @@ +/* + * Copyright Andrey Semashev 2018. + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * https://www.boost.org/LICENSE_1_0.txt) + */ +/*! + * \file uncaught_exceptions.hpp + * \author Andrey Semashev + * \date 2018-11-10 + * + * \brief This header provides an `uncaught_exception` function implementation, which was introduced in C++17. + * + * The code in this file is based on the implementation by Evgeny Panasyuk: + * + * https://github.com/panaseleus/stack_unwinding/blob/master/boost/exception/uncaught_exception_count.hpp + */ + +#include +#include + +#if defined(BOOST_HAS_PRAGMA_ONCE) +#pragma once +#endif + +namespace boost { + +namespace core { + +namespace detail { + +// cxxabi.h availability macro +#if defined(__has_include) && (!defined(BOOST_GCC) || (__GNUC__ + 0) >= 5) +# if __has_include() +# define BOOST_CORE_HAS_CXXABI_H +# endif +#elif defined(__GLIBCXX__) || defined(__GLIBCPP__) +# define BOOST_CORE_HAS_CXXABI_H +#endif + +#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411 +#define BOOST_CORE_HAS_UNCAUGHT_EXCEPTIONS +#elif defined(BOOST_CORE_HAS_CXXABI_H) +// MinGW GCC 4.4 seem to not work the same way the newer GCC versions do. As a result, __cxa_get_globals based implementation will always return 0. +// Just disable it for now and fall back to std::uncaught_exception(). +#if !defined(__MINGW32__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5))) +// Only GCC 4.7 declares __cxa_get_globals() in cxxabi.h, older compilers do not expose this function but it's there +#define BOOST_CORE_HAS_CXA_GET_GLOBALS +extern "C" void* __cxa_get_globals(); +#endif +#elif defined(_MSC_VER) +#if _MSC_VER >= 1900 +// Visual Studio 14 supports N4152 std::uncaught_exceptions() +#define BOOST_CORE_HAS_UNCAUGHT_EXCEPTIONS +#elif _MSC_VER >= 1400 +#define BOOST_CORE_HAS_GETPTD +extern "C" void* _getptd(); +#endif +#endif + +} // namespace detail + +#if !defined(BOOST_CORE_HAS_UNCAUGHT_EXCEPTIONS) && !defined(BOOST_CORE_HAS_CXA_GET_GLOBALS) && !defined(BOOST_CORE_HAS_GETPTD) +//! This macro is defined when `uncaught_exceptions` is not guaranteed to return values greater than 1 if multiple exceptions are pending +#define BOOST_CORE_UNCAUGHT_EXCEPTIONS_EMULATED +#endif + +//! Returns the number of currently pending exceptions +inline unsigned int uncaught_exceptions() BOOST_NOEXCEPT +{ +#if defined(BOOST_CORE_HAS_UNCAUGHT_EXCEPTIONS) + // C++17 implementation + return static_cast< unsigned int >(std::uncaught_exceptions()); +#elif defined(BOOST_CORE_HAS_CXA_GET_GLOBALS) + // Tested on {clang 3.2,GCC 3.5.6,GCC 4.1.2,GCC 4.4.6,GCC 4.4.7}x{x32,x64} + return *(reinterpret_cast< const unsigned int* >(static_cast< const char* >(boost::core::detail::__cxa_get_globals()) + sizeof(void*))); // __cxa_eh_globals::uncaughtExceptions, x32 offset - 0x4, x64 - 0x8 +#elif defined(BOOST_CORE_HAS_GETPTD) + // MSVC specific. Tested on {MSVC2005SP1,MSVC2008SP1,MSVC2010SP1,MSVC2012}x{x32,x64}. + return *(reinterpret_cast< const unsigned int* >(static_cast< const char* >(boost::core::detail::_getptd()) + (sizeof(void*) == 8 ? 0x100 : 0x90))); // _tiddata::_ProcessingThrow, x32 offset - 0x90, x64 - 0x100 +#else + // Portable C++03 implementation. Does not allow to detect multiple nested exceptions. + return static_cast< unsigned int >(std::uncaught_exception()); +#endif +} + +#undef BOOST_CORE_HAS_CXXABI_H +#undef BOOST_CORE_HAS_CXA_GET_GLOBALS +#undef BOOST_CORE_HAS_UNCAUGHT_EXCEPTIONS +#undef BOOST_CORE_HAS_GETPTD + +} // namespace core + +} // namespace boost diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index b4af148..7b7875d 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -138,5 +138,8 @@ run test_lib_typeid.cpp lib_typeid : : : static : test_lib_typeid_static ; run test_lib_typeid.cpp lib_typeid : : : shared off : test_lib_typeid_shared_no_rtti ; run test_lib_typeid.cpp lib_typeid : : : static off : test_lib_typeid_static_no_rtti ; +run uncaught_exceptions.cpp : : : on ; +run uncaught_exceptions_np.cpp : : : on ; + use-project /boost/core/swap : ./swap ; build-project ./swap ; diff --git a/test/uncaught_exceptions.cpp b/test/uncaught_exceptions.cpp new file mode 100644 index 0000000..d8b1fc6 --- /dev/null +++ b/test/uncaught_exceptions.cpp @@ -0,0 +1,55 @@ +/* + * Copyright Andrey Semashev 2018. + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * https://www.boost.org/LICENSE_1_0.txt) + */ +/*! + * \file uncaught_exceptions.cpp + * \author Andrey Semashev + * \date 2018-11-10 + * + * \brief This file contains tests for the uncaught_exceptions function. + * + * This file only contains the very basic checks of functionality that can be portably achieved + * through C++03 std::uncaught_exception. + */ + +#include +#include + +struct my_exception {}; + +class exception_watcher +{ + unsigned int& m_count; + +public: + explicit exception_watcher(unsigned int& count) : m_count(count) {} + ~exception_watcher() { m_count = boost::core::uncaught_exceptions(); } +}; + +// Tests for uncaught_exceptions when used in a destructor while an exception propagates +void test_in_destructor() +{ + const unsigned int root_count = boost::core::uncaught_exceptions(); + + unsigned int level1_count = root_count; + try + { + exception_watcher watcher(level1_count); + throw my_exception(); + } + catch (...) + { + } + + BOOST_TEST_NE(root_count, level1_count); +} + +int main() +{ + test_in_destructor(); + + return boost::report_errors(); +} diff --git a/test/uncaught_exceptions_np.cpp b/test/uncaught_exceptions_np.cpp new file mode 100644 index 0000000..7498ccf --- /dev/null +++ b/test/uncaught_exceptions_np.cpp @@ -0,0 +1,90 @@ +/* + * Copyright Andrey Semashev 2018. + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * https://www.boost.org/LICENSE_1_0.txt) + */ +/*! + * \file uncaught_exceptions_np.cpp + * \author Andrey Semashev + * \date 2018-11-10 + * + * \brief This file contains tests for the uncaught_exceptions function. + * + * This file contains checks that are compiler specific and not quite portable or require C++17. + */ + +#include + +#if !defined(BOOST_CORE_UNCAUGHT_EXCEPTIONS_EMULATED) + +#include + +struct my_exception1 {}; +struct my_exception2 {}; + +class exception_watcher2 +{ + unsigned int& m_count; + +public: + explicit exception_watcher2(unsigned int& count) : m_count(count) {} + ~exception_watcher2() { m_count = boost::core::uncaught_exceptions(); } +}; + +class exception_watcher1 +{ + unsigned int& m_count1; + unsigned int& m_count2; + +public: + exception_watcher1(unsigned int& count1, unsigned int& count2) : m_count1(count1), m_count2(count2) {} + ~exception_watcher1() + { + m_count1 = boost::core::uncaught_exceptions(); + try + { + exception_watcher2 watcher2(m_count2); + throw my_exception2(); + } + catch (...) + { + } + } +}; + +// Tests for uncaught_exceptions when used in nested destructors while an exception propagates +void test_in_nested_destructors() +{ + const unsigned int root_count = boost::core::uncaught_exceptions(); + + unsigned int level1_count = root_count, level2_count = root_count; + try + { + exception_watcher1 watcher1(level1_count, level2_count); + throw my_exception1(); + } + catch (...) + { + } + + BOOST_TEST_NE(root_count, level1_count); + BOOST_TEST_NE(root_count, level2_count); + BOOST_TEST_NE(level1_count, level2_count); +} + +int main() +{ + test_in_nested_destructors(); + + return boost::report_errors(); +} + +#else + +int main() +{ + return 0; +} + +#endif