[libc++][hardening] Introduce assertion semantics (#148268)
Assertion semantics closely mimic C++26 Contracts evaluation semantics. This brings our implementation closer in line with C++26 Library Hardening (one particular benefit is that using the `observe` semantic makes adopting hardening easier for projects).
This commit is contained in:
parent
aa0629dabe
commit
7345508c6f
1
.github/workflows/libcxx-build-and-test.yaml
vendored
1
.github/workflows/libcxx-build-and-test.yaml
vendored
@ -128,6 +128,7 @@ jobs:
|
||||
'generic-abi-unstable',
|
||||
'generic-hardening-mode-debug',
|
||||
'generic-hardening-mode-extensive',
|
||||
'generic-hardening-mode-extensive-observe-semantic',
|
||||
'generic-hardening-mode-fast',
|
||||
'generic-hardening-mode-fast-with-abi-breaks',
|
||||
'generic-merged',
|
||||
|
@ -0,0 +1,2 @@
|
||||
set(LIBCXX_HARDENING_MODE "extensive" CACHE STRING "")
|
||||
set(LIBCXX_TEST_PARAMS "assertion_semantic=observe" CACHE STRING "")
|
@ -39,6 +39,8 @@ modes are:
|
||||
|
||||
Enabling hardening has no impact on the ABI.
|
||||
|
||||
.. _notes-for-users:
|
||||
|
||||
Notes for users
|
||||
---------------
|
||||
|
||||
@ -72,6 +74,11 @@ to control the level by passing **one** of the following options to the compiler
|
||||
pre-built components. Most libc++ code is header-based, so a user-provided
|
||||
value for ``_LIBCPP_HARDENING_MODE`` will be mostly respected.
|
||||
|
||||
In some cases, users might want to override the assertion semantic used by the
|
||||
library.
|
||||
This can be done similarly to setting the hardening mode; please refer to the
|
||||
:ref:`relevant section <assertion-semantics>`.
|
||||
|
||||
Notes for vendors
|
||||
-----------------
|
||||
|
||||
@ -260,6 +267,60 @@ output. This is less secure and increases the size of the binary (among other
|
||||
things, it has to store the error message strings) but makes the failure easier
|
||||
to debug. It also allows testing the error messages in our test suite.
|
||||
|
||||
This default behavior can be customized by users via :ref:`assertion semantics
|
||||
<assertion-semantics>`; it can also be completely overridden by vendors by
|
||||
providing a :ref:`custom assertion failure handler
|
||||
<override-assertion-handler>`.
|
||||
|
||||
.. _assertion-semantics:
|
||||
|
||||
Assertion semantics
|
||||
-------------------
|
||||
|
||||
What happens when an assertion fails depends on the assertion semantic being
|
||||
used. Four assertion semantics are available, based on C++26 Contracts
|
||||
evaluation semantics:
|
||||
|
||||
- ``ignore`` evaluates the assertion but has no effect if it fails (note that it
|
||||
differs from the Contracts ``ignore`` semantic which would not evaluate
|
||||
the assertion at all);
|
||||
- ``observe`` logs an error (indicating, if possible on the platform, that the
|
||||
error is fatal) but continues execution;
|
||||
- ``quick-enforce`` terminates the program as fast as possible via a trap
|
||||
instruction. It is the default semantic for the production modes (``fast`` and
|
||||
``extensive``);
|
||||
- ``enforce`` logs an error and then terminates the program. It is the default
|
||||
semantic for the ``debug`` mode.
|
||||
|
||||
Notes:
|
||||
|
||||
- Continuing execution after a hardening check fails results in undefined
|
||||
behavior; the ``observe`` semantic is meant to make adopting hardening easier
|
||||
but should not be used outside of the adoption period;
|
||||
- C++26 wording for Library Hardening precludes a conforming Hardened
|
||||
implementation from using the Contracts ``ignore`` semantic when evaluating
|
||||
hardened preconditions in the Library. Libc++ allows using this semantic for
|
||||
hardened preconditions, but please be aware that using ``ignore`` does not
|
||||
produce a conforming "Hardened" implementation, unlike the other semantics
|
||||
above.
|
||||
|
||||
The default assertion semantics are as follows:
|
||||
|
||||
- ``fast``: ``quick-enforce``;
|
||||
- ``extensive``: ``quick-enforce``;
|
||||
- ``debug``: ``enforce``.
|
||||
|
||||
The default assertion semantics can be overridden by passing **one** of the
|
||||
following options to the compiler:
|
||||
|
||||
- ``-D_LIBCPP_ASSERTION_SEMANTIC=_LIBCPP_ASSERTION_SEMANTIC_IGNORE``
|
||||
- ``-D_LIBCPP_ASSERTION_SEMANTIC=_LIBCPP_ASSERTION_SEMANTIC_OBSERVE``
|
||||
- ``-D_LIBCPP_ASSERTION_SEMANTIC=_LIBCPP_ASSERTION_SEMANTIC_QUICK_ENFORCE``
|
||||
- ``-D_LIBCPP_ASSERTION_SEMANTIC=_LIBCPP_ASSERTION_SEMANTIC_ENFORCE``
|
||||
|
||||
All the :ref:`same notes <notes-for-users>` apply to setting this macro as for
|
||||
setting ``_LIBCPP_HARDENING_MODE``.
|
||||
|
||||
.. _override-assertion-handler:
|
||||
|
||||
Overriding the assertion failure handler
|
||||
|
@ -88,6 +88,11 @@ Improvements and New Features
|
||||
|
||||
- ``ctype::tolower`` and ``ctype::toupper`` have been optimized, resulting in a 2x performance improvement.
|
||||
|
||||
- Hardening now supports assertion semantics that allow customizing how a hardening assertion failure is handled. The
|
||||
four available semantics, modeled on C++26 Contracts, are ``ignore``, ``observe``, ``quick-enforce`` and ``enforce``.
|
||||
The ``observe`` semantic is intended to make it easier to adopt Hardening in production but should not be used outside
|
||||
of this scenario. Please refer to the :ref:`Hardening documentation <hardening>` for details.
|
||||
|
||||
Deprecations and Removals
|
||||
-------------------------
|
||||
|
||||
|
@ -147,6 +147,40 @@ _LIBCPP_HARDENING_MODE_EXTENSIVE, \
|
||||
_LIBCPP_HARDENING_MODE_DEBUG
|
||||
# endif
|
||||
|
||||
// Hardening assertion semantics generally mirror the evaluation semantics of C++26 Contracts:
|
||||
// - `ignore` evaluates the assertion but doesn't do anything if it fails (note that it differs from the Contracts
|
||||
// `ignore` semantic which wouldn't evaluate the assertion at all);
|
||||
// - `observe` logs an error (indicating, if possible, that the error is fatal) and continues execution;
|
||||
// - `quick-enforce` terminates the program as fast as possible (via trapping);
|
||||
// - `enforce` logs an error and then terminates the program.
|
||||
//
|
||||
// Notes:
|
||||
// - Continuing execution after a hardening check fails results in undefined behavior; the `observe` semantic is meant
|
||||
// to make adopting hardening easier but should not be used outside of this scenario;
|
||||
// - C++26 wording for Library Hardening precludes a conforming Hardened implementation from using the Contracts
|
||||
// `ignore` semantic when evaluating hardened preconditions in the Library. Libc++ allows using this semantic for
|
||||
// hardened preconditions, however, be aware that using `ignore` does not produce a conforming "Hardened"
|
||||
// implementation, unlike the other semantics above.
|
||||
// clang-format off
|
||||
# define _LIBCPP_ASSERTION_SEMANTIC_IGNORE (1 << 1)
|
||||
# define _LIBCPP_ASSERTION_SEMANTIC_OBSERVE (1 << 2)
|
||||
# define _LIBCPP_ASSERTION_SEMANTIC_QUICK_ENFORCE (1 << 3)
|
||||
# define _LIBCPP_ASSERTION_SEMANTIC_ENFORCE (1 << 4)
|
||||
// clang-format on
|
||||
|
||||
// Allow users to define an arbitrary assertion semantic; otherwise, use the default mapping from modes to semantics.
|
||||
// The default is for production-capable modes to use `quick-enforce` (i.e., trap) and for the `debug` mode to use
|
||||
// `enforce` (i.e., log and abort).
|
||||
# ifndef _LIBCPP_ASSERTION_SEMANTIC
|
||||
|
||||
# if _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG
|
||||
# define _LIBCPP_ASSERTION_SEMANTIC _LIBCPP_ASSERTION_SEMANTIC_ENFORCE
|
||||
# else
|
||||
# define _LIBCPP_ASSERTION_SEMANTIC _LIBCPP_ASSERTION_SEMANTIC_QUICK_ENFORCE
|
||||
# endif
|
||||
|
||||
# endif // _LIBCPP_ASSERTION_SEMANTIC
|
||||
|
||||
// } HARDENING
|
||||
|
||||
# define _LIBCPP_TOSTRING2(x) #x
|
||||
|
@ -43,17 +43,17 @@ int main(int, char**) {
|
||||
}
|
||||
// mismatch of static extent
|
||||
{
|
||||
TEST_LIBCPP_ASSERT_FAILURE(([] { std::extents<int, D, 5> e1(std::array{1000, 3}); }()),
|
||||
TEST_LIBCPP_ASSERT_FAILURE(([] { [[maybe_unused]] std::extents<int, D, 5> e1(std::array{1000, 3}); }()),
|
||||
"extents construction: mismatch of provided arguments with static extents.");
|
||||
}
|
||||
// value out of range
|
||||
{
|
||||
TEST_LIBCPP_ASSERT_FAILURE(([] { std::extents<signed char, D, 5> e1(std::array{1000, 5}); }()),
|
||||
TEST_LIBCPP_ASSERT_FAILURE(([] { [[maybe_unused]] std::extents<signed char, D, 5> e1(std::array{1000, 5}); }()),
|
||||
"extents ctor: arguments must be representable as index_type and nonnegative");
|
||||
}
|
||||
// negative value
|
||||
{
|
||||
TEST_LIBCPP_ASSERT_FAILURE(([] { std::extents<signed char, D, 5> e1(std::array{-1, 5}); }()),
|
||||
TEST_LIBCPP_ASSERT_FAILURE(([] { [[maybe_unused]] std::extents<signed char, D, 5> e1(std::array{-1, 5}); }()),
|
||||
"extents ctor: arguments must be representable as index_type and nonnegative");
|
||||
}
|
||||
return 0;
|
||||
|
@ -45,17 +45,17 @@ int main(int, char**) {
|
||||
}
|
||||
// mismatch of static extent
|
||||
{
|
||||
TEST_LIBCPP_ASSERT_FAILURE(([] { std::extents<int, D, 5> e1(1000, 3); }()),
|
||||
TEST_LIBCPP_ASSERT_FAILURE(([] { [[maybe_unused]] std::extents<int, D, 5> e1(1000, 3); }()),
|
||||
"extents construction: mismatch of provided arguments with static extents.");
|
||||
}
|
||||
// value out of range
|
||||
{
|
||||
TEST_LIBCPP_ASSERT_FAILURE(([] { std::extents<signed char, D, 5> e1(1000, 5); }()),
|
||||
TEST_LIBCPP_ASSERT_FAILURE(([] { [[maybe_unused]] std::extents<signed char, D, 5> e1(1000, 5); }()),
|
||||
"extents ctor: arguments must be representable as index_type and nonnegative");
|
||||
}
|
||||
// negative value
|
||||
{
|
||||
TEST_LIBCPP_ASSERT_FAILURE(([] { std::extents<signed char, D, 5> e1(-1, 5); }()),
|
||||
TEST_LIBCPP_ASSERT_FAILURE(([] { [[maybe_unused]] std::extents<signed char, D, 5> e1(-1, 5); }()),
|
||||
"extents ctor: arguments must be representable as index_type and nonnegative");
|
||||
}
|
||||
return 0;
|
||||
|
@ -44,7 +44,7 @@ int main(int, char**) {
|
||||
{
|
||||
TEST_LIBCPP_ASSERT_FAILURE(
|
||||
([=] {
|
||||
std::layout_left::mapping<std::extents<signed char, D>> m(
|
||||
[[maybe_unused]] std::layout_left::mapping<std::extents<signed char, D>> m(
|
||||
std::layout_left::mapping<std::extents<int, D>>(std::extents<int, D>(500)));
|
||||
}()),
|
||||
"extents ctor: arguments must be representable as index_type and nonnegative");
|
||||
@ -55,7 +55,7 @@ int main(int, char**) {
|
||||
[[maybe_unused]] std::extents<signed char, D, 5> e(arg_exts);
|
||||
// but the product is not, so we can't use it for layout_left
|
||||
TEST_LIBCPP_ASSERT_FAILURE(
|
||||
([=] { std::layout_left::mapping<std::extents<signed char, D, 5>> m(arg); }()),
|
||||
([=] { [[maybe_unused]] std::layout_left::mapping<std::extents<signed char, D, 5>> m(arg); }()),
|
||||
"layout_left::mapping converting ctor: other.required_span_size() must be representable as index_type.");
|
||||
}
|
||||
return 0;
|
||||
|
@ -31,7 +31,10 @@ int main(int, char**) {
|
||||
{
|
||||
// the extents are representable but the product is not, so we can't use it for layout_left
|
||||
TEST_LIBCPP_ASSERT_FAILURE(
|
||||
([=] { std::layout_left::mapping<std::extents<signed char, D, 5>> m(std::extents<signed char, D, 5>(100)); }()),
|
||||
([=] {
|
||||
[[maybe_unused]] std::layout_left::mapping<std::extents<signed char, D, 5>> m(
|
||||
std::extents<signed char, D, 5>(100));
|
||||
}()),
|
||||
"layout_left::mapping extents ctor: product of extents must be representable as index_type.");
|
||||
}
|
||||
return 0;
|
||||
|
@ -39,14 +39,14 @@ int main(int, char**) {
|
||||
}
|
||||
// mismatch of static extent
|
||||
{
|
||||
TEST_LIBCPP_ASSERT_FAILURE(([=] { std::layout_left::mapping<std::extents<int, 3>> m(arg); }()),
|
||||
TEST_LIBCPP_ASSERT_FAILURE(([=] { [[maybe_unused]] std::layout_left::mapping<std::extents<int, 3>> m(arg); }()),
|
||||
"extents construction: mismatch of provided arguments with static extents.");
|
||||
}
|
||||
// non-representability of extents itself
|
||||
{
|
||||
TEST_LIBCPP_ASSERT_FAILURE(
|
||||
([=] {
|
||||
std::layout_left::mapping<std::extents<signed char, D>> m(
|
||||
[[maybe_unused]] std::layout_left::mapping<std::extents<signed char, D>> m(
|
||||
std::layout_right::mapping<std::extents<int, D>>(std::extents<int, D>(500)));
|
||||
}()),
|
||||
"extents ctor: arguments must be representable as index_type and nonnegative");
|
||||
|
@ -37,14 +37,14 @@ int main(int, char**) {
|
||||
}
|
||||
// mismatch of static extent
|
||||
{
|
||||
TEST_LIBCPP_ASSERT_FAILURE(([=] { std::layout_right::mapping<std::extents<int, D, 3>> m(arg); }()),
|
||||
TEST_LIBCPP_ASSERT_FAILURE(([=] { [[maybe_unused]] std::layout_right::mapping<std::extents<int, D, 3>> m(arg); }()),
|
||||
"extents construction: mismatch of provided arguments with static extents.");
|
||||
}
|
||||
// non-representability of extents itself
|
||||
{
|
||||
TEST_LIBCPP_ASSERT_FAILURE(
|
||||
([=] {
|
||||
std::layout_right::mapping<std::extents<signed char, D>> m(
|
||||
[[maybe_unused]] std::layout_right::mapping<std::extents<signed char, D>> m(
|
||||
std::layout_right::mapping<std::extents<int, D>>(std::extents<int, D>(500)));
|
||||
}()),
|
||||
"extents ctor: arguments must be representable as index_type and nonnegative");
|
||||
@ -55,7 +55,7 @@ int main(int, char**) {
|
||||
[[maybe_unused]] std::extents<signed char, D, 5> e(arg_exts);
|
||||
// but the product is not, so we can't use it for layout_right
|
||||
TEST_LIBCPP_ASSERT_FAILURE(
|
||||
([=] { std::layout_right::mapping<std::extents<signed char, D, 5>> m(arg); }()),
|
||||
([=] { [[maybe_unused]] std::layout_right::mapping<std::extents<signed char, D, 5>> m(arg); }()),
|
||||
"layout_right::mapping converting ctor: other.required_span_size() must be representable as index_type.");
|
||||
}
|
||||
return 0;
|
||||
|
@ -32,7 +32,8 @@ int main(int, char**) {
|
||||
// the extents are representable but the product is not, so we can't use it for layout_right
|
||||
TEST_LIBCPP_ASSERT_FAILURE(
|
||||
([=] {
|
||||
std::layout_right::mapping<std::extents<signed char, D, 5>> m(std::extents<signed char, D, 5>(100));
|
||||
[[maybe_unused]] std::layout_right::mapping<std::extents<signed char, D, 5>> m(
|
||||
std::extents<signed char, D, 5>(100));
|
||||
}()),
|
||||
"layout_right::mapping extents ctor: product of extents must be representable as index_type.");
|
||||
}
|
||||
|
@ -39,14 +39,14 @@ int main(int, char**) {
|
||||
}
|
||||
// mismatch of static extent
|
||||
{
|
||||
TEST_LIBCPP_ASSERT_FAILURE(([=] { std::layout_right::mapping<std::extents<int, 3>> m(arg); }()),
|
||||
TEST_LIBCPP_ASSERT_FAILURE(([=] { [[maybe_unused]] std::layout_right::mapping<std::extents<int, 3>> m(arg); }()),
|
||||
"extents construction: mismatch of provided arguments with static extents.");
|
||||
}
|
||||
// non-representability of extents itself
|
||||
{
|
||||
TEST_LIBCPP_ASSERT_FAILURE(
|
||||
([=] {
|
||||
std::layout_right::mapping<std::extents<signed char, D>> m(
|
||||
[[maybe_unused]] std::layout_right::mapping<std::extents<signed char, D>> m(
|
||||
std::layout_left::mapping<std::extents<int, D>>(std::extents<int, D>(500)));
|
||||
}()),
|
||||
"extents ctor: arguments must be representable as index_type and nonnegative");
|
||||
|
@ -58,14 +58,15 @@ int main(int, char**) {
|
||||
{
|
||||
std::extents<int, D, D> arg_exts{100, 5};
|
||||
std::layout_stride::mapping<std::extents<int, D, D>> arg(arg_exts, std::array<int, 2>{1, 100});
|
||||
TEST_LIBCPP_ASSERT_FAILURE(([=] { std::layout_stride::mapping<std::extents<int, D, 3>> m(arg); }()),
|
||||
TEST_LIBCPP_ASSERT_FAILURE(
|
||||
([=] { [[maybe_unused]] std::layout_stride::mapping<std::extents<int, D, 3>> m(arg); }()),
|
||||
"extents construction: mismatch of provided arguments with static extents.");
|
||||
}
|
||||
// non-representability of extents itself
|
||||
{
|
||||
TEST_LIBCPP_ASSERT_FAILURE(
|
||||
([=] {
|
||||
std::layout_stride::mapping<std::extents<signed char, D>> m(
|
||||
[[maybe_unused]] std::layout_stride::mapping<std::extents<signed char, D>> m(
|
||||
std::layout_stride::mapping<std::extents<int, D>>(std::extents<int, D>(500), std::array<int, 1>{1}));
|
||||
}()),
|
||||
"extents ctor: arguments must be representable as index_type and nonnegative");
|
||||
@ -73,7 +74,8 @@ int main(int, char**) {
|
||||
// all strides must be larger than zero
|
||||
{
|
||||
always_convertible_layout::mapping<std::dextents<int, 2>> offset_map(std::dextents<int, 2>{10, 10}, 100, -1);
|
||||
TEST_LIBCPP_ASSERT_FAILURE(([=] { std::layout_stride::mapping<std::extents<signed char, D, D>> m(offset_map); }()),
|
||||
TEST_LIBCPP_ASSERT_FAILURE(
|
||||
([=] { [[maybe_unused]] std::layout_stride::mapping<std::extents<signed char, D, D>> m(offset_map); }()),
|
||||
"layout_stride::mapping converting ctor: all strides must be greater than 0");
|
||||
}
|
||||
// required_span_size not representable, while individual extents are
|
||||
@ -84,7 +86,7 @@ int main(int, char**) {
|
||||
[[maybe_unused]] std::extents<signed char, D, 5> e(arg_exts);
|
||||
// but the product is not, so we can't use it for layout_stride
|
||||
TEST_LIBCPP_ASSERT_FAILURE(
|
||||
([=] { std::layout_stride::mapping<std::extents<signed char, D, 5>> m(arg); }()),
|
||||
([=] { [[maybe_unused]] std::layout_stride::mapping<std::extents<signed char, D, 5>> m(arg); }()),
|
||||
"layout_stride::mapping converting ctor: other.required_span_size() must be representable as index_type.");
|
||||
}
|
||||
// required_span_size not representable, while individual extents are, edge case
|
||||
@ -98,13 +100,14 @@ int main(int, char**) {
|
||||
[[maybe_unused]] std::extents<signed char, D, 10> e(arg_exts);
|
||||
// but the product is not, so we can't use it for layout_stride
|
||||
TEST_LIBCPP_ASSERT_FAILURE(
|
||||
([=] { std::layout_stride::mapping<std::extents<signed char, D, 10>> m(arg); }()),
|
||||
([=] { [[maybe_unused]] std::layout_stride::mapping<std::extents<signed char, D, 10>> m(arg); }()),
|
||||
"layout_stride::mapping converting ctor: other.required_span_size() must be representable as index_type.");
|
||||
}
|
||||
// base offset must be 0 (i.e. mapping(0,...,0)==0) for a strided layout with positive strides
|
||||
{
|
||||
always_convertible_layout::mapping<std::dextents<int, 2>> offset_map(std::dextents<int, 2>{10, 10}, 3);
|
||||
TEST_LIBCPP_ASSERT_FAILURE(([=] { std::layout_stride::mapping<std::extents<signed char, D, D>> m(offset_map); }()),
|
||||
TEST_LIBCPP_ASSERT_FAILURE(
|
||||
([=] { [[maybe_unused]] std::layout_stride::mapping<std::extents<signed char, D, D>> m(offset_map); }()),
|
||||
"layout_stride::mapping converting ctor: base offset of mapping must be zero.");
|
||||
}
|
||||
return 0;
|
||||
|
@ -42,7 +42,7 @@ int main(int, char**) {
|
||||
{
|
||||
TEST_LIBCPP_ASSERT_FAILURE(
|
||||
([=] {
|
||||
std::layout_stride::mapping<std::extents<unsigned, D, 5, 7>> m(
|
||||
[[maybe_unused]] std::layout_stride::mapping<std::extents<unsigned, D, 5, 7>> m(
|
||||
std::extents<unsigned, D, 5, 7>(20), std::array<unsigned, 3>{4, 1, 200});
|
||||
}()),
|
||||
"layout_stride::mapping ctor: the provided extents and strides lead to a non-unique mapping");
|
||||
@ -58,7 +58,7 @@ int main(int, char**) {
|
||||
// will fail because neither of the equal strides is associated with an extent of 1
|
||||
TEST_LIBCPP_ASSERT_FAILURE(
|
||||
([=] {
|
||||
std::layout_stride::mapping<std::extents<unsigned, D, 5, 2>> m3(
|
||||
[[maybe_unused]] std::layout_stride::mapping<std::extents<unsigned, D, 5, 2>> m3(
|
||||
std::extents<unsigned, D, 5, 2>(2), std::array<unsigned, 3>{5, 1, 5});
|
||||
}()),
|
||||
"layout_stride::mapping ctor: the provided extents and strides lead to a non-unique mapping");
|
||||
|
@ -42,7 +42,7 @@ int main(int, char**) {
|
||||
// the extents are representable but the product with strides is not, so we can't use it for layout_stride
|
||||
TEST_LIBCPP_ASSERT_FAILURE(
|
||||
([=] {
|
||||
std::layout_stride::mapping<std::extents<signed char, D, 5>> m(
|
||||
[[maybe_unused]] std::layout_stride::mapping<std::extents<signed char, D, 5>> m(
|
||||
std::extents<signed char, D, 5>(20), std::array<int, 2>{20, 1});
|
||||
}()),
|
||||
"layout_stride::mapping ctor: required span size is not representable as index_type.");
|
||||
@ -51,7 +51,7 @@ int main(int, char**) {
|
||||
static_assert(static_cast<unsigned char>(257u) == 1);
|
||||
TEST_LIBCPP_ASSERT_FAILURE(
|
||||
([=] {
|
||||
std::layout_stride::mapping<std::extents<unsigned char, D, 5>> m(
|
||||
[[maybe_unused]] std::layout_stride::mapping<std::extents<unsigned char, D, 5>> m(
|
||||
std::extents<unsigned char, D, 5>(20), std::array<unsigned, 2>{257, 1});
|
||||
}()),
|
||||
"layout_stride::mapping ctor: required span size is not representable as index_type.");
|
||||
@ -59,14 +59,14 @@ int main(int, char**) {
|
||||
// negative strides are not allowed, check with unsigned index_type so we make sure we catch that
|
||||
TEST_LIBCPP_ASSERT_FAILURE(
|
||||
([=] {
|
||||
std::layout_stride::mapping<std::extents<unsigned, D, 5>> m(
|
||||
[[maybe_unused]] std::layout_stride::mapping<std::extents<unsigned, D, 5>> m(
|
||||
std::extents<unsigned, D, 5>(20), std::array<int, 2>{20, -1});
|
||||
}()),
|
||||
"layout_stride::mapping ctor: all strides must be greater than 0");
|
||||
// zero strides are not allowed, check with unsigned index_type so we make sure we catch that
|
||||
TEST_LIBCPP_ASSERT_FAILURE(
|
||||
([=] {
|
||||
std::layout_stride::mapping<std::extents<unsigned, D, 5>> m(
|
||||
[[maybe_unused]] std::layout_stride::mapping<std::extents<unsigned, D, 5>> m(
|
||||
std::extents<unsigned, D, 5>(20), std::array<unsigned, 2>{20, 0});
|
||||
}()),
|
||||
"layout_stride::mapping ctor: all strides must be greater than 0");
|
||||
|
@ -43,7 +43,7 @@ int main(int, char**) {
|
||||
TEST_LIBCPP_ASSERT_FAILURE(
|
||||
([=] {
|
||||
std::array<unsigned, 3> strides{4, 1, 200};
|
||||
std::layout_stride::mapping<std::extents<unsigned, D, 5, 7>> m(
|
||||
[[maybe_unused]] std::layout_stride::mapping<std::extents<unsigned, D, 5, 7>> m(
|
||||
std::extents<unsigned, D, 5, 7>(20), std::span(strides));
|
||||
}()),
|
||||
"layout_stride::mapping ctor: the provided extents and strides lead to a non-unique mapping");
|
||||
@ -61,7 +61,7 @@ int main(int, char**) {
|
||||
// will fail because neither of the equal strides is associated with an extent of 1
|
||||
TEST_LIBCPP_ASSERT_FAILURE(
|
||||
([=] {
|
||||
std::layout_stride::mapping<std::extents<unsigned, D, 5, 2>> m3(
|
||||
[[maybe_unused]] std::layout_stride::mapping<std::extents<unsigned, D, 5, 2>> m3(
|
||||
std::extents<unsigned, D, 5, 2>(2), std::span(strides));
|
||||
}()),
|
||||
"layout_stride::mapping ctor: the provided extents and strides lead to a non-unique mapping");
|
||||
|
@ -8,6 +8,8 @@
|
||||
// UNSUPPORTED: no-threads
|
||||
// UNSUPPORTED: c++03, c++11, c++14, c++17
|
||||
// REQUIRES: libcpp-hardening-mode={{extensive|debug}}
|
||||
// Without the assertion, the test will most likely time out.
|
||||
// UNSUPPORTED: libcpp-assertion-semantic={{ignore|observe}}
|
||||
|
||||
// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
|
||||
|
||||
|
@ -18,6 +18,8 @@
|
||||
|
||||
// REQUIRES: has-unix-headers
|
||||
// REQUIRES: libcpp-hardening-mode={{extensive|debug}}
|
||||
// Without the assertion, the test will most likely time out.
|
||||
// UNSUPPORTED: libcpp-assertion-semantic={{ignore|observe}}
|
||||
// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
|
||||
|
||||
#include <latch>
|
||||
|
@ -24,9 +24,10 @@
|
||||
|
||||
#include "check_assertion.h"
|
||||
|
||||
int main(int, char **) {
|
||||
int main(int, char**) {
|
||||
{
|
||||
TEST_LIBCPP_ASSERT_FAILURE([]{ std::latch l(-1); }(),
|
||||
TEST_LIBCPP_ASSERT_FAILURE(
|
||||
[] { [[maybe_unused]] std::latch l(-1); }(),
|
||||
"latch::latch(ptrdiff_t): latch cannot be "
|
||||
"initialized with a negative value");
|
||||
}
|
||||
|
@ -26,7 +26,7 @@
|
||||
int main(int, char**) {
|
||||
{
|
||||
TEST_LIBCPP_ASSERT_FAILURE(
|
||||
[] { std::counting_semaphore<> s(-1); }(),
|
||||
[] { [[maybe_unused]] std::counting_semaphore<> s(-1); }(),
|
||||
"counting_semaphore::counting_semaphore(ptrdiff_t): counting_semaphore cannot be "
|
||||
"initialized with a negative value");
|
||||
}
|
||||
|
@ -44,15 +44,32 @@ static constexpr const char* Marker = "###";
|
||||
using MatchResult = std::pair<bool, std::string>;
|
||||
using Matcher = std::function<MatchResult(const std::string& /*text*/)>;
|
||||
|
||||
MatchResult MatchAssertionMessage(const std::string& text, std::string_view expected_message) {
|
||||
// Using the marker makes matching more precise, but we cannot output the marker when the `observe` semantic is used
|
||||
// (because it doesn't allow customizing the logging function). If the marker is not available, fall back to using less
|
||||
// precise matching by just the error message.
|
||||
MatchResult MatchAssertionMessage(const std::string& text, std::string_view expected_message, bool use_marker) {
|
||||
// Extract information from the error message. This has to stay synchronized with how we format assertions in the
|
||||
// library.
|
||||
std::regex assertion_format(".*###\\n(.*):(\\d+): assertion (.*) failed: (.*)\\n###");
|
||||
std::string assertion_format_string = [&] {
|
||||
if (use_marker)
|
||||
return (".*###\\n(.*):(\\d+): assertion (.*) failed: (.*)\\n###");
|
||||
return ("(.*):(\\d+): assertion (.*) failed: (.*)\\n");
|
||||
}();
|
||||
std::regex assertion_format(assertion_format_string);
|
||||
|
||||
std::smatch match_result;
|
||||
bool has_match = std::regex_match(text, match_result, assertion_format);
|
||||
assert(has_match);
|
||||
assert(match_result.size() == 5);
|
||||
// If a non-terminating assertion semantic is used, more than one assertion might be triggered before the process
|
||||
// dies, so we cannot expect the entire target string to match.
|
||||
bool has_match = std::regex_search(text, match_result, assertion_format);
|
||||
if (!has_match || match_result.size() != 5) {
|
||||
std::stringstream matching_error;
|
||||
matching_error //
|
||||
<< "Failed to parse the assertion message.\n" //
|
||||
<< "Using marker: " << use_marker << "\n" //
|
||||
<< "Expected message: '" << expected_message.data() << "'\n" //
|
||||
<< "Stderr contents: '" << text.c_str() << "'\n";
|
||||
return MatchResult(/*success=*/false, matching_error.str());
|
||||
}
|
||||
|
||||
const std::string& file = match_result[1];
|
||||
int line = std::stoi(match_result[2]);
|
||||
@ -72,9 +89,9 @@ MatchResult MatchAssertionMessage(const std::string& text, std::string_view expe
|
||||
return MatchResult(/*success=*/true, /*maybe_error=*/"");
|
||||
}
|
||||
|
||||
Matcher MakeAssertionMessageMatcher(std::string_view assertion_message) {
|
||||
Matcher MakeAssertionMessageMatcher(std::string_view assertion_message, bool use_marker = true) {
|
||||
return [=](const std::string& text) { //
|
||||
return MatchAssertionMessage(text, assertion_message);
|
||||
return MatchAssertionMessage(text, assertion_message, use_marker);
|
||||
};
|
||||
}
|
||||
|
||||
@ -85,13 +102,17 @@ Matcher MakeAnyMatcher() {
|
||||
}
|
||||
|
||||
enum class DeathCause {
|
||||
// Valid causes
|
||||
// Valid causes.
|
||||
VerboseAbort = 1,
|
||||
StdAbort,
|
||||
StdTerminate,
|
||||
Trap,
|
||||
// Invalid causes
|
||||
// Causes that might be invalid or might stem from undefined behavior (relevant for non-terminating assertion
|
||||
// semantics).
|
||||
DidNotDie,
|
||||
Segfault,
|
||||
ArithmeticError,
|
||||
// Always invalid causes.
|
||||
SetupFailure,
|
||||
Unknown
|
||||
};
|
||||
@ -108,6 +129,16 @@ bool IsValidCause(DeathCause cause) {
|
||||
}
|
||||
}
|
||||
|
||||
bool IsTestSetupErrorCause(DeathCause cause) {
|
||||
switch (cause) {
|
||||
case DeathCause::SetupFailure:
|
||||
case DeathCause::Unknown:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::string ToString(DeathCause cause) {
|
||||
switch (cause) {
|
||||
case DeathCause::VerboseAbort:
|
||||
@ -120,10 +151,14 @@ std::string ToString(DeathCause cause) {
|
||||
return "trap";
|
||||
case DeathCause::DidNotDie:
|
||||
return "<invalid cause (child did not die)>";
|
||||
case DeathCause::Segfault:
|
||||
return "<invalid cause (segmentation fault)>";
|
||||
case DeathCause::ArithmeticError:
|
||||
return "<invalid cause (fatal arithmetic error)>";
|
||||
case DeathCause::SetupFailure:
|
||||
return "<invalid cause (child failed to set up test environment)>";
|
||||
return "<test setup error (child failed to set up test environment)>";
|
||||
case DeathCause::Unknown:
|
||||
return "<invalid cause (cause unknown)>";
|
||||
return "<test setup error (test doesn't know how to interpret the death cause)>";
|
||||
}
|
||||
|
||||
assert(false && "Unreachable");
|
||||
@ -225,9 +260,38 @@ public:
|
||||
return DeathTestResult(Outcome::Success, cause);
|
||||
}
|
||||
|
||||
void PrintFailureDetails(std::string_view failure_description, std::string_view stmt, DeathCause cause) const {
|
||||
std::fprintf(
|
||||
stderr, "Failure: EXPECT_DEATH( %s ) failed!\n(reason: %s)\n\n", stmt.data(), failure_description.data());
|
||||
// When non-terminating assertion semantics are used, the program will invoke UB which might or might not crash the
|
||||
// process; we make sure that the execution produces the expected error message but otherwise consider the test run
|
||||
// successful whether the child process dies or not.
|
||||
template <class Func>
|
||||
DeathTestResult RunWithoutGuaranteedDeath(Func&& func, const Matcher& matcher) {
|
||||
std::signal(SIGABRT, [](int) { StopChildProcess(DeathCause::StdAbort); });
|
||||
std::set_terminate([] { StopChildProcess(DeathCause::StdTerminate); });
|
||||
|
||||
DeathCause cause = Run(func);
|
||||
|
||||
if (IsTestSetupErrorCause(cause)) {
|
||||
return DeathTestResult(Outcome::InvalidCause, cause, ToString(cause));
|
||||
}
|
||||
|
||||
MatchResult match_result = matcher(GetChildStdErr());
|
||||
if (!match_result.first) {
|
||||
auto failure_description = std::string("Child produced a different error message\n") + match_result.second;
|
||||
return DeathTestResult(Outcome::UnexpectedErrorMessage, cause, failure_description);
|
||||
}
|
||||
|
||||
return DeathTestResult(Outcome::Success, cause);
|
||||
}
|
||||
|
||||
void PrintFailureDetails(std::string_view invocation,
|
||||
std::string_view failure_description,
|
||||
std::string_view stmt,
|
||||
DeathCause cause) const {
|
||||
std::fprintf(stderr,
|
||||
"Failure: %s( %s ) failed!\n(reason: %s)\n\n",
|
||||
invocation.data(),
|
||||
stmt.data(),
|
||||
failure_description.data());
|
||||
|
||||
if (cause != DeathCause::Unknown) {
|
||||
std::fprintf(stderr, "child exit code: %d\n", GetChildExitCode());
|
||||
@ -311,10 +375,16 @@ private:
|
||||
|
||||
if (WIFSIGNALED(status_value)) {
|
||||
exit_code_ = WTERMSIG(status_value);
|
||||
// `__builtin_trap` generqtes `SIGILL` on x86 and `SIGTRAP` on ARM.
|
||||
// `__builtin_trap` generates `SIGILL` on x86 and `SIGTRAP` on ARM.
|
||||
if (exit_code_ == SIGILL || exit_code_ == SIGTRAP) {
|
||||
return DeathCause::Trap;
|
||||
}
|
||||
if (exit_code_ == SIGSEGV) {
|
||||
return DeathCause::Segfault;
|
||||
}
|
||||
if (exit_code_ == SIGFPE) {
|
||||
return DeathCause::ArithmeticError;
|
||||
}
|
||||
}
|
||||
|
||||
return DeathCause::Unknown;
|
||||
@ -357,7 +427,7 @@ bool ExpectDeath(
|
||||
DeathTest test_case;
|
||||
DeathTestResult test_result = test_case.Run(expected_causes, func, matcher);
|
||||
if (!test_result.success()) {
|
||||
test_case.PrintFailureDetails(test_result.failure_description(), stmt, test_result.cause());
|
||||
test_case.PrintFailureDetails("EXPECT_DEATH", test_result.failure_description(), stmt, test_result.cause());
|
||||
}
|
||||
|
||||
return test_result.success();
|
||||
@ -378,6 +448,22 @@ bool ExpectDeath(DeathCause expected_cause, const char* stmt, Func&& func) {
|
||||
return ExpectDeath(std::array<DeathCause, 1>{expected_cause}, stmt, func, MakeAnyMatcher());
|
||||
}
|
||||
|
||||
template <class Func>
|
||||
bool ExpectLog(const char* stmt, Func&& func, const Matcher& matcher) {
|
||||
DeathTest test_case;
|
||||
DeathTestResult test_result = test_case.RunWithoutGuaranteedDeath(func, matcher);
|
||||
if (!test_result.success()) {
|
||||
test_case.PrintFailureDetails("EXPECT_LOG", test_result.failure_description(), stmt, test_result.cause());
|
||||
}
|
||||
|
||||
return test_result.success();
|
||||
}
|
||||
|
||||
template <class Func>
|
||||
bool ExpectLog(const char* stmt, Func&& func) {
|
||||
return ExpectLog(stmt, func, MakeAnyMatcher());
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
|
||||
/// Assert that the specified expression aborts with the expected cause and, optionally, error message.
|
||||
@ -392,13 +478,28 @@ bool ExpectDeath(DeathCause expected_cause, const char* stmt, Func&& func) {
|
||||
#define EXPECT_STD_TERMINATE(...) \
|
||||
assert( ExpectDeath(DeathCause::StdTerminate, #__VA_ARGS__, __VA_ARGS__) )
|
||||
|
||||
#if defined(_LIBCPP_HARDENING_MODE) && _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG
|
||||
#if defined(_LIBCPP_ASSERTION_SEMANTIC)
|
||||
|
||||
#if _LIBCPP_ASSERTION_SEMANTIC == _LIBCPP_ASSERTION_SEMANTIC_ENFORCE
|
||||
#define TEST_LIBCPP_ASSERT_FAILURE(expr, message) \
|
||||
assert(( ExpectDeath(DeathCause::VerboseAbort, #expr, [&]() { (void)(expr); }, MakeAssertionMessageMatcher(message)) ))
|
||||
#elif _LIBCPP_ASSERTION_SEMANTIC == _LIBCPP_ASSERTION_SEMANTIC_QUICK_ENFORCE
|
||||
#define TEST_LIBCPP_ASSERT_FAILURE(expr, message) \
|
||||
assert(( ExpectDeath(DeathCause::Trap, #expr, [&]() { (void)(expr); }) ))
|
||||
#elif _LIBCPP_ASSERTION_SEMANTIC == _LIBCPP_ASSERTION_SEMANTIC_OBSERVE
|
||||
#define TEST_LIBCPP_ASSERT_FAILURE(expr, message) \
|
||||
assert(( ExpectLog(#expr, [&]() { (void)(expr); }, MakeAssertionMessageMatcher(message, /*use_marker=*/false)) ))
|
||||
#elif _LIBCPP_ASSERTION_SEMANTIC == _LIBCPP_ASSERTION_SEMANTIC_IGNORE
|
||||
#define TEST_LIBCPP_ASSERT_FAILURE(expr, message) \
|
||||
assert(( ExpectLog(#expr, [&]() { (void)(expr); }) ))
|
||||
#else
|
||||
#error "Unknown value for _LIBCPP_ASSERTION_SEMANTIC"
|
||||
#endif // _LIBCPP_ASSERTION_SEMANTIC == _LIBCPP_ASSERTION_SEMANTIC_ENFORCE
|
||||
|
||||
#else
|
||||
#define TEST_LIBCPP_ASSERT_FAILURE(expr, message) \
|
||||
assert(( ExpectDeath(DeathCause::Trap, #expr, [&]() { (void)(expr); }) ))
|
||||
#endif // _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG
|
||||
#endif // defined(_LIBCPP_ASSERTION_SEMANTIC)
|
||||
|
||||
// clang-format on
|
||||
|
||||
|
@ -53,7 +53,7 @@ bool TestDeathTest(
|
||||
}
|
||||
|
||||
if (!maybe_failure_description.empty()) {
|
||||
test_case.PrintFailureDetails(maybe_failure_description, stmt, test_result.cause());
|
||||
test_case.PrintFailureDetails("EXPECT_DEATH", maybe_failure_description, stmt, test_result.cause());
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -76,7 +76,7 @@ DeathCause assertion_death_cause = DeathCause::Trap;
|
||||
#endif
|
||||
|
||||
int main(int, char**) {
|
||||
auto fail_assert = [] { _LIBCPP_ASSERT(false, "Some message"); };
|
||||
[[maybe_unused]] auto fail_assert = [] { _LIBCPP_ASSERT(false, "Some message"); };
|
||||
Matcher good_matcher = MakeAssertionMessageMatcher("Some message");
|
||||
Matcher bad_matcher = MakeAssertionMessageMatcher("Bad expected message");
|
||||
|
||||
@ -89,16 +89,22 @@ int main(int, char**) {
|
||||
// Success -- trapping.
|
||||
TEST_DEATH_TEST(Outcome::Success, DeathCause::Trap, __builtin_trap());
|
||||
|
||||
// `_LIBCPP_ASSERT` does not terminate the program if the `observe` semantic is used, so these tests would fail with
|
||||
// `DidNotDie` cause.
|
||||
#if _LIBCPP_ASSERTION_SEMANTIC != _LIBCPP_ASSERTION_SEMANTIC_OBSERVE
|
||||
|
||||
// Success -- assertion failure with any matcher.
|
||||
TEST_DEATH_TEST_MATCHES(Outcome::Success, assertion_death_cause, MakeAnyMatcher(), fail_assert());
|
||||
|
||||
// Success -- assertion failure with a specific matcher.
|
||||
TEST_DEATH_TEST_MATCHES(Outcome::Success, assertion_death_cause, good_matcher, fail_assert());
|
||||
|
||||
#if _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG
|
||||
# if _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG
|
||||
// Failure -- error message doesn't match.
|
||||
TEST_DEATH_TEST_MATCHES(Outcome::UnexpectedErrorMessage, assertion_death_cause, bad_matcher, fail_assert());
|
||||
#endif
|
||||
# endif
|
||||
|
||||
#endif // _LIBCPP_ASSERTION_SEMANTIC != _LIBCPP_ASSERTION_SEMANTIC_OBSERVE
|
||||
|
||||
// Invalid cause -- child did not die.
|
||||
TEST_DEATH_TEST(Outcome::InvalidCause, DeathCause::DidNotDie, ((void)0));
|
||||
@ -125,7 +131,9 @@ int main(int, char**) {
|
||||
EXPECT_DEATH_MATCHES(simple_matcher, invoke_verbose_abort());
|
||||
EXPECT_STD_ABORT(invoke_abort());
|
||||
EXPECT_STD_TERMINATE([] { std::terminate(); });
|
||||
#if _LIBCPP_ASSERTION_SEMANTIC != _LIBCPP_ASSERTION_SEMANTIC_OBSERVE
|
||||
TEST_LIBCPP_ASSERT_FAILURE(fail_assert(), "Some message");
|
||||
#endif
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -442,6 +442,12 @@ generic-hardening-mode-extensive)
|
||||
check-runtimes
|
||||
check-abi-list
|
||||
;;
|
||||
generic-hardening-mode-extensive-observe-semantic)
|
||||
clean
|
||||
generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-hardening-mode-extensive-observe-semantic.cmake"
|
||||
check-runtimes
|
||||
check-abi-list
|
||||
;;
|
||||
generic-hardening-mode-debug)
|
||||
clean
|
||||
generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-hardening-mode-debug.cmake"
|
||||
|
@ -454,5 +454,24 @@ DEFAULT_PARAMETERS = [
|
||||
help="Whether to test the main or C++03-specific headers. Only changes behaviour when std=c++03.",
|
||||
actions=lambda enabled: [] if not enabled else [AddFlag("-D_LIBCPP_USE_FROZEN_CXX03_HEADERS"), AddFeature("FROZEN-CXX03-HEADERS-FIXME")],
|
||||
),
|
||||
Parameter(
|
||||
name='assertion_semantic',
|
||||
choices=["ignore", "observe", "quick_enforce", "enforce", "undefined"],
|
||||
type=str,
|
||||
default="undefined",
|
||||
help="Whether to override the assertion semantic used by hardening. This is only meaningful when running the "
|
||||
"tests against libc++ with hardening enabled. By default, no assertion semantic is specified explicitly, so "
|
||||
"the default one will be used (depending on the hardening mode).",
|
||||
actions=lambda assertion_semantic: filter(
|
||||
None,
|
||||
[
|
||||
AddCompileFlag("-D_LIBCPP_ASSERTION_SEMANTIC=_LIBCPP_ASSERTION_SEMANTIC_IGNORE") if assertion_semantic == "ignore" else None,
|
||||
AddCompileFlag("-D_LIBCPP_ASSERTION_SEMANTIC=_LIBCPP_ASSERTION_SEMANTIC_OBSERVE") if assertion_semantic == "observe" else None,
|
||||
AddCompileFlag("-D_LIBCPP_ASSERTION_SEMANTIC=_LIBCPP_ASSERTION_SEMANTIC_QUICK_ENFORCE") if assertion_semantic == "quick_enforce" else None,
|
||||
AddCompileFlag("-D_LIBCPP_ASSERTION_SEMANTIC=_LIBCPP_ASSERTION_SEMANTIC_ENFORCE") if assertion_semantic == "enforce" else None,
|
||||
AddFeature("libcpp-assertion-semantic={}".format(assertion_semantic)) if assertion_semantic != "undefined" else None,
|
||||
],
|
||||
),
|
||||
),
|
||||
]
|
||||
# fmt: on
|
||||
|
31
libcxx/vendor/llvm/default_assertion_handler.in
vendored
31
libcxx/vendor/llvm/default_assertion_handler.in
vendored
@ -16,6 +16,7 @@
|
||||
# include <__cxx03/__verbose_trap>
|
||||
#else
|
||||
# include <__config>
|
||||
# include <__log_hardening_failure>
|
||||
# include <__verbose_abort>
|
||||
# include <__verbose_trap>
|
||||
#endif
|
||||
@ -24,14 +25,40 @@
|
||||
# pragma GCC system_header
|
||||
#endif
|
||||
|
||||
#if _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG
|
||||
#if __cplusplus < 201103L && defined(_LIBCPP_USE_FROZEN_CXX03_HEADERS)
|
||||
|
||||
// Keep the old implementation that doesn't support assertion semantics for backward compatibility with the frozen C++03
|
||||
// mode.
|
||||
# if _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG
|
||||
# define _LIBCPP_ASSERTION_HANDLER(message) _LIBCPP_VERBOSE_ABORT("%s", message)
|
||||
# else
|
||||
# define _LIBCPP_ASSERTION_HANDLER(message) _LIBCPP_VERBOSE_TRAP(message)
|
||||
# endif // _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG
|
||||
|
||||
#else
|
||||
|
||||
# if _LIBCPP_ASSERTION_SEMANTIC == _LIBCPP_ASSERTION_SEMANTIC_IGNORE
|
||||
# define _LIBCPP_ASSERTION_HANDLER(message) ((void)0)
|
||||
|
||||
# elif _LIBCPP_ASSERTION_SEMANTIC == _LIBCPP_ASSERTION_SEMANTIC_OBSERVE
|
||||
# define _LIBCPP_ASSERTION_HANDLER(message) _LIBCPP_LOG_HARDENING_FAILURE(message)
|
||||
|
||||
# elif _LIBCPP_ASSERTION_SEMANTIC == _LIBCPP_ASSERTION_SEMANTIC_QUICK_ENFORCE
|
||||
# define _LIBCPP_ASSERTION_HANDLER(message) _LIBCPP_VERBOSE_TRAP(message)
|
||||
|
||||
#endif // _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG
|
||||
# elif _LIBCPP_ASSERTION_SEMANTIC == _LIBCPP_ASSERTION_SEMANTIC_ENFORCE
|
||||
# define _LIBCPP_ASSERTION_HANDLER(message) _LIBCPP_VERBOSE_ABORT("%s", message)
|
||||
|
||||
# else
|
||||
|
||||
# error _LIBCPP_ASSERTION_SEMANTIC must be set to one of the following values: \
|
||||
_LIBCPP_ASSERTION_SEMANTIC_IGNORE, \
|
||||
_LIBCPP_ASSERTION_SEMANTIC_OBSERVE, \
|
||||
_LIBCPP_ASSERTION_SEMANTIC_QUICK_ENFORCE, \
|
||||
_LIBCPP_ASSERTION_SEMANTIC_ENFORCE
|
||||
|
||||
# endif // _LIBCPP_ASSERTION_SEMANTIC == _LIBCPP_ASSERTION_SEMANTIC_IGNORE
|
||||
|
||||
#endif // __cplusplus < 201103L && defined(_LIBCPP_USE_FROZEN_CXX03_HEADERS)
|
||||
|
||||
#endif // _LIBCPP___ASSERTION_HANDLER
|
||||
|
@ -19,6 +19,14 @@
|
||||
#include "../abort_message.h"
|
||||
#endif
|
||||
|
||||
#ifndef _LIBCPP_LOG_HARDENING_FAILURE
|
||||
// Libc++abi does not have any functionality to log and continue, so we drop
|
||||
// error messages when we build the demangler with `observe` assertion semantic.
|
||||
// Once the layering with libc++ is improved, this could use the libc++
|
||||
// functionality to log hardening failures.
|
||||
#define _LIBCPP_LOG_HARDENING_FAILURE(message) ((void)0)
|
||||
#endif
|
||||
|
||||
#include <version>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
Loading…
x
Reference in New Issue
Block a user