diff --git a/libcxx/CMakeLists.txt b/libcxx/CMakeLists.txt index 75cb63222da35..a4046da847dd8 100644 --- a/libcxx/CMakeLists.txt +++ b/libcxx/CMakeLists.txt @@ -69,6 +69,13 @@ if (NOT "${LIBCXX_HARDENING_MODE}" IN_LIST LIBCXX_SUPPORTED_HARDENING_MODES) message(FATAL_ERROR "Unsupported hardening mode: '${LIBCXX_HARDENING_MODE}'. Supported values are ${LIBCXX_SUPPORTED_HARDENING_MODES}.") endif() +set(LIBCXX_ASSERTION_HANDLER_FILE + "${CMAKE_CURRENT_SOURCE_DIR}/vendor/llvm/default_assertion_handler.in" + CACHE STRING + "Specify the path to a header that contains a custom implementation of the + assertion handler that gets invoked when a hardening assertion fails. If + provided, this header will be included by the library, replacing the + default assertion handler.") option(LIBCXX_ENABLE_RANDOM_DEVICE "Whether to include support for std::random_device in the library. Disabling this can be useful when building the library for platforms that don't have diff --git a/libcxx/docs/BuildingLibcxx.rst b/libcxx/docs/BuildingLibcxx.rst index 0b4d420528651..dedf0a516bbb7 100644 --- a/libcxx/docs/BuildingLibcxx.rst +++ b/libcxx/docs/BuildingLibcxx.rst @@ -409,6 +409,15 @@ libc++ Feature Options Use the specified GCC toolchain and standard library when building the native stdlib benchmark tests. +.. option:: LIBCXX_ASSERTION_HANDLER_FILE:PATH + + **Default**:: ``"${CMAKE_CURRENT_SOURCE_DIR}/vendor/llvm/default_assertion_handler.in"`` + + Specify the path to a header that contains a custom implementation of the + assertion handler that gets invoked when a hardening assertion fails. If + provided, this header will be included by the library, replacing the + default assertion handler. + libc++ ABI Feature Options -------------------------- @@ -473,6 +482,43 @@ LLVM-specific options others. +.. _assertion-handler: + +Overriding the default assertion handler +========================================== + +When the library wants to terminate due to an unforeseen condition (such as +a hardening assertion failure), the program is aborted through a special verbose +termination function. The library provides a default function that prints an +error message and calls ``std::abort()``. Note that this function 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. On older +platforms, the program will terminate in an unspecified unsuccessful manner, but +the quality of diagnostics won't be great. + +However, vendors can also override that mechanism at CMake configuration time. +When a hardening assertion fails, the library invokes the +``_LIBCPP_ASSERTION_HANDLER`` macro. A vendor may provide a header that contains +a custom definition of this macro and specify the path to the header via the +``LIBCXX_ASSERTION_HANDLER_FILE`` CMake variable. If provided, this header will +be included by the library and replace the default implementation. The header +must not include any standard library headers (directly or transitively) because +doing so will almost always create a circular dependency. The +``_LIBCPP_ASSERTION_HANDLER(message)`` macro takes a single parameter that +contains an error message explaining the hardening failure and some details +about the source location that triggered it. + +When a hardening assertion fails, it means that the program is about to invoke +library undefined behavior. For this reason, the custom assertion handler is +generally expected to terminate the program. If a custom assertion handler +decides to avoid doing so (e.g. it chooses to log and continue instead), it does +so at its own risk -- this approach should only be used in non-production builds +and with an understanding of potential consequences. Furthermore, the custom +assertion handler should not throw any exceptions as it may be invoked from +standard library functions that are marked ``noexcept`` (so throwing will result +in ``std::terminate`` being called). + + Using Alternate ABI libraries ============================= diff --git a/libcxx/docs/ReleaseNotes/18.rst b/libcxx/docs/ReleaseNotes/18.rst index 882f53b8d9f83..da190aa8d3f64 100644 --- a/libcxx/docs/ReleaseNotes/18.rst +++ b/libcxx/docs/ReleaseNotes/18.rst @@ -107,6 +107,10 @@ Deprecations and Removals macro is provided to restore the previous behavior, and it will be supported in the LLVM 18 release only. In LLVM 19 and beyond, ``_LIBCPP_ENABLE_NARROWING_CONVERSIONS_IN_VARIANT`` will not be honored anymore. +- The only supported way to customize the assertion handler that gets invoked when a hardening assertion fails + is now by setting the ``LIBCXX_ASSERTION_HANDLER_FILE`` CMake variable and providing a custom header. See + the documentation on overriding the default assertion handler for details. + - The ``_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED`` macro is not honored anymore in LLVM 18. Please see the updated documentation about the hardening modes in libc++ and in particular the ``_LIBCPP_VERBOSE_ABORT`` macro for details. diff --git a/libcxx/docs/UsingLibcxx.rst b/libcxx/docs/UsingLibcxx.rst index e1bbf39b9634a..917f86b9aef97 100644 --- a/libcxx/docs/UsingLibcxx.rst +++ b/libcxx/docs/UsingLibcxx.rst @@ -146,70 +146,6 @@ IWYU, you should run the tool like so: If you would prefer to not use that flag, then you can replace ``/path/to/include-what-you-use/share/libcxx.imp`` file with the libc++-provided ``libcxx.imp`` file. -.. _termination-handler: - -Overriding the default termination handler -========================================== - -When the library wants to terminate due to an unforeseen condition (such as a hardening assertion -failure), the program is aborted through a special verbose termination function. The library provides -a default function that prints an error message and calls ``std::abort()``. Note that this function 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. On older platforms, the program will terminate in an -unspecified unsuccessful manner, but the quality of diagnostics won't be great. - -However, users can also override that mechanism at two different levels. First, the mechanism can be -overridden at compile time by defining the ``_LIBCPP_VERBOSE_ABORT(format, args...)`` variadic macro. -When that macro is defined, it will be called with a format string as the first argument, followed by -a series of arguments to format using printf-style formatting. Compile-time customization may be -useful to get precise control over code generation, however it is also inconvenient to use in -some cases. Indeed, compile-time customization of the verbose termination function requires that all -translation units be compiled with a consistent definition for ``_LIBCPP_VERBOSE_ABORT`` to avoid ODR -violations, which can add complexity in the build system of users. - -Otherwise, if compile-time customization is not necessary, link-time customization of the handler is also -possible, similarly to how replacing ``operator new`` works. This mechanism trades off fine-grained control -over the call site where the termination is initiated in exchange for better ergonomics. Link-time -customization is done by simply defining the following function in exactly one translation unit of your -program: - -.. code-block:: cpp - - void __libcpp_verbose_abort(char const* format, ...) - -This mechanism is similar to how one can replace the default definition of ``operator new`` -and ``operator delete``. For example: - -.. code-block:: cpp - - // In HelloWorldHandler.cpp - #include // must include any libc++ header before defining the function (C compatibility headers excluded) - - void std::__libcpp_verbose_abort(char const* format, ...) { - std::va_list list; - va_start(list, format); - std::vfprintf(stderr, format, list); - va_end(list); - - std::abort(); - } - - // In HelloWorld.cpp - #include - - int main() { - std::vector v; - int& x = v[0]; // Your termination function will be called here if hardening is enabled. - } - -Also note that the verbose termination function should never return. Since assertions in libc++ -catch undefined behavior, your code will proceed with undefined behavior if your function is called -and does return. - -Furthermore, exceptions should not be thrown from the function. Indeed, many functions in the -library are ``noexcept``, and any exception thrown from the termination function will result -in ``std::terminate`` being called. - Libc++ Configuration Macros =========================== diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt index 0fe3ab44d2466..9fdf978a89d7e 100644 --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -1020,9 +1020,11 @@ endforeach() configure_file("__config_site.in" "${LIBCXX_GENERATED_INCLUDE_TARGET_DIR}/__config_site" @ONLY) configure_file("module.modulemap.in" "${LIBCXX_GENERATED_INCLUDE_DIR}/module.modulemap" @ONLY) +configure_file("${LIBCXX_ASSERTION_HANDLER_FILE}" "${LIBCXX_GENERATED_INCLUDE_DIR}/__assertion_handler" COPYONLY) set(_all_includes "${LIBCXX_GENERATED_INCLUDE_TARGET_DIR}/__config_site" - "${LIBCXX_GENERATED_INCLUDE_DIR}/module.modulemap") + "${LIBCXX_GENERATED_INCLUDE_DIR}/module.modulemap" + "${LIBCXX_GENERATED_INCLUDE_DIR}/__assertion_handler") foreach(f ${files}) set(src "${CMAKE_CURRENT_SOURCE_DIR}/${f}") set(dst "${LIBCXX_GENERATED_INCLUDE_DIR}/${f}") @@ -1059,6 +1061,12 @@ if (LIBCXX_INSTALL_HEADERS) PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT cxx-headers) + # Install the generated __assertion_handler file to the generic include dir. + install(FILES "${LIBCXX_GENERATED_INCLUDE_DIR}/__assertion_handler" + DESTINATION "${LIBCXX_INSTALL_INCLUDE_DIR}" + PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ + COMPONENT cxx-headers) + # Install the generated modulemap file to the generic include dir. install(FILES "${LIBCXX_GENERATED_INCLUDE_DIR}/module.modulemap" DESTINATION "${LIBCXX_INSTALL_INCLUDE_DIR}" diff --git a/libcxx/include/__assert b/libcxx/include/__assert index d4af7e6c7192a..eb862b5369b25 100644 --- a/libcxx/include/__assert +++ b/libcxx/include/__assert @@ -10,8 +10,8 @@ #ifndef _LIBCPP___ASSERT #define _LIBCPP___ASSERT +#include <__assertion_handler> // Note: this include is generated by CMake and is potentially vendor-provided. #include <__config> -#include <__verbose_abort> #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) # pragma GCC system_header @@ -20,8 +20,8 @@ #define _LIBCPP_ASSERT(expression, message) \ (__builtin_expect(static_cast(expression), 1) \ ? (void)0 \ - : _LIBCPP_VERBOSE_ABORT( \ - "%s:%d: assertion %s failed: %s\n", __builtin_FILE(), __builtin_LINE(), #expression, message)) + : _LIBCPP_ASSERTION_HANDLER(__FILE__ ":" _LIBCPP_TOSTRING(__LINE__) ": assertion " _LIBCPP_TOSTRING( \ + expression) " failed: " message "\n")) // TODO: __builtin_assume can currently inhibit optimizations. Until this has been fixed and we can add // assumptions without a clear optimization intent, disable that to avoid worsening the code generation. diff --git a/libcxx/include/__memory/assume_aligned.h b/libcxx/include/__memory/assume_aligned.h index c66fb49ebb3c0..aeb6de77f2a57 100644 --- a/libcxx/include/__memory/assume_aligned.h +++ b/libcxx/include/__memory/assume_aligned.h @@ -29,7 +29,15 @@ _LIBCPP_NODISCARD _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Tp* __ass if (__libcpp_is_constant_evaluated()) { return __ptr; } else { - _LIBCPP_ASSERT_UNCATEGORIZED(reinterpret_cast(__ptr) % _Np == 0, "Alignment assumption is violated"); + { + // Note: the `%` symbol is used to introduce a conversion specifier in printf format syntax and thus cannot appear + // in an expression passed to a hardening assertion (because that expression might end up being passed to + // a printf-style formatting function). As a workaround, use an intermediate variable (lambdas aren't supported in + // C++03). + bool __is_aligned = reinterpret_cast(__ptr) % _Np == 0; + (void)__is_aligned; // Prevent "maybe unused" warnings in modes which don't enable the assertion. + _LIBCPP_ASSERT_UNCATEGORIZED(__is_aligned, "Alignment assumption is violated"); + } return static_cast<_Tp*>(__builtin_assume_aligned(__ptr, _Np)); } } diff --git a/libcxx/src/include/to_chars_floating_point.h b/libcxx/src/include/to_chars_floating_point.h index e4715d10d97da..40179fc616c97 100644 --- a/libcxx/src/include/to_chars_floating_point.h +++ b/libcxx/src/include/to_chars_floating_point.h @@ -289,7 +289,15 @@ to_chars_result _Floating_to_chars_hex_precision( for (;;) { _LIBCPP_ASSERT_INTERNAL(_Number_of_bits_remaining >= 4, ""); - _LIBCPP_ASSERT_INTERNAL(_Number_of_bits_remaining % 4 == 0, ""); + { + // Note: the `%` symbol is used to introduce a conversion specifier in printf format syntax and thus + // cannot appear in an expression passed to a hardening assertion (because that expression might end up + // being passed to a printf-style formatting function). As a workaround, use an intermediate variable + // (lambdas aren't supported in C++03). + bool __is_aligned = _Number_of_bits_remaining % 4 == 0; + (void)__is_aligned; // Prevent "maybe unused" warnings in modes which don't enable the assertion. + _LIBCPP_ASSERT_INTERNAL(__is_aligned, ""); + } _Number_of_bits_remaining -= 4; const uint32_t _Nibble = static_cast(_Adjusted_mantissa >> _Number_of_bits_remaining); @@ -416,7 +424,15 @@ to_chars_result _Floating_to_chars_hex_shortest( // _Number_of_bits_remaining. do { _LIBCPP_ASSERT_INTERNAL(_Number_of_bits_remaining >= 4, ""); - _LIBCPP_ASSERT_INTERNAL(_Number_of_bits_remaining % 4 == 0, ""); + { + // Note: the `%` symbol is used to introduce a conversion specifier in printf format syntax and thus + // cannot appear in an expression passed to a hardening assertion (because that expression might end up + // being passed to a printf-style formatting function). As a workaround, use an intermediate variable + // (lambdas aren't supported in C++03). + bool __is_aligned = _Number_of_bits_remaining % 4 == 0; + (void)__is_aligned; // Prevent "maybe unused" warnings in modes which don't enable the assertion. + _LIBCPP_ASSERT_INTERNAL(__is_aligned, ""); + } _Number_of_bits_remaining -= 4; const uint32_t _Nibble = static_cast(_Adjusted_mantissa >> _Number_of_bits_remaining); diff --git a/libcxx/src/memory_resource.cpp b/libcxx/src/memory_resource.cpp index 42c366893f736..adc4747dc0135 100644 --- a/libcxx/src/memory_resource.cpp +++ b/libcxx/src/memory_resource.cpp @@ -230,7 +230,15 @@ class unsynchronized_pool_resource::__fixed_pool { } void* __allocate_in_new_chunk(memory_resource* upstream, size_t block_size, size_t chunk_size) { - _LIBCPP_ASSERT_INTERNAL(chunk_size % block_size == 0, ""); + { + // Note: the `%` symbol is used to introduce a conversion specifier in printf format syntax and thus cannot appear + // in an expression passed to a hardening assertion (because that expression might end up being passed to + // a printf-style formatting function). As a workaround, use an intermediate variable (lambdas aren't supported in + // C++03). + bool __is_aligned = chunk_size % block_size == 0; + (void)__is_aligned; // Prevent "maybe unused" warnings in modes which don't enable the assertion. + _LIBCPP_ASSERT_INTERNAL(__is_aligned, ""); + } static_assert(__default_alignment >= alignof(std::max_align_t), ""); static_assert(__default_alignment >= alignof(__chunk_footer), ""); static_assert(__default_alignment >= alignof(__vacancy_header), ""); diff --git a/libcxx/test/libcxx/assertions/modes/enabling_assertions_enables_extensive_mode.pass.cpp b/libcxx/test/libcxx/assertions/modes/enabling_assertions_enables_extensive_mode.pass.cpp index be08159d561bb..a40ae84fa8e85 100644 --- a/libcxx/test/libcxx/assertions/modes/enabling_assertions_enables_extensive_mode.pass.cpp +++ b/libcxx/test/libcxx/assertions/modes/enabling_assertions_enables_extensive_mode.pass.cpp @@ -10,8 +10,8 @@ // This test ensures that enabling assertions with the legacy `_LIBCPP_ENABLE_ASSERTIONS` now enables the extensive // hardening mode. -// `check_assertion.h` is only available starting from C++11 and requires Unix headers. -// UNSUPPORTED: c++03, !has-unix-headers +// `check_assertion.h` is only available starting from C++11 and requires Unix headers and regex support. +// UNSUPPORTED: c++03, !has-unix-headers, no-localization // The ability to set a custom abort message is required to compare the assertion message. // XFAIL: availability-verbose_abort-missing // Note that GCC doesn't support `-Wno-macro-redefined`. diff --git a/libcxx/test/libcxx/assertions/modes/override_with_debug_mode.pass.cpp b/libcxx/test/libcxx/assertions/modes/override_with_debug_mode.pass.cpp index 665babf59c259..f49ad490780c3 100644 --- a/libcxx/test/libcxx/assertions/modes/override_with_debug_mode.pass.cpp +++ b/libcxx/test/libcxx/assertions/modes/override_with_debug_mode.pass.cpp @@ -8,10 +8,8 @@ // This test ensures that we can override any hardening mode with the debug mode on a per-TU basis. -// `check_assertion.h` is only available starting from C++11. -// UNSUPPORTED: c++03 -// `check_assertion.h` requires Unix headers. -// REQUIRES: has-unix-headers +// `check_assertion.h` is only available starting from C++11 and requires Unix headers and regex support. +// UNSUPPORTED: c++03, !has-unix-headers, no-localization // The ability to set a custom abort message is required to compare the assertion message. // XFAIL: availability-verbose_abort-missing // ADDITIONAL_COMPILE_FLAGS: -U_LIBCPP_HARDENING_MODE -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG diff --git a/libcxx/test/libcxx/assertions/modes/override_with_extensive_mode.pass.cpp b/libcxx/test/libcxx/assertions/modes/override_with_extensive_mode.pass.cpp index e2b0ea7208118..f5323c671c9b1 100644 --- a/libcxx/test/libcxx/assertions/modes/override_with_extensive_mode.pass.cpp +++ b/libcxx/test/libcxx/assertions/modes/override_with_extensive_mode.pass.cpp @@ -8,10 +8,8 @@ // This test ensures that we can override any hardening mode with the extensive hardening mode on a per-TU basis. -// `check_assertion.h` is only available starting from C++11. -// UNSUPPORTED: c++03 -// `check_assertion.h` requires Unix headers. -// REQUIRES: has-unix-headers +// `check_assertion.h` is only available starting from C++11 and requires Unix headers and regex support. +// UNSUPPORTED: c++03, !has-unix-headers, no-localization // The ability to set a custom abort message is required to compare the assertion message. // XFAIL: availability-verbose_abort-missing // ADDITIONAL_COMPILE_FLAGS: -U_LIBCPP_HARDENING_MODE -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE diff --git a/libcxx/test/libcxx/assertions/modes/override_with_fast_mode.pass.cpp b/libcxx/test/libcxx/assertions/modes/override_with_fast_mode.pass.cpp index b38afed17e567..5ee22cc45f3bf 100644 --- a/libcxx/test/libcxx/assertions/modes/override_with_fast_mode.pass.cpp +++ b/libcxx/test/libcxx/assertions/modes/override_with_fast_mode.pass.cpp @@ -8,10 +8,8 @@ // This test ensures that we can override any hardening mode with the fast mode on a per-TU basis. -// `check_assertion.h` is only available starting from C++11. -// UNSUPPORTED: c++03 -// `check_assertion.h` requires Unix headers. -// REQUIRES: has-unix-headers +// `check_assertion.h` is only available starting from C++11 and requires Unix headers and regex support. +// UNSUPPORTED: c++03, !has-unix-headers, no-localization // The ability to set a custom abort message is required to compare the assertion message. // XFAIL: availability-verbose_abort-missing // ADDITIONAL_COMPILE_FLAGS: -U_LIBCPP_HARDENING_MODE -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST diff --git a/libcxx/test/libcxx/assertions/modes/override_with_unchecked_mode.pass.cpp b/libcxx/test/libcxx/assertions/modes/override_with_unchecked_mode.pass.cpp index 65707a9863d07..8ea8b731ec4d3 100644 --- a/libcxx/test/libcxx/assertions/modes/override_with_unchecked_mode.pass.cpp +++ b/libcxx/test/libcxx/assertions/modes/override_with_unchecked_mode.pass.cpp @@ -8,10 +8,8 @@ // This test ensures that we can override any hardening mode with the unchecked mode on a per-TU basis. -// `check_assertion.h` is only available starting from C++11. -// UNSUPPORTED: c++03 -// `check_assertion.h` requires Unix headers. -// REQUIRES: has-unix-headers +// `check_assertion.h` is only available starting from C++11 and requires Unix headers and regex support. +// UNSUPPORTED: c++03, !has-unix-headers, no-localization // ADDITIONAL_COMPILE_FLAGS: -U_LIBCPP_HARDENING_MODE -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_NONE #include diff --git a/libcxx/test/libcxx/containers/sequences/deque/asan_caterpillar.pass.cpp b/libcxx/test/libcxx/containers/sequences/deque/asan_caterpillar.pass.cpp index b99b2737bce02..d3c6e0493ee28 100644 --- a/libcxx/test/libcxx/containers/sequences/deque/asan_caterpillar.pass.cpp +++ b/libcxx/test/libcxx/containers/sequences/deque/asan_caterpillar.pass.cpp @@ -11,8 +11,8 @@ // Regression test to error in deque::__annotate_from_to in deque, // with origin in deque::__add_back_capacity. -// REQUIRES: has-unix-headers -// UNSUPPORTED: c++03 +// `check_assertion.h` is only available starting from C++11 and requires Unix headers and regex support. +// UNSUPPORTED: c++03, !has-unix-headers, no-localization #include #include diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.fill/pstl.exception_handling.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.fill/pstl.exception_handling.pass.cpp index f7279655cb129..dda642be85bc0 100644 --- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.fill/pstl.exception_handling.pass.cpp +++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.fill/pstl.exception_handling.pass.cpp @@ -8,7 +8,8 @@ // UNSUPPORTED: c++03, c++11, c++14 // UNSUPPORTED: no-exceptions -// REQUIRES: has-unix-headers +// `check_assertion.h` requires Unix headers and regex support. +// UNSUPPORTED: !has-unix-headers, no-localization // UNSUPPORTED: libcpp-has-no-incomplete-pstl diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/pstl.exception_handling.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/pstl.exception_handling.pass.cpp index ffe4f1866ebe6..bb8ab42172226 100644 --- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/pstl.exception_handling.pass.cpp +++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/pstl.exception_handling.pass.cpp @@ -8,7 +8,8 @@ // UNSUPPORTED: c++03, c++11, c++14 // UNSUPPORTED: no-exceptions -// REQUIRES: has-unix-headers +// `check_assertion.h` requires Unix headers and regex support. +// UNSUPPORTED: !has-unix-headers, no-localization // UNSUPPORTED: libcpp-has-no-incomplete-pstl diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.replace/pstl.exception_handling.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.replace/pstl.exception_handling.pass.cpp index 98fde95fb0e44..c02496bef4212 100644 --- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.replace/pstl.exception_handling.pass.cpp +++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.replace/pstl.exception_handling.pass.cpp @@ -8,7 +8,8 @@ // UNSUPPORTED: c++03, c++11, c++14 // UNSUPPORTED: no-exceptions -// REQUIRES: has-unix-headers +// `check_assertion.h` requires Unix headers and regex support. +// UNSUPPORTED: !has-unix-headers, no-localization // UNSUPPORTED: libcpp-has-no-incomplete-pstl diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.rotate/pstl.exception_handling.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.rotate/pstl.exception_handling.pass.cpp index d0b77661334d7..88d177a6e39f4 100644 --- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.rotate/pstl.exception_handling.pass.cpp +++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.rotate/pstl.exception_handling.pass.cpp @@ -8,7 +8,8 @@ // UNSUPPORTED: c++03, c++11, c++14 // UNSUPPORTED: no-exceptions -// REQUIRES: has-unix-headers +// `check_assertion.h` requires Unix headers and regex support. +// UNSUPPORTED: !has-unix-headers, no-localization // UNSUPPORTED: libcpp-has-no-incomplete-pstl diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.transform/pstl.exception_handling.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.transform/pstl.exception_handling.pass.cpp index 6c7149f4048ad..439204060e189 100644 --- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.transform/pstl.exception_handling.pass.cpp +++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.transform/pstl.exception_handling.pass.cpp @@ -8,7 +8,8 @@ // UNSUPPORTED: c++03, c++11, c++14 // UNSUPPORTED: no-exceptions -// REQUIRES: has-unix-headers +// `check_assertion.h` requires Unix headers and regex support. +// UNSUPPORTED: !has-unix-headers, no-localization // UNSUPPORTED: libcpp-has-no-incomplete-pstl diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.all_of/pstl.exception_handling.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.all_of/pstl.exception_handling.pass.cpp index ae5063c5264ea..d1c031bdd97a2 100644 --- a/libcxx/test/std/algorithms/alg.nonmodifying/alg.all_of/pstl.exception_handling.pass.cpp +++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.all_of/pstl.exception_handling.pass.cpp @@ -8,7 +8,8 @@ // UNSUPPORTED: c++03, c++11, c++14 // UNSUPPORTED: no-exceptions -// REQUIRES: has-unix-headers +// `check_assertion.h` requires Unix headers and regex support. +// UNSUPPORTED: !has-unix-headers, no-localization // UNSUPPORTED: libcpp-has-no-incomplete-pstl diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.any_of/pstl.exception_handling.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.any_of/pstl.exception_handling.pass.cpp index 167c4ac4df95e..58fe79b34c008 100644 --- a/libcxx/test/std/algorithms/alg.nonmodifying/alg.any_of/pstl.exception_handling.pass.cpp +++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.any_of/pstl.exception_handling.pass.cpp @@ -8,7 +8,8 @@ // UNSUPPORTED: c++03, c++11, c++14 // UNSUPPORTED: no-exceptions -// REQUIRES: has-unix-headers +// `check_assertion.h` requires Unix headers and regex support. +// UNSUPPORTED: !has-unix-headers, no-localization // UNSUPPORTED: libcpp-has-no-incomplete-pstl diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.equal/pstl.exception_handling.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.equal/pstl.exception_handling.pass.cpp index 4c2f9946165e7..1bcd858f3c02d 100644 --- a/libcxx/test/std/algorithms/alg.nonmodifying/alg.equal/pstl.exception_handling.pass.cpp +++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.equal/pstl.exception_handling.pass.cpp @@ -8,7 +8,8 @@ // UNSUPPORTED: c++03, c++11, c++14 // UNSUPPORTED: no-exceptions -// REQUIRES: has-unix-headers +// `check_assertion.h` requires Unix headers and regex support. +// UNSUPPORTED: !has-unix-headers, no-localization // UNSUPPORTED: libcpp-has-no-incomplete-pstl diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.find/pstl.exception_handling.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.find/pstl.exception_handling.pass.cpp index 06b0810c257b3..b0ee4f8d062ef 100644 --- a/libcxx/test/std/algorithms/alg.nonmodifying/alg.find/pstl.exception_handling.pass.cpp +++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.find/pstl.exception_handling.pass.cpp @@ -8,7 +8,8 @@ // UNSUPPORTED: c++03, c++11, c++14 // UNSUPPORTED: no-exceptions -// REQUIRES: has-unix-headers +// `check_assertion.h` requires Unix headers and regex support. +// UNSUPPORTED: !has-unix-headers, no-localization // UNSUPPORTED: libcpp-has-no-incomplete-pstl diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/pstl.exception_handling.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/pstl.exception_handling.pass.cpp index ab15bdf5d8b99..a63276f1e025d 100644 --- a/libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/pstl.exception_handling.pass.cpp +++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/pstl.exception_handling.pass.cpp @@ -8,7 +8,8 @@ // UNSUPPORTED: c++03, c++11, c++14 // UNSUPPORTED: no-exceptions -// REQUIRES: has-unix-headers +// `check_assertion.h` requires Unix headers and regex support. +// UNSUPPORTED: !has-unix-headers, no-localization // UNSUPPORTED: libcpp-has-no-incomplete-pstl diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.none_of/pstl.exception_handling.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.none_of/pstl.exception_handling.pass.cpp index 9a6d8d636200c..26e6fea5904fe 100644 --- a/libcxx/test/std/algorithms/alg.nonmodifying/alg.none_of/pstl.exception_handling.pass.cpp +++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.none_of/pstl.exception_handling.pass.cpp @@ -8,7 +8,8 @@ // UNSUPPORTED: c++03, c++11, c++14 // UNSUPPORTED: no-exceptions -// REQUIRES: has-unix-headers +// `check_assertion.h` requires Unix headers and regex support. +// UNSUPPORTED: !has-unix-headers, no-localization // UNSUPPORTED: libcpp-has-no-incomplete-pstl diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.merge/pstl.exception_handling.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.merge/pstl.exception_handling.pass.cpp index b56959809829b..b48a5a9fa2b7d 100644 --- a/libcxx/test/std/algorithms/alg.sorting/alg.merge/pstl.exception_handling.pass.cpp +++ b/libcxx/test/std/algorithms/alg.sorting/alg.merge/pstl.exception_handling.pass.cpp @@ -8,7 +8,8 @@ // UNSUPPORTED: c++03, c++11, c++14 // UNSUPPORTED: no-exceptions -// REQUIRES: has-unix-headers +// `check_assertion.h` requires Unix headers and regex support. +// UNSUPPORTED: !has-unix-headers, no-localization // UNSUPPORTED: libcpp-has-no-incomplete-pstl diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/pstl.exception_handling.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/pstl.exception_handling.pass.cpp index 43ee937a8a185..1dc603cfaa554 100644 --- a/libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/pstl.exception_handling.pass.cpp +++ b/libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/pstl.exception_handling.pass.cpp @@ -8,7 +8,8 @@ // UNSUPPORTED: c++03, c++11, c++14 // UNSUPPORTED: no-exceptions -// REQUIRES: has-unix-headers +// `check_assertion.h` requires Unix headers and regex support. +// UNSUPPORTED: !has-unix-headers, no-localization // UNSUPPORTED: libcpp-has-no-incomplete-pstl diff --git a/libcxx/test/std/algorithms/numeric.ops/reduce/pstl.exception_handling.pass.cpp b/libcxx/test/std/algorithms/numeric.ops/reduce/pstl.exception_handling.pass.cpp index 65a112774ff76..d52889b1be147 100644 --- a/libcxx/test/std/algorithms/numeric.ops/reduce/pstl.exception_handling.pass.cpp +++ b/libcxx/test/std/algorithms/numeric.ops/reduce/pstl.exception_handling.pass.cpp @@ -8,7 +8,8 @@ // UNSUPPORTED: c++03, c++11, c++14 // UNSUPPORTED: no-exceptions -// REQUIRES: has-unix-headers +// `check_assertion.h` requires Unix headers and regex support. +// UNSUPPORTED: !has-unix-headers, no-localization // UNSUPPORTED: libcpp-has-no-incomplete-pstl diff --git a/libcxx/test/std/algorithms/numeric.ops/transform.reduce/pstl.exception_handling.pass.cpp b/libcxx/test/std/algorithms/numeric.ops/transform.reduce/pstl.exception_handling.pass.cpp index d2df251d5c8ad..5ac04334f0005 100644 --- a/libcxx/test/std/algorithms/numeric.ops/transform.reduce/pstl.exception_handling.pass.cpp +++ b/libcxx/test/std/algorithms/numeric.ops/transform.reduce/pstl.exception_handling.pass.cpp @@ -8,7 +8,8 @@ // UNSUPPORTED: c++03, c++11, c++14 // UNSUPPORTED: no-exceptions -// REQUIRES: has-unix-headers +// `check_assertion.h` requires Unix headers and regex support. +// UNSUPPORTED: !has-unix-headers, no-localization // UNSUPPORTED: libcpp-has-no-incomplete-pstl diff --git a/libcxx/test/support/check_assertion.h b/libcxx/test/support/check_assertion.h index 98dd95b11556e..65341969564bd 100644 --- a/libcxx/test/support/check_assertion.h +++ b/libcxx/test/support/check_assertion.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -57,6 +58,10 @@ struct AssertionInfoMatcher { } bool empty() const { return is_empty_; } + bool IsAnyMatcher() const { + return msg_ == any_msg && file_ == any_file && line_ == any_line; + } + private: bool CheckLineMatches(int got_line) const { if (line_ == any_line) @@ -92,6 +97,7 @@ struct AssertionInfoMatcher { // Allow any match return true; } + private: bool is_empty_; std::string_view msg_; @@ -107,9 +113,7 @@ inline AssertionInfoMatcher& GlobalMatcher() { } struct DeathTest { - enum ResultKind { - RK_DidNotDie, RK_MatchFound, RK_MatchFailure, RK_Terminate, RK_SetupFailure, RK_Unknown - }; + enum ResultKind { RK_DidNotDie, RK_MatchFound, RK_MatchFailure, RK_Terminate, RK_SetupFailure, RK_Trap, RK_Unknown }; static const char* ResultKindToString(ResultKind RK) { #define CASE(K) case K: return #K @@ -120,6 +124,7 @@ struct DeathTest { CASE(RK_MatchFound); CASE(RK_Unknown); CASE(RK_Terminate); + CASE(RK_Trap); } return "not a result kind"; } @@ -128,7 +133,7 @@ struct DeathTest { return val >= RK_DidNotDie && val <= RK_Unknown; } - DeathTest(AssertionInfoMatcher const& Matcher) : matcher_(Matcher) {} + DeathTest(AssertionInfoMatcher const& matcher) : matcher_(matcher) {} template ResultKind Run(Func&& f) { @@ -203,6 +208,14 @@ struct DeathTest { return RK_Unknown; return static_cast(exit_code_); } + + if (WIFSIGNALED(status_value)) { + exit_code_ = WTERMSIG(status_value); + if (exit_code_ == SIGILL || exit_code_ == SIGTRAP) { + return RK_Trap; + } + } + return RK_Unknown; } @@ -235,18 +248,23 @@ struct DeathTest { }; #ifdef _LIBCPP_VERSION -void std::__libcpp_verbose_abort(char const* format, ...) { - // Extract information from the error message. This has to stay synchronized with - // how we format assertions in the library. - va_list list; - va_start(list, format); - char const* file = va_arg(list, char const*); - int line = va_arg(list, int); - char const* expression = va_arg(list, char const*); (void)expression; - char const* message = va_arg(list, char const*); - va_end(list); - - if (GlobalMatcher().Matches(file, line, message)) { +void std::__libcpp_verbose_abort(char const* message, ...) { + // Extract information from the error message. This has to stay synchronized with how we format assertions in the + // library. Note that the variadic arguments to `__libcpp_verbose_abort` are empty but maintained for backward + // compatibility reasons. + std::regex message_format("(.*):(\\d+): assertion (.*) failed: (.*)\\n"); + + std::cmatch match_result; + bool has_match = std::regex_match(message, match_result, message_format); + assert(has_match); + assert(match_result.size() == 5); + + std::string file = match_result[1]; + int line = std::stoi(match_result[2]); + // Omitting `expression` in `match_result[3]` + std::string failure_reason = match_result[4]; + + if (GlobalMatcher().Matches(file.c_str(), line, failure_reason.c_str())) { std::exit(DeathTest::RK_MatchFound); } std::exit(DeathTest::RK_MatchFailure); @@ -257,13 +275,20 @@ void std::__libcpp_verbose_abort(char const* format, ...) { std::exit(DeathTest::RK_Terminate); } +enum class DeathCause { + VerboseAbort, + StdTerminate, + HardeningAssertion +}; + template -inline bool ExpectDeath(const char* stmt, Func&& func, AssertionInfoMatcher Matcher) { +inline bool ExpectDeath(DeathCause expected_cause, const char* stmt, Func&& func, AssertionInfoMatcher matcher) { std::set_terminate(terminate_handler); - DeathTest DT(Matcher); + DeathTest DT(matcher); DeathTest::ResultKind RK = DT.Run(func); + auto OnFailure = [&](const char* msg) { - std::fprintf(stderr, "EXPECT_DEATH( %s ) failed! (%s)\n\n", stmt, msg); + std::fprintf(stderr, "Failure: EXPECT_DEATH( %s ) failed!\n(Reason: %s)\n\n", stmt, msg); if (RK != DeathTest::RK_Unknown) { std::fprintf(stderr, "child exit code: %d\n", DT.getChildExitCode()); } @@ -275,34 +300,51 @@ inline bool ExpectDeath(const char* stmt, Func&& func, AssertionInfoMatcher Matc } return false; }; + switch (RK) { case DeathTest::RK_MatchFound: + return true; + case DeathTest::RK_Terminate: return true; + + case DeathTest::RK_Trap: + switch (expected_cause) { + case DeathCause::HardeningAssertion: +#if _LIBCPP_HARDENING_MODE != _LIBCPP_HARDENING_MODE_DEBUG + return true; +#else + return OnFailure("The process has trapped but was expected to invoke verbose abort."); +#endif + case DeathCause::VerboseAbort: + return OnFailure("The process has trapped but was expected to invoke verbose abort."); + case DeathCause::StdTerminate: + return OnFailure("The process has trapped but was expected to call `std::terminate`."); + } + case DeathTest::RK_SetupFailure: return OnFailure("child failed to setup test environment"); case DeathTest::RK_Unknown: - return OnFailure("reason unknown"); + return OnFailure("reason unknown"); case DeathTest::RK_DidNotDie: - return OnFailure("child did not die"); + return OnFailure("child did not die"); case DeathTest::RK_MatchFailure: - return OnFailure("matcher failed"); + return OnFailure("matcher failed"); } + assert(false && "unreachable"); } template -inline bool ExpectDeath(const char* stmt, Func&& func) { - return ExpectDeath(stmt, func, AnyMatcher); +inline bool ExpectDeath(DeathCause expected_cause, const char* stmt, Func&& func) { + return ExpectDeath(expected_cause, stmt, func, AnyMatcher); } -/// Assert that the specified expression throws a libc++ debug exception. -#define EXPECT_DEATH(...) assert((ExpectDeath(#__VA_ARGS__, [&]() { __VA_ARGS__; } ))) - -#define EXPECT_STD_TERMINATE(...) assert(ExpectDeath(#__VA_ARGS__, __VA_ARGS__)) - -#define EXPECT_DEATH_MATCHES(Matcher, ...) assert((ExpectDeath(#__VA_ARGS__, [&]() { __VA_ARGS__; }, Matcher))) +/// Assert that the specified expression aborts. +#define EXPECT_DEATH(...) /* */ assert((ExpectDeath(DeathCause::VerboseAbort, #__VA_ARGS__, [&]() { __VA_ARGS__; } ))) +#define EXPECT_DEATH_MATCHES(matcher, ...) assert((ExpectDeath(DeathCause::VerboseAbort, #__VA_ARGS__, [&]() { __VA_ARGS__; }, matcher))) +#define EXPECT_STD_TERMINATE(...) /* */ assert(ExpectDeath(DeathCause::StdTerminate, #__VA_ARGS__, __VA_ARGS__)) -#define TEST_LIBCPP_ASSERT_FAILURE(expr, message) assert((ExpectDeath(#expr, [&]() { (void)(expr); }, AssertionInfoMatcher(message)))) +#define TEST_LIBCPP_ASSERT_FAILURE(expr, message) assert((ExpectDeath(DeathCause::HardeningAssertion, #expr, [&]() { (void)(expr); }, AssertionInfoMatcher(message)))) #endif // TEST_SUPPORT_CHECK_ASSERTION_H diff --git a/libcxx/utils/libcxx/header_information.py b/libcxx/utils/libcxx/header_information.py index 54e18b5ea533d..326931b0081c0 100644 --- a/libcxx/utils/libcxx/header_information.py +++ b/libcxx/utils/libcxx/header_information.py @@ -168,6 +168,9 @@ def is_modulemap_header(header): if header == "__config_site": return False + if header == "__assertion_handler": + return False + # exclude libc++abi files if header in ["cxxabi.h", "__cxxabi_config.h"]: return False diff --git a/libcxx/vendor/llvm/default_assertion_handler.in b/libcxx/vendor/llvm/default_assertion_handler.in new file mode 100644 index 0000000000000..e82b7052a85d2 --- /dev/null +++ b/libcxx/vendor/llvm/default_assertion_handler.in @@ -0,0 +1,30 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP___ASSERTION_HANDLER +#define _LIBCPP___ASSERTION_HANDLER + +#include <__config> +#include <__verbose_abort> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +#if _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG + +# define _LIBCPP_ASSERTION_HANDLER(message) _LIBCPP_VERBOSE_ABORT(message) + +#else + +# define _LIBCPP_ASSERTION_HANDLER(message) ((void)message, __builtin_trap()) + +#endif // _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG + +#endif // _LIBCPP___ASSERTION_HANDLER