diff --git a/libcxx/CMakeLists.txt b/libcxx/CMakeLists.txt --- a/libcxx/CMakeLists.txt +++ b/libcxx/CMakeLists.txt @@ -82,7 +82,10 @@ include(HandleCompilerRT) # Basic options --------------------------------------------------------------- -option(LIBCXX_ENABLE_ASSERTIONS "Enable assertions independent of build mode." OFF) +option(LIBCXX_ENABLE_ASSERTIONS + "Enable assertions inside the compiled library, and at the same time make it the + default when compiling user code. Note that assertions can be enabled or disabled + by users in their own code regardless of this option." OFF) option(LIBCXX_ENABLE_SHARED "Build libc++ as a shared library." ON) option(LIBCXX_ENABLE_STATIC "Build libc++ as a static library." ON) option(LIBCXX_ENABLE_EXPERIMENTAL_LIBRARY "Build libc++experimental.a" ON) @@ -675,7 +678,6 @@ endif() # Assertion flags ============================================================= -define_if(LIBCXX_ENABLE_ASSERTIONS -D_LIBCPP_DEBUG=0) define_if(LIBCXX_DEBUG_BUILD -D_DEBUG) if (LIBCXX_ENABLE_ASSERTIONS AND NOT LIBCXX_DEBUG_BUILD) # MSVC doesn't like _DEBUG on release builds. See PR 4379. @@ -877,6 +879,11 @@ config_define_if_not(LIBCXX_ENABLE_UNICODE _LIBCPP_HAS_NO_UNICODE) config_define_if_not(LIBCXX_ENABLE_WIDE_CHARACTERS _LIBCPP_HAS_NO_WIDE_CHARACTERS) config_define_if_not(LIBCXX_ENABLE_VENDOR_AVAILABILITY_ANNOTATIONS _LIBCPP_HAS_NO_VENDOR_AVAILABILITY_ANNOTATIONS) +if (LIBCXX_ENABLE_ASSERTIONS) + config_define(1 _LIBCPP_ENABLE_ASSERTIONS_DEFAULT) +else() + config_define(0 _LIBCPP_ENABLE_ASSERTIONS_DEFAULT) +endif() # Incomplete features get their own specific disabling flags. This makes it # easier to grep for target specific flags once the feature is complete. config_define_if_not(LIBCXX_ENABLE_INCOMPLETE_FEATURES _LIBCPP_HAS_NO_INCOMPLETE_FORMAT) diff --git a/libcxx/cmake/caches/Generic-assertions.cmake b/libcxx/cmake/caches/Generic-assertions.cmake --- a/libcxx/cmake/caches/Generic-assertions.cmake +++ b/libcxx/cmake/caches/Generic-assertions.cmake @@ -1 +1,3 @@ set(LIBCXX_ENABLE_ASSERTIONS ON CACHE BOOL "") +set(LIBCXX_TEST_PARAMS "enable_assertions=True" CACHE STRING "") +set(LIBCXXABI_TEST_PARAMS "enable_assertions=True" CACHE STRING "") diff --git a/libcxx/docs/BuildingLibcxx.rst b/libcxx/docs/BuildingLibcxx.rst --- a/libcxx/docs/BuildingLibcxx.rst +++ b/libcxx/docs/BuildingLibcxx.rst @@ -216,7 +216,10 @@ **Default**: ``OFF`` - Build libc++ with assertions enabled. + Build libc++ with assertions enabled in the compiled library, and enable assertions + by default when building user code as well. Assertions can be turned off by users + by defining ``_LIBCPP_ENABLE_ASSERTIONS=0``. For details, see + :ref:`the documentation `. .. option:: LIBCXX_ENABLE_SHARED:BOOL diff --git a/libcxx/docs/ReleaseNotes.rst b/libcxx/docs/ReleaseNotes.rst --- a/libcxx/docs/ReleaseNotes.rst +++ b/libcxx/docs/ReleaseNotes.rst @@ -46,6 +46,12 @@ "heapsort with bounce" to reduce the number of comparisons, and rearranges elements using move-assignment instead of `swap`. + - Libc++ now supports a variety of assertions that can be turned on to help catch + undefined behavior in user code. This new support is now separate from the old + (and incomplete) Debug Mode. Vendors can select whether the library they ship + should include assertions or not by default. For details, see + :ref:`the documentation ` about this new feature. + API Changes ----------- @@ -74,6 +80,10 @@ - The C++14 function ``std::quoted(const char*)`` is no longer supported in C++03 or C++11 modes. +- Setting a custom debug handler with ``std::__libcpp_debug_function`` is not + supported anymore. Please migrate to using the new support for + :ref:`assertions ` instead. + ABI Changes ----------- diff --git a/libcxx/docs/UsingLibcxx.rst b/libcxx/docs/UsingLibcxx.rst --- a/libcxx/docs/UsingLibcxx.rst +++ b/libcxx/docs/UsingLibcxx.rst @@ -121,6 +121,69 @@ +.. _assertions-mode: + +Enabling the "safe libc++" mode +=============================== + +Libc++ contains a number of assertions whose goal is to catch undefined behavior in the +library, usually caused by precondition violations. Those assertions do not aim to be +exhaustive -- instead they aim to provide a good balance between safety and performance. +In particular, these assertions do not change the complexity of algorithms. However, they +might, in some cases, interfere with compiler optimizations. + +By default, these assertions are turned off. Vendors can decide to turn them on while building +the compiled library by defining ``LIBCXX_ENABLE_ASSERTIONS=ON`` at CMake configuration time. +When ``LIBCXX_ENABLE_ASSERTIONS`` is used, the compiled library will be built with assertions +enabled, **and** user code will be built with assertions enabled by default. If +``LIBCXX_ENABLE_ASSERTIONS=OFF`` at CMake configure time, the compiled library will not contain +assertions and the default when building user code will be to have assertions disabled. + +Independently of any vendor-selected default, users can always control whether assertions +are enabled in their code by defining ``_LIBCPP_ENABLE_ASSERTIONS=0|1`` before including any +libc++ header. Note that if the compiled library was built by the vendor without assertions, +functions compiled inside the static or shared library won't have assertions enabled even if +the user defines ``_LIBCPP_ENABLE_ASSERTIONS=1`` (the same is true for the inverse case where +the static or shared library was compiled **with** assertions but the user tries to disable +them). However, most of the code in libc++ is in the headers, so the user-selected value for +``_LIBCPP_ENABLE_ASSERTIONS`` (if any) will usually be respected. + +When an assertion fails, an assertion handler function is called. The library provides a default +assertion handler that prints an error message and calls ``std::abort()``. Note that this assertion +handler is provided by the static or shared library, so it is only available when deploying to a +platform where the compiled library is sufficiently recent. However, users can also override that +assertion handler with their own, which can be useful to provide custom behavior, or when deploying +to older platforms where the default assertion handler isn't available. + +Replacing the default assertion handler is done by defining the ``void __libcpp_assertion_handler(__libcpp_assertion_info)`` +function, similarly to how ``operator new`` and ``operator delete`` can be replaced. For example: + +.. code-block:: cpp + + // In HelloWorldHandler.cpp + #include // must include any libc++ header before defining the handler + + void std::__libcpp_assertion_handler(char const* file, int line, char const* expression, char const* message) { + std::printf("Assertion %s failed at %s:%d, more info: %s", expression, file, line, message); + std::abort(); + } + + // In HelloWorld.cpp + #include + + int main() { + std::vector v; + int& x = v[0]; // Your assertion handler will be called here if _LIBCPP_ENABLE_ASSERTIONS=1 + } + +Also note that the assertion handler should usually not return. Since the assertions in libc++ +catch undefined behavior, your code will proceed with undefined behavior if your assertion +handler is called and does return. + +Furthermore, throwing an exception from the assertion handler is not recommended. Indeed, many +functions in the library are ``noexcept``, and any exception thrown from the assertion handler +will result in ``std::terminate`` being called. + Libc++ Configuration Macros =========================== diff --git a/libcxx/include/__assert b/libcxx/include/__assert --- a/libcxx/include/__assert +++ b/libcxx/include/__assert @@ -11,51 +11,37 @@ #define _LIBCPP___ASSERT #include <__config> -#include // for std::string #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) # pragma GCC system_header #endif +// This is for backwards compatibility with code that might have been enabling +// assertions through the Debug mode previously. #if _LIBCPP_DEBUG_LEVEL >= 1 -# define _LIBCPP_ASSERT(x, m) ((x) ? (void)0 : ::std::__libcpp_debug_function(::std::__libcpp_debug_info(__FILE__, __LINE__, #x, m))) +# ifndef _LIBCPP_ENABLE_ASSERTIONS +# define _LIBCPP_ENABLE_ASSERTIONS 1 +# endif +#endif + +#ifndef _LIBCPP_ENABLE_ASSERTIONS +# define _LIBCPP_ENABLE_ASSERTIONS _LIBCPP_ENABLE_ASSERTIONS_DEFAULT +#endif + +#if _LIBCPP_ENABLE_ASSERTIONS != 0 && _LIBCPP_ENABLE_ASSERTIONS != 1 +# error "_LIBCPP_ENABLE_ASSERTIONS must be set to 0 or 1" +#endif + +#if _LIBCPP_ENABLE_ASSERTIONS +# define _LIBCPP_ASSERT(expression, message) ((expression) ? (void)0 : ::std::__libcpp_assertion_handler(__FILE__, __LINE__, #expression, message)) #else -# define _LIBCPP_ASSERT(x, m) ((void)0) +# define _LIBCPP_ASSERT(x, m) ((void)0) #endif _LIBCPP_BEGIN_NAMESPACE_STD -struct _LIBCPP_TEMPLATE_VIS __libcpp_debug_info { - _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR - __libcpp_debug_info() - : __file_(nullptr), __line_(-1), __pred_(nullptr), __msg_(nullptr) {} - _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR - __libcpp_debug_info(const char* __f, int __l, const char* __p, const char* __m) - : __file_(__f), __line_(__l), __pred_(__p), __msg_(__m) {} - - _LIBCPP_FUNC_VIS string what() const; - - const char* __file_; - int __line_; - const char* __pred_; - const char* __msg_; -}; - -/// __libcpp_debug_function_type - The type of the assertion failure handler. -typedef void(*__libcpp_debug_function_type)(__libcpp_debug_info const&); - -/// __libcpp_debug_function - The handler function called when a _LIBCPP_ASSERT -/// fails. -extern _LIBCPP_EXPORTED_FROM_ABI __libcpp_debug_function_type __libcpp_debug_function; - -/// __libcpp_abort_debug_function - A debug handler that aborts when called. -_LIBCPP_NORETURN _LIBCPP_FUNC_VIS -void __libcpp_abort_debug_function(__libcpp_debug_info const&); - -/// __libcpp_set_debug_function - Set the debug handler to the specified -/// function. -_LIBCPP_FUNC_VIS -bool __libcpp_set_debug_function(__libcpp_debug_function_type __func); +_LIBCPP_OVERRIDABLE_FUNC_VIS +void __libcpp_assertion_handler(char const* __file, int __line, char const* __expression, char const* __message); _LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/include/__config_site.in b/libcxx/include/__config_site.in --- a/libcxx/include/__config_site.in +++ b/libcxx/include/__config_site.in @@ -32,6 +32,7 @@ #cmakedefine _LIBCPP_HAS_NO_WIDE_CHARACTERS #cmakedefine _LIBCPP_HAS_NO_INCOMPLETE_FORMAT #cmakedefine _LIBCPP_HAS_NO_INCOMPLETE_RANGES +#cmakedefine01 _LIBCPP_ENABLE_ASSERTIONS_DEFAULT // __USE_MINGW_ANSI_STDIO gets redefined on MinGW #ifdef __clang__ diff --git a/libcxx/include/version b/libcxx/include/version --- a/libcxx/include/version +++ b/libcxx/include/version @@ -193,6 +193,7 @@ */ +#include <__assert> #include <__config> #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) diff --git a/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.debug.incomplete.abilist b/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.debug.incomplete.abilist --- a/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.debug.incomplete.abilist +++ b/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.debug.incomplete.abilist @@ -1561,6 +1561,7 @@ {'is_defined': True, 'name': '__ZNSt3__123__libcpp_atomic_monitorEPVKv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__123__libcpp_debug_functionE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZNSt3__125notify_all_at_thread_exitERNS_18condition_variableENS_11unique_lockINS_5mutexEEE', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__126__libcpp_assertion_handlerEPKciS1_S1_', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__127__insertion_sort_incompleteIRNS_6__lessIaaEEPaEEbT0_S5_T_', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__127__insertion_sort_incompleteIRNS_6__lessIccEEPcEEbT0_S5_T_', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__127__insertion_sort_incompleteIRNS_6__lessIddEEPdEEbT0_S5_T_', 'type': 'FUNC'} diff --git a/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.nodebug.noincomplete.abilist b/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.nodebug.noincomplete.abilist --- a/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.nodebug.noincomplete.abilist +++ b/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.nodebug.noincomplete.abilist @@ -1534,6 +1534,7 @@ {'is_defined': True, 'name': '__ZNSt3__123__libcpp_atomic_monitorEPVKNS_17__cxx_atomic_implIxNS_22__cxx_atomic_base_implIxEEEE', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__123__libcpp_atomic_monitorEPVKv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__125notify_all_at_thread_exitERNS_18condition_variableENS_11unique_lockINS_5mutexEEE', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__126__libcpp_assertion_handlerEPKciS1_S1_', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__127__insertion_sort_incompleteIRNS_6__lessIaaEEPaEEbT0_S5_T_', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__127__insertion_sort_incompleteIRNS_6__lessIccEEPcEEbT0_S5_T_', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__127__insertion_sort_incompleteIRNS_6__lessIddEEPdEEbT0_S5_T_', 'type': 'FUNC'} diff --git a/libcxx/lib/abi/x86_64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.debug.incomplete.abilist b/libcxx/lib/abi/x86_64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.debug.incomplete.abilist --- a/libcxx/lib/abi/x86_64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.debug.incomplete.abilist +++ b/libcxx/lib/abi/x86_64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.debug.incomplete.abilist @@ -1561,6 +1561,7 @@ {'is_defined': True, 'name': '__ZNSt3__123__libcpp_atomic_monitorEPVKv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__123__libcpp_debug_functionE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZNSt3__125notify_all_at_thread_exitERNS_18condition_variableENS_11unique_lockINS_5mutexEEE', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__126__libcpp_assertion_handlerEPKciS1_S1_', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__127__insertion_sort_incompleteIRNS_6__lessIaaEEPaEEbT0_S5_T_', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__127__insertion_sort_incompleteIRNS_6__lessIccEEPcEEbT0_S5_T_', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__127__insertion_sort_incompleteIRNS_6__lessIddEEPdEEbT0_S5_T_', 'type': 'FUNC'} diff --git a/libcxx/lib/abi/x86_64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.nodebug.noincomplete.abilist b/libcxx/lib/abi/x86_64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.nodebug.noincomplete.abilist --- a/libcxx/lib/abi/x86_64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.nodebug.noincomplete.abilist +++ b/libcxx/lib/abi/x86_64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.nodebug.noincomplete.abilist @@ -1534,6 +1534,7 @@ {'is_defined': True, 'name': '__ZNSt3__123__libcpp_atomic_monitorEPVKNS_17__cxx_atomic_implIxNS_22__cxx_atomic_base_implIxEEEE', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__123__libcpp_atomic_monitorEPVKv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__125notify_all_at_thread_exitERNS_18condition_variableENS_11unique_lockINS_5mutexEEE', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__126__libcpp_assertion_handlerEPKciS1_S1_', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__127__insertion_sort_incompleteIRNS_6__lessIaaEEPaEEbT0_S5_T_', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__127__insertion_sort_incompleteIRNS_6__lessIccEEPcEEbT0_S5_T_', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__127__insertion_sort_incompleteIRNS_6__lessIddEEPdEEbT0_S5_T_', 'type': 'FUNC'} diff --git a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.debug.incomplete.abilist b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.debug.incomplete.abilist --- a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.debug.incomplete.abilist +++ b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.debug.incomplete.abilist @@ -1252,6 +1252,7 @@ {'is_defined': True, 'name': '_ZNSt3__123__libcpp_atomic_monitorEPVKv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__123__libcpp_debug_functionE', 'size': 8, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZNSt3__125notify_all_at_thread_exitERNS_18condition_variableENS_11unique_lockINS_5mutexEEE', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__126__libcpp_assertion_handlerEPKciS1_S1_', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__127__insertion_sort_incompleteIRNS_6__lessIaaEEPaEEbT0_S5_T_', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__127__insertion_sort_incompleteIRNS_6__lessIccEEPcEEbT0_S5_T_', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__127__insertion_sort_incompleteIRNS_6__lessIddEEPdEEbT0_S5_T_', 'type': 'FUNC'} diff --git a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.debug.noincomplete.abilist b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.debug.noincomplete.abilist --- a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.debug.noincomplete.abilist +++ b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.debug.noincomplete.abilist @@ -1249,6 +1249,7 @@ {'is_defined': True, 'name': '_ZNSt3__123__libcpp_atomic_monitorEPVKv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__123__libcpp_debug_functionE', 'size': 8, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZNSt3__125notify_all_at_thread_exitERNS_18condition_variableENS_11unique_lockINS_5mutexEEE', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__126__libcpp_assertion_handlerEPKciS1_S1_', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__127__insertion_sort_incompleteIRNS_6__lessIaaEEPaEEbT0_S5_T_', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__127__insertion_sort_incompleteIRNS_6__lessIccEEPcEEbT0_S5_T_', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__127__insertion_sort_incompleteIRNS_6__lessIddEEPdEEbT0_S5_T_', 'type': 'FUNC'} diff --git a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.nodebug.incomplete.abilist b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.nodebug.incomplete.abilist --- a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.nodebug.incomplete.abilist +++ b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.nodebug.incomplete.abilist @@ -1228,6 +1228,7 @@ {'is_defined': True, 'name': '_ZNSt3__123__libcpp_atomic_monitorEPVKNS_17__cxx_atomic_implIiNS_22__cxx_atomic_base_implIiEEEE', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__123__libcpp_atomic_monitorEPVKv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__125notify_all_at_thread_exitERNS_18condition_variableENS_11unique_lockINS_5mutexEEE', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__126__libcpp_assertion_handlerEPKciS1_S1_', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__127__insertion_sort_incompleteIRNS_6__lessIaaEEPaEEbT0_S5_T_', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__127__insertion_sort_incompleteIRNS_6__lessIccEEPcEEbT0_S5_T_', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__127__insertion_sort_incompleteIRNS_6__lessIddEEPdEEbT0_S5_T_', 'type': 'FUNC'} diff --git a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.noexceptions.nonew.debug.incomplete.abilist b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.noexceptions.nonew.debug.incomplete.abilist --- a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.noexceptions.nonew.debug.incomplete.abilist +++ b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.noexceptions.nonew.debug.incomplete.abilist @@ -1224,6 +1224,7 @@ {'is_defined': True, 'name': '_ZNSt3__123__libcpp_atomic_monitorEPVKv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__123__libcpp_debug_functionE', 'size': 8, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZNSt3__125notify_all_at_thread_exitERNS_18condition_variableENS_11unique_lockINS_5mutexEEE', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__126__libcpp_assertion_handlerEPKciS1_S1_', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__127__insertion_sort_incompleteIRNS_6__lessIaaEEPaEEbT0_S5_T_', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__127__insertion_sort_incompleteIRNS_6__lessIccEEPcEEbT0_S5_T_', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__127__insertion_sort_incompleteIRNS_6__lessIddEEPdEEbT0_S5_T_', 'type': 'FUNC'} diff --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt --- a/libcxx/src/CMakeLists.txt +++ b/libcxx/src/CMakeLists.txt @@ -4,6 +4,7 @@ set(LIBCXX_SOURCES algorithm.cpp any.cpp + assert.cpp atomic.cpp barrier.cpp bind.cpp @@ -65,8 +66,8 @@ if (LIBCXX_ENABLE_DEBUG_MODE_SUPPORT) list(APPEND LIBCXX_SOURCES - assert.cpp debug.cpp + legacy_debug_handler.cpp ) endif() diff --git a/libcxx/src/assert.cpp b/libcxx/src/assert.cpp --- a/libcxx/src/assert.cpp +++ b/libcxx/src/assert.cpp @@ -10,29 +10,13 @@ #include <__config> #include #include -#include _LIBCPP_BEGIN_NAMESPACE_STD -std::string __libcpp_debug_info::what() const { - string msg = __file_; - msg += ":" + std::to_string(__line_) + ": _LIBCPP_ASSERT '"; - msg += __pred_; - msg += "' failed. "; - msg += __msg_; - return msg; -} - -_LIBCPP_NORETURN void __libcpp_abort_debug_function(__libcpp_debug_info const& info) { - std::fprintf(stderr, "%s\n", info.what().c_str()); - std::abort(); -} - -constinit __libcpp_debug_function_type __libcpp_debug_function = __libcpp_abort_debug_function; - -bool __libcpp_set_debug_function(__libcpp_debug_function_type __func) { - __libcpp_debug_function = __func; - return true; +_LIBCPP_WEAK +void __libcpp_assertion_handler(char const* __file, int __line, char const* __expression, char const* __message) { + std::fprintf(stderr, "%s:%d: libc++ assertion '%s' failed. %s\n", __file, __line, __expression, __message); + std::abort(); } _LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/src/assert.cpp b/libcxx/src/legacy_debug_handler.cpp copy from libcxx/src/assert.cpp copy to libcxx/src/legacy_debug_handler.cpp --- a/libcxx/src/assert.cpp +++ b/libcxx/src/legacy_debug_handler.cpp @@ -6,14 +6,26 @@ // //===----------------------------------------------------------------------===// -#include <__assert> #include <__config> #include #include #include +// This file defines the legacy default debug handler and related mechanisms +// to set it. This is for backwards ABI compatibility with code that has been +// using this debug handler previously. + _LIBCPP_BEGIN_NAMESPACE_STD +struct _LIBCPP_TEMPLATE_VIS __libcpp_debug_info { + _LIBCPP_EXPORTED_FROM_ABI string what() const; + + const char* __file_; + int __line_; + const char* __pred_; + const char* __msg_; +}; + std::string __libcpp_debug_info::what() const { string msg = __file_; msg += ":" + std::to_string(__line_) + ": _LIBCPP_ASSERT '"; @@ -23,13 +35,17 @@ return msg; } -_LIBCPP_NORETURN void __libcpp_abort_debug_function(__libcpp_debug_info const& info) { - std::fprintf(stderr, "%s\n", info.what().c_str()); - std::abort(); +_LIBCPP_NORETURN _LIBCPP_EXPORTED_FROM_ABI void __libcpp_abort_debug_function(__libcpp_debug_info const& info) { + std::fprintf(stderr, "%s\n", info.what().c_str()); + std::abort(); } +typedef void (*__libcpp_debug_function_type)(__libcpp_debug_info const&); + +_LIBCPP_EXPORTED_FROM_ABI constinit __libcpp_debug_function_type __libcpp_debug_function = __libcpp_abort_debug_function; +_LIBCPP_EXPORTED_FROM_ABI bool __libcpp_set_debug_function(__libcpp_debug_function_type __func) { __libcpp_debug_function = __func; return true; diff --git a/libcxx/test/libcxx/assertions/assertion_handler.abort.pass.cpp b/libcxx/test/libcxx/assertions/assertion_handler.abort.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/assertions/assertion_handler.abort.pass.cpp @@ -0,0 +1,27 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// Test that the default assertion handler aborts the program. + +// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_ASSERTIONS=1 + +#include <__assert> +#include +#include + +void signal_handler(int signal) { + if (signal == SIGABRT) + std::_Exit(EXIT_SUCCESS); + std::_Exit(EXIT_FAILURE); +} + +int main(int, char**) { + if (std::signal(SIGABRT, signal_handler) != SIG_ERR) + _LIBCPP_ASSERT(false, "foo"); + return EXIT_FAILURE; +} diff --git a/libcxx/test/libcxx/assertions/assertion_handler.no-abort.pass.cpp b/libcxx/test/libcxx/assertions/assertion_handler.no-abort.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/assertions/assertion_handler.no-abort.pass.cpp @@ -0,0 +1,23 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// Test that _LIBCPP_ASSERT doesn't do anything when assertions are disabled. + +// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_ASSERTIONS=0 + +#include <__assert> +#include + +bool executed_condition = false; +bool f() { executed_condition = true; return false; } + +int main(int, char**) { + _LIBCPP_ASSERT(f(), "message"); // should not execute anything + assert(!executed_condition); // really make sure we did not execute anything at all + return 0; +} diff --git a/libcxx/test/libcxx/assertions/assertion_handler.pass.cpp b/libcxx/test/libcxx/assertions/assertion_handler.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/assertions/assertion_handler.pass.cpp @@ -0,0 +1,25 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// Test that we can set a custom assertion handler. + +// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_ASSERTIONS=1 +#include + +bool handler_called = false; +void std::__libcpp_assertion_handler(char const*, int, char const*, char const*) { + handler_called = true; +} + +#include + +int main(int, char**) { + _LIBCPP_ASSERT(false, "message"); + assert(handler_called); + return 0; +} diff --git a/libcxx/test/libcxx/debug/check_assertion_test.pass.cpp b/libcxx/test/libcxx/debug/check_assertion_test.pass.cpp --- a/libcxx/test/libcxx/debug/check_assertion_test.pass.cpp +++ b/libcxx/test/libcxx/debug/check_assertion_test.pass.cpp @@ -16,7 +16,7 @@ #include "test_macros.h" template -inline bool TestDeathTest(const char* stmt, Func&& func, DeathTest::ResultKind ExpectResult, DebugInfoMatcher Matcher = AnyMatcher) { +inline bool TestDeathTest(const char* stmt, Func&& func, DeathTest::ResultKind ExpectResult, AssertionInfoMatcher Matcher = AnyMatcher) { DeathTest DT(Matcher); DeathTest::ResultKind RK = DT.Run(func); auto OnFailure = [&](std::string msg) { @@ -42,7 +42,7 @@ } void test_no_match_found() { - DebugInfoMatcher ExpectMatch("my message"); + AssertionInfoMatcher ExpectMatch("my message"); TEST_DEATH_TEST_MATCHES(DeathTest::RK_MatchFailure, ExpectMatch, my_libcpp_assert()); } diff --git a/libcxx/test/libcxx/debug/containers/string.pass.cpp b/libcxx/test/libcxx/debug/containers/string.pass.cpp --- a/libcxx/test/libcxx/debug/containers/string.pass.cpp +++ b/libcxx/test/libcxx/debug/containers/string.pass.cpp @@ -82,7 +82,7 @@ EXPECT_DEATH( C1.erase(it1) ); C1.erase(C1.begin(), C1.end()); assert(C1.size() == 0); - EXPECT_DEATH_MATCHES(DebugInfoMatcher("string::pop_back(): string is already empty"), C1.pop_back() ); + TEST_LIBCPP_ASSERT_FAILURE(C1.pop_back(), "string::pop_back(): string is already empty"); } }; diff --git a/libcxx/test/libcxx/debug/register_debug_handler.pass.cpp b/libcxx/test/libcxx/debug/register_debug_handler.pass.cpp deleted file mode 100644 --- a/libcxx/test/libcxx/debug/register_debug_handler.pass.cpp +++ /dev/null @@ -1,30 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_DEBUG=1 -// UNSUPPORTED: libcxx-no-debug-mode - -#include -#include -#include -#include <__debug> -#include - -#include "test_macros.h" - -void my_debug_function(std::__libcpp_debug_info const& info) { - assert(info.__msg_ == std::string("foo")); - std::exit(0); -} - -int main(int, char**) -{ - std::__libcpp_set_debug_function(&my_debug_function); - _LIBCPP_ASSERT(false, "foo"); - return 1; -} diff --git a/libcxx/test/support/check_assertion.h b/libcxx/test/support/check_assertion.h --- a/libcxx/test/support/check_assertion.h +++ b/libcxx/test/support/check_assertion.h @@ -9,16 +9,6 @@ #ifndef TEST_SUPPORT_CHECK_ASSERTION_H #define TEST_SUPPORT_CHECK_ASSERTION_H -#ifndef _LIBCPP_DEBUG -#error _LIBCPP_DEBUG must be defined before including this header -#endif - -#include -#ifndef _LIBCPP_VERSION -#error "This header may only be used for libc++ tests" -#endif - -#include <__debug> #include #include #include @@ -33,28 +23,31 @@ #include "test_macros.h" #include "test_allocator.h" +#ifndef _LIBCPP_VERSION +# error "This header may only be used for libc++ tests" +#endif + #if TEST_STD_VER < 11 # error "C++11 or greater is required to use this header" #endif -struct DebugInfoMatcher { +struct AssertionInfoMatcher { static const int any_line = -1; static constexpr const char* any_file = "*"; static constexpr const char* any_msg = "*"; - constexpr DebugInfoMatcher() : is_empty_(true), msg_(any_msg, __builtin_strlen(any_msg)), file_(any_file, __builtin_strlen(any_file)), line_(any_line) { } - constexpr DebugInfoMatcher(const char* msg, const char* file = any_file, int line = any_line) + constexpr AssertionInfoMatcher() : is_empty_(true), msg_(any_msg, __builtin_strlen(any_msg)), file_(any_file, __builtin_strlen(any_file)), line_(any_line) { } + constexpr AssertionInfoMatcher(const char* msg, const char* file = any_file, int line = any_line) : is_empty_(false), msg_(msg, __builtin_strlen(msg)), file_(file, __builtin_strlen(file)), line_(line) {} - bool Matches(std::__libcpp_debug_info const& got) const { + bool Matches(char const* file, int line, char const* message) const { assert(!empty() && "empty matcher"); - if (CheckLineMatches(got.__line_) && CheckFileMatches(got.__file_) && - CheckMessageMatches(got.__msg_)) + if (CheckLineMatches(line) && CheckFileMatches(file) && CheckMessageMatches(message)) return true; // Write to stdout because that's the file descriptor captured by the parent // process. - std::printf("Failed to match debug info!\n%s\nVS\n%s\n", ToString().data(), got.what().data()); + std::printf("Failed to match debug info!\n%s\nVS\n%s:%d (%s)\n", ToString().data(), file, line, message); return false; } @@ -108,10 +101,10 @@ int line_; }; -static constexpr DebugInfoMatcher AnyMatcher(DebugInfoMatcher::any_msg); +static constexpr AssertionInfoMatcher AnyMatcher(AssertionInfoMatcher::any_msg); -inline DebugInfoMatcher& GlobalMatcher() { - static DebugInfoMatcher GMatch; +inline AssertionInfoMatcher& GlobalMatcher() { + static AssertionInfoMatcher GMatch; return GMatch; } @@ -136,15 +129,7 @@ return val >= RK_DidNotDie && val <= RK_Unknown; } - TEST_NORETURN static void DeathTestDebugHandler(std::__libcpp_debug_info const& info) { - assert(!GlobalMatcher().empty()); - if (GlobalMatcher().Matches(info)) { - std::exit(RK_MatchFound); - } - std::exit(RK_MatchFailure); - } - - DeathTest(DebugInfoMatcher const& Matcher) : matcher_(Matcher) {} + DeathTest(AssertionInfoMatcher const& Matcher) : matcher_(Matcher) {} template ResultKind Run(Func&& f) { @@ -180,7 +165,6 @@ DupFD(GetStdErrWriteFD(), STDERR_FILENO); GlobalMatcher() = matcher_; - std::__libcpp_set_debug_function(&DeathTestDebugHandler); f(); std::exit(RK_DidNotDie); } @@ -242,7 +226,7 @@ return stderr_pipe_fd_[1]; } private: - DebugInfoMatcher matcher_; + AssertionInfoMatcher matcher_; pid_t child_pid_ = -1; int exit_code_ = -1; int stdout_pipe_fd_[2]; @@ -251,8 +235,16 @@ std::string stderr_from_child_; }; +void std::__libcpp_assertion_handler(char const* file, int line, char const* /*expression*/, char const* message) { + assert(!GlobalMatcher().empty()); + if (GlobalMatcher().Matches(file, line, message)) { + std::exit(DeathTest::RK_MatchFound); + } + std::exit(DeathTest::RK_MatchFailure); +} + template -inline bool ExpectDeath(const char* stmt, Func&& func, DebugInfoMatcher Matcher) { +inline bool ExpectDeath(const char* stmt, Func&& func, AssertionInfoMatcher Matcher) { DeathTest DT(Matcher); DeathTest::ResultKind RK = DT.Run(func); auto OnFailure = [&](const char* msg) { @@ -293,6 +285,6 @@ #define EXPECT_DEATH_MATCHES(Matcher, ...) assert((ExpectDeath(#__VA_ARGS__, [&]() { __VA_ARGS__; }, Matcher))) -#define TEST_LIBCPP_ASSERT_FAILURE(expr, message) assert((ExpectDeath(#expr, [&]() { (void)(expr); }, DebugInfoMatcher(message)))) +#define TEST_LIBCPP_ASSERT_FAILURE(expr, message) assert((ExpectDeath(#expr, [&]() { (void)(expr); }, AssertionInfoMatcher(message)))) #endif // TEST_SUPPORT_CHECK_ASSERTION_H diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py --- a/libcxx/utils/generate_feature_test_macro_components.py +++ b/libcxx/utils/generate_feature_test_macro_components.py @@ -937,6 +937,7 @@ */ +#include <__assert> #include <__config> #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) diff --git a/libcxx/utils/libcxx/test/params.py b/libcxx/utils/libcxx/test/params.py --- a/libcxx/utils/libcxx/test/params.py +++ b/libcxx/utils/libcxx/test/params.py @@ -183,6 +183,11 @@ AddFeature('libcxx-no-debug-mode') ]), + Parameter(name='enable_assertions', choices=[True, False], type=bool, default=False, + help="Whether to enable assertions when compiling the test suite. This is only meaningful when " + "running the tests against libc++.", + actions=lambda assertions: [AddCompileFlag('-D_LIBCPP_ENABLE_ASSERTIONS=1')] if assertions else []), + Parameter(name='additional_features', type=list, default=[], help="A comma-delimited list of additional features that will be enabled when running the tests. " "This should be used sparingly since specifying ad-hoc features manually is error-prone and "