[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:
Konstantin Varlamov 2025-07-15 02:14:30 -07:00 committed by GitHub
parent aa0629dabe
commit 7345508c6f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 351 additions and 67 deletions

View File

@ -128,6 +128,7 @@ jobs:
'generic-abi-unstable', 'generic-abi-unstable',
'generic-hardening-mode-debug', 'generic-hardening-mode-debug',
'generic-hardening-mode-extensive', 'generic-hardening-mode-extensive',
'generic-hardening-mode-extensive-observe-semantic',
'generic-hardening-mode-fast', 'generic-hardening-mode-fast',
'generic-hardening-mode-fast-with-abi-breaks', 'generic-hardening-mode-fast-with-abi-breaks',
'generic-merged', 'generic-merged',

View File

@ -0,0 +1,2 @@
set(LIBCXX_HARDENING_MODE "extensive" CACHE STRING "")
set(LIBCXX_TEST_PARAMS "assertion_semantic=observe" CACHE STRING "")

View File

@ -39,6 +39,8 @@ modes are:
Enabling hardening has no impact on the ABI. Enabling hardening has no impact on the ABI.
.. _notes-for-users:
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 pre-built components. Most libc++ code is header-based, so a user-provided
value for ``_LIBCPP_HARDENING_MODE`` will be mostly respected. 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 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 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. 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: .. _override-assertion-handler:
Overriding the assertion failure handler Overriding the assertion failure handler

View File

@ -88,6 +88,11 @@ Improvements and New Features
- ``ctype::tolower`` and ``ctype::toupper`` have been optimized, resulting in a 2x performance improvement. - ``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 Deprecations and Removals
------------------------- -------------------------

View File

@ -147,6 +147,40 @@ _LIBCPP_HARDENING_MODE_EXTENSIVE, \
_LIBCPP_HARDENING_MODE_DEBUG _LIBCPP_HARDENING_MODE_DEBUG
# endif # 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 // } HARDENING
# define _LIBCPP_TOSTRING2(x) #x # define _LIBCPP_TOSTRING2(x) #x

View File

@ -43,17 +43,17 @@ int main(int, char**) {
} }
// mismatch of static extent // 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."); "extents construction: mismatch of provided arguments with static extents.");
} }
// value out of range // 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"); "extents ctor: arguments must be representable as index_type and nonnegative");
} }
// negative value // 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"); "extents ctor: arguments must be representable as index_type and nonnegative");
} }
return 0; return 0;

View File

@ -45,17 +45,17 @@ int main(int, char**) {
} }
// mismatch of static extent // 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."); "extents construction: mismatch of provided arguments with static extents.");
} }
// value out of range // 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"); "extents ctor: arguments must be representable as index_type and nonnegative");
} }
// negative value // 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"); "extents ctor: arguments must be representable as index_type and nonnegative");
} }
return 0; return 0;

View File

@ -44,7 +44,7 @@ int main(int, char**) {
{ {
TEST_LIBCPP_ASSERT_FAILURE( 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))); std::layout_left::mapping<std::extents<int, D>>(std::extents<int, D>(500)));
}()), }()),
"extents ctor: arguments must be representable as index_type and nonnegative"); "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); [[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 // but the product is not, so we can't use it for layout_left
TEST_LIBCPP_ASSERT_FAILURE( 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."); "layout_left::mapping converting ctor: other.required_span_size() must be representable as index_type.");
} }
return 0; return 0;

View File

@ -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 // the extents are representable but the product is not, so we can't use it for layout_left
TEST_LIBCPP_ASSERT_FAILURE( 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."); "layout_left::mapping extents ctor: product of extents must be representable as index_type.");
} }
return 0; return 0;

View File

@ -39,14 +39,14 @@ int main(int, char**) {
} }
// mismatch of static extent // 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."); "extents construction: mismatch of provided arguments with static extents.");
} }
// non-representability of extents itself // non-representability of extents itself
{ {
TEST_LIBCPP_ASSERT_FAILURE( 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))); std::layout_right::mapping<std::extents<int, D>>(std::extents<int, D>(500)));
}()), }()),
"extents ctor: arguments must be representable as index_type and nonnegative"); "extents ctor: arguments must be representable as index_type and nonnegative");

View File

@ -37,14 +37,14 @@ int main(int, char**) {
} }
// mismatch of static extent // 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."); "extents construction: mismatch of provided arguments with static extents.");
} }
// non-representability of extents itself // non-representability of extents itself
{ {
TEST_LIBCPP_ASSERT_FAILURE( 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))); std::layout_right::mapping<std::extents<int, D>>(std::extents<int, D>(500)));
}()), }()),
"extents ctor: arguments must be representable as index_type and nonnegative"); "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); [[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 // but the product is not, so we can't use it for layout_right
TEST_LIBCPP_ASSERT_FAILURE( 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."); "layout_right::mapping converting ctor: other.required_span_size() must be representable as index_type.");
} }
return 0; return 0;

View File

@ -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 // the extents are representable but the product is not, so we can't use it for layout_right
TEST_LIBCPP_ASSERT_FAILURE( 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."); "layout_right::mapping extents ctor: product of extents must be representable as index_type.");
} }

View File

@ -39,14 +39,14 @@ int main(int, char**) {
} }
// mismatch of static extent // 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."); "extents construction: mismatch of provided arguments with static extents.");
} }
// non-representability of extents itself // non-representability of extents itself
{ {
TEST_LIBCPP_ASSERT_FAILURE( 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))); std::layout_left::mapping<std::extents<int, D>>(std::extents<int, D>(500)));
}()), }()),
"extents ctor: arguments must be representable as index_type and nonnegative"); "extents ctor: arguments must be representable as index_type and nonnegative");

View File

@ -58,14 +58,15 @@ int main(int, char**) {
{ {
std::extents<int, D, D> arg_exts{100, 5}; 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}); 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(
"extents construction: mismatch of provided arguments with static extents."); ([=] { [[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 // non-representability of extents itself
{ {
TEST_LIBCPP_ASSERT_FAILURE( 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})); 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"); "extents ctor: arguments must be representable as index_type and nonnegative");
@ -73,8 +74,9 @@ int main(int, char**) {
// all strides must be larger than zero // 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); 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(
"layout_stride::mapping converting ctor: all strides must be greater than 0"); ([=] { [[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 // 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); [[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 // but the product is not, so we can't use it for layout_stride
TEST_LIBCPP_ASSERT_FAILURE( 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."); "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 // required_span_size not representable, while individual extents are, edge case
@ -98,14 +100,15 @@ int main(int, char**) {
[[maybe_unused]] std::extents<signed char, D, 10> e(arg_exts); [[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 // but the product is not, so we can't use it for layout_stride
TEST_LIBCPP_ASSERT_FAILURE( 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."); "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 // 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); 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(
"layout_stride::mapping converting ctor: base offset of mapping must be zero."); ([=] { [[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; return 0;
} }

View File

@ -42,7 +42,7 @@ int main(int, char**) {
{ {
TEST_LIBCPP_ASSERT_FAILURE( 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}); 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"); "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 // will fail because neither of the equal strides is associated with an extent of 1
TEST_LIBCPP_ASSERT_FAILURE( 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}); 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"); "layout_stride::mapping ctor: the provided extents and strides lead to a non-unique mapping");

View File

@ -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 // the extents are representable but the product with strides is not, so we can't use it for layout_stride
TEST_LIBCPP_ASSERT_FAILURE( 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}); 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."); "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); static_assert(static_cast<unsigned char>(257u) == 1);
TEST_LIBCPP_ASSERT_FAILURE( 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}); 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."); "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 // negative strides are not allowed, check with unsigned index_type so we make sure we catch that
TEST_LIBCPP_ASSERT_FAILURE( 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}); std::extents<unsigned, D, 5>(20), std::array<int, 2>{20, -1});
}()), }()),
"layout_stride::mapping ctor: all strides must be greater than 0"); "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 // zero strides are not allowed, check with unsigned index_type so we make sure we catch that
TEST_LIBCPP_ASSERT_FAILURE( 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}); std::extents<unsigned, D, 5>(20), std::array<unsigned, 2>{20, 0});
}()), }()),
"layout_stride::mapping ctor: all strides must be greater than 0"); "layout_stride::mapping ctor: all strides must be greater than 0");

View File

@ -43,7 +43,7 @@ int main(int, char**) {
TEST_LIBCPP_ASSERT_FAILURE( TEST_LIBCPP_ASSERT_FAILURE(
([=] { ([=] {
std::array<unsigned, 3> strides{4, 1, 200}; 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)); 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"); "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 // will fail because neither of the equal strides is associated with an extent of 1
TEST_LIBCPP_ASSERT_FAILURE( 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)); 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"); "layout_stride::mapping ctor: the provided extents and strides lead to a non-unique mapping");

View File

@ -8,6 +8,8 @@
// UNSUPPORTED: no-threads // UNSUPPORTED: no-threads
// UNSUPPORTED: c++03, c++11, c++14, c++17 // UNSUPPORTED: c++03, c++11, c++14, c++17
// REQUIRES: libcpp-hardening-mode={{extensive|debug}} // 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 // XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing

View File

@ -18,6 +18,8 @@
// REQUIRES: has-unix-headers // REQUIRES: has-unix-headers
// REQUIRES: libcpp-hardening-mode={{extensive|debug}} // 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 // XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
#include <latch> #include <latch>

View File

@ -24,11 +24,12 @@
#include "check_assertion.h" #include "check_assertion.h"
int main(int, char **) { int main(int, char**) {
{ {
TEST_LIBCPP_ASSERT_FAILURE([]{ std::latch l(-1); }(), TEST_LIBCPP_ASSERT_FAILURE(
"latch::latch(ptrdiff_t): latch cannot be " [] { [[maybe_unused]] std::latch l(-1); }(),
"initialized with a negative value"); "latch::latch(ptrdiff_t): latch cannot be "
"initialized with a negative value");
} }
// We can't check the precondition for max() because there's no value // We can't check the precondition for max() because there's no value

View File

@ -26,7 +26,7 @@
int main(int, char**) { int main(int, char**) {
{ {
TEST_LIBCPP_ASSERT_FAILURE( 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 " "counting_semaphore::counting_semaphore(ptrdiff_t): counting_semaphore cannot be "
"initialized with a negative value"); "initialized with a negative value");
} }

View File

@ -44,15 +44,32 @@ static constexpr const char* Marker = "###";
using MatchResult = std::pair<bool, std::string>; using MatchResult = std::pair<bool, std::string>;
using Matcher = std::function<MatchResult(const std::string& /*text*/)>; 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 // Extract information from the error message. This has to stay synchronized with how we format assertions in the
// library. // 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; std::smatch match_result;
bool has_match = std::regex_match(text, match_result, assertion_format); // If a non-terminating assertion semantic is used, more than one assertion might be triggered before the process
assert(has_match); // dies, so we cannot expect the entire target string to match.
assert(match_result.size() == 5); 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]; const std::string& file = match_result[1];
int line = std::stoi(match_result[2]); 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=*/""); 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 [=](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 { enum class DeathCause {
// Valid causes // Valid causes.
VerboseAbort = 1, VerboseAbort = 1,
StdAbort, StdAbort,
StdTerminate, StdTerminate,
Trap, Trap,
// Invalid causes // Causes that might be invalid or might stem from undefined behavior (relevant for non-terminating assertion
// semantics).
DidNotDie, DidNotDie,
Segfault,
ArithmeticError,
// Always invalid causes.
SetupFailure, SetupFailure,
Unknown 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) { std::string ToString(DeathCause cause) {
switch (cause) { switch (cause) {
case DeathCause::VerboseAbort: case DeathCause::VerboseAbort:
@ -120,10 +151,14 @@ std::string ToString(DeathCause cause) {
return "trap"; return "trap";
case DeathCause::DidNotDie: case DeathCause::DidNotDie:
return "<invalid cause (child did not die)>"; 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: 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: 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"); assert(false && "Unreachable");
@ -225,9 +260,38 @@ public:
return DeathTestResult(Outcome::Success, cause); return DeathTestResult(Outcome::Success, cause);
} }
void PrintFailureDetails(std::string_view failure_description, std::string_view stmt, DeathCause cause) const { // When non-terminating assertion semantics are used, the program will invoke UB which might or might not crash the
std::fprintf( // process; we make sure that the execution produces the expected error message but otherwise consider the test run
stderr, "Failure: EXPECT_DEATH( %s ) failed!\n(reason: %s)\n\n", stmt.data(), failure_description.data()); // 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) { if (cause != DeathCause::Unknown) {
std::fprintf(stderr, "child exit code: %d\n", GetChildExitCode()); std::fprintf(stderr, "child exit code: %d\n", GetChildExitCode());
@ -311,10 +375,16 @@ private:
if (WIFSIGNALED(status_value)) { if (WIFSIGNALED(status_value)) {
exit_code_ = WTERMSIG(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) { if (exit_code_ == SIGILL || exit_code_ == SIGTRAP) {
return DeathCause::Trap; return DeathCause::Trap;
} }
if (exit_code_ == SIGSEGV) {
return DeathCause::Segfault;
}
if (exit_code_ == SIGFPE) {
return DeathCause::ArithmeticError;
}
} }
return DeathCause::Unknown; return DeathCause::Unknown;
@ -357,7 +427,7 @@ bool ExpectDeath(
DeathTest test_case; DeathTest test_case;
DeathTestResult test_result = test_case.Run(expected_causes, func, matcher); DeathTestResult test_result = test_case.Run(expected_causes, func, matcher);
if (!test_result.success()) { 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(); 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()); 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 // clang-format off
/// Assert that the specified expression aborts with the expected cause and, optionally, error message. /// 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(...) \ #define EXPECT_STD_TERMINATE(...) \
assert( ExpectDeath(DeathCause::StdTerminate, #__VA_ARGS__, __VA_ARGS__) ) 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) \ #define TEST_LIBCPP_ASSERT_FAILURE(expr, message) \
assert(( ExpectDeath(DeathCause::VerboseAbort, #expr, [&]() { (void)(expr); }, MakeAssertionMessageMatcher(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 #else
#define TEST_LIBCPP_ASSERT_FAILURE(expr, message) \ #define TEST_LIBCPP_ASSERT_FAILURE(expr, message) \
assert(( ExpectDeath(DeathCause::Trap, #expr, [&]() { (void)(expr); }) )) assert(( ExpectDeath(DeathCause::Trap, #expr, [&]() { (void)(expr); }) ))
#endif // _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG #endif // defined(_LIBCPP_ASSERTION_SEMANTIC)
// clang-format on // clang-format on

View File

@ -53,7 +53,7 @@ bool TestDeathTest(
} }
if (!maybe_failure_description.empty()) { 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; return false;
} }
@ -76,9 +76,9 @@ DeathCause assertion_death_cause = DeathCause::Trap;
#endif #endif
int main(int, char**) { 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 good_matcher = MakeAssertionMessageMatcher("Some message");
Matcher bad_matcher = MakeAssertionMessageMatcher("Bad expected message"); Matcher bad_matcher = MakeAssertionMessageMatcher("Bad expected message");
// Test the implementation of death tests. We're bypassing the assertions added by the actual `EXPECT_DEATH` macros // Test the implementation of death tests. We're bypassing the assertions added by the actual `EXPECT_DEATH` macros
// which allows us to test failure cases (where the assertion would fail) as well. // which allows us to test failure cases (where the assertion would fail) as well.
@ -89,16 +89,22 @@ int main(int, char**) {
// Success -- trapping. // Success -- trapping.
TEST_DEATH_TEST(Outcome::Success, DeathCause::Trap, __builtin_trap()); 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. // Success -- assertion failure with any matcher.
TEST_DEATH_TEST_MATCHES(Outcome::Success, assertion_death_cause, MakeAnyMatcher(), fail_assert()); TEST_DEATH_TEST_MATCHES(Outcome::Success, assertion_death_cause, MakeAnyMatcher(), fail_assert());
// Success -- assertion failure with a specific matcher. // Success -- assertion failure with a specific matcher.
TEST_DEATH_TEST_MATCHES(Outcome::Success, assertion_death_cause, good_matcher, fail_assert()); 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. // Failure -- error message doesn't match.
TEST_DEATH_TEST_MATCHES(Outcome::UnexpectedErrorMessage, assertion_death_cause, bad_matcher, fail_assert()); 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. // Invalid cause -- child did not die.
TEST_DEATH_TEST(Outcome::InvalidCause, DeathCause::DidNotDie, ((void)0)); 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_DEATH_MATCHES(simple_matcher, invoke_verbose_abort());
EXPECT_STD_ABORT(invoke_abort()); EXPECT_STD_ABORT(invoke_abort());
EXPECT_STD_TERMINATE([] { std::terminate(); }); EXPECT_STD_TERMINATE([] { std::terminate(); });
#if _LIBCPP_ASSERTION_SEMANTIC != _LIBCPP_ASSERTION_SEMANTIC_OBSERVE
TEST_LIBCPP_ASSERT_FAILURE(fail_assert(), "Some message"); TEST_LIBCPP_ASSERT_FAILURE(fail_assert(), "Some message");
#endif
} }
return 0; return 0;

View File

@ -442,6 +442,12 @@ generic-hardening-mode-extensive)
check-runtimes check-runtimes
check-abi-list 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) generic-hardening-mode-debug)
clean clean
generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-hardening-mode-debug.cmake" generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-hardening-mode-debug.cmake"

View File

@ -454,5 +454,24 @@ DEFAULT_PARAMETERS = [
help="Whether to test the main or C++03-specific headers. Only changes behaviour when std=c++03.", 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")], 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 # fmt: on

View File

@ -16,6 +16,7 @@
# include <__cxx03/__verbose_trap> # include <__cxx03/__verbose_trap>
#else #else
# include <__config> # include <__config>
# include <__log_hardening_failure>
# include <__verbose_abort> # include <__verbose_abort>
# include <__verbose_trap> # include <__verbose_trap>
#endif #endif
@ -24,14 +25,40 @@
# pragma GCC system_header # pragma GCC system_header
#endif #endif
#if _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG #if __cplusplus < 201103L && defined(_LIBCPP_USE_FROZEN_CXX03_HEADERS)
# define _LIBCPP_ASSERTION_HANDLER(message) _LIBCPP_VERBOSE_ABORT("%s", message) // 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 #else
# define _LIBCPP_ASSERTION_HANDLER(message) _LIBCPP_VERBOSE_TRAP(message) # if _LIBCPP_ASSERTION_SEMANTIC == _LIBCPP_ASSERTION_SEMANTIC_IGNORE
# define _LIBCPP_ASSERTION_HANDLER(message) ((void)0)
#endif // _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG # 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)
# 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 #endif // _LIBCPP___ASSERTION_HANDLER

View File

@ -19,6 +19,14 @@
#include "../abort_message.h" #include "../abort_message.h"
#endif #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> #include <version>
#ifdef _MSC_VER #ifdef _MSC_VER