Skip to content

Commit 2ad957b

Browse files
committed
[libc++][hardening] Introduce assertion semantics
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).
1 parent e227fce commit 2ad957b

21 files changed

+299
-62
lines changed

libcxx/docs/Hardening.rst

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ modes are:
3939

4040
Enabling hardening has no impact on the ABI.
4141

42+
.. _notes-for-users:
43+
4244
Notes for users
4345
---------------
4446

@@ -72,6 +74,10 @@ to control the level by passing **one** of the following options to the compiler
7274
pre-built components. Most libc++ code is header-based, so a user-provided
7375
value for ``_LIBCPP_HARDENING_MODE`` will be mostly respected.
7476

77+
In some cases, users might want to override the default assertion semantic.
78+
This can be done similarly to setting the hardening mode; please refer to the
79+
:ref:`relevant section <assertion-semantics>`.
80+
7581
Notes for vendors
7682
-----------------
7783

@@ -260,6 +266,60 @@ output. This is less secure and increases the size of the binary (among other
260266
things, it has to store the error message strings) but makes the failure easier
261267
to debug. It also allows testing the error messages in our test suite.
262268

269+
This default behavior can be customized by users via :ref:`assertion semantics
270+
<assertion-semantics>`; it can also be completely overridden by vendors by
271+
providing a :ref:`custom assertion failure handler
272+
<override-assertion-handler>`.
273+
274+
.. _assertion-semantics:
275+
276+
Assertion semantics
277+
-------------------
278+
279+
What happens when an assertion fails depends on the assertion semantic being
280+
used. Four assertion semantics are available, based on C++26 Contracts
281+
evaluation semantics:
282+
283+
- ``ignore`` evaluates the assertion but has no effect if it fails (note that it
284+
differs from the Contracts ``ignore`` semantic which would not evaluate
285+
the assertion at all);
286+
- ``observe`` logs an error (indicating, if possible on the platform, that the
287+
error is fatal) but continues execution;
288+
- ``quick-enforce`` terminates the program as fast as possible via a trap
289+
instruction. It is the default semantic for the production modes (``fast`` and
290+
``extensive``);
291+
- ``enforce`` logs an error and then terminates the program. It is the default
292+
semantic for the ``debug`` mode.
293+
294+
Notes:
295+
296+
- Continuing execution after a hardening check fails results in undefined
297+
behavior; the ``observe`` semantic is meant to make adopting hardening easier
298+
but should not be used outside of the adoption period;
299+
- C++26 wording for Library Hardening precludes a conforming Hardened
300+
implementation from using the Contracts ``ignore`` semantic when evaluating
301+
hardened preconditions in the Library. Libc++ allows using this semantic for
302+
hardened preconditions, but please be aware that using ``ignore`` does not
303+
produce a conforming "Hardened" implementation, unlike the other semantics
304+
above.
305+
306+
The default assertion semantics are as follows:
307+
308+
- ``fast``: ``quick-enforce``;
309+
- ``extensive``: ``quick-enforce``;
310+
- ``debug``: ``enforce``.
311+
312+
The default assertion semantics can be overridden by passing **one** of the
313+
following options to the compiler:
314+
315+
- ``-D_LIBCPP_ASSERTION_SEMANTIC=_LIBCPP_ASSERTION_SEMANTIC_IGNORE``
316+
- ``-D_LIBCPP_ASSERTION_SEMANTIC=_LIBCPP_ASSERTION_SEMANTIC_OBSERVE``
317+
- ``-D_LIBCPP_ASSERTION_SEMANTIC=_LIBCPP_ASSERTION_SEMANTIC_QUICK_ENFORCE``
318+
- ``-D_LIBCPP_ASSERTION_SEMANTIC=_LIBCPP_ASSERTION_SEMANTIC_ENFORCE``
319+
320+
All the :ref:`same notes <notes-for-users>` apply to setting this macro as for
321+
setting ``_LIBCPP_HARDENING_MODE``.
322+
263323
.. _override-assertion-handler:
264324

265325
Overriding the assertion failure handler

libcxx/docs/ReleaseNotes/21.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ Improvements and New Features
8888

8989
- ``ctype::tolower`` and ``ctype::toupper`` have been optimized, resulting in a 2x performance improvement.
9090

91+
- Hardening now supports assertion semantics that allow customizing how a hardening assertion failure is handled. The
92+
four available semantics, modeled on C++26 Contracts, are ``ignore``, ``observe``, ``quick-enforce`` and ``enforce``.
93+
The ``observe`` semantic is intended to make it easier to adopt Hardening in production but should not be used outside
94+
of this scenario. Please refer to the :ref:`Hardening documentation <hardening>` for details.
95+
9196
Deprecations and Removals
9297
-------------------------
9398

libcxx/include/__config

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,40 @@ _LIBCPP_HARDENING_MODE_EXTENSIVE, \
147147
_LIBCPP_HARDENING_MODE_DEBUG
148148
# endif
149149

150+
// Hardening assertion semantics generally mirror the evaluation semantics of C++26 Contracts:
151+
// - `ignore` evaluates the assertion but doesn't do anything if it fails (note that it differs from the Contracts
152+
// `ignore` semantic which wouldn't evaluate the assertion at all);
153+
// - `observe` logs an error (indicating, if possible, that the error is fatal) and continues execution;
154+
// - `quick-enforce` terminates the program as fast as possible (via trapping);
155+
// - `enforce` logs an error and then terminates the program.
156+
//
157+
// Notes:
158+
// - Continuing execution after a hardening check fails results in undefined behavior; the `observe` semantic is meant
159+
// to make adopting hardening easier but should not be used outside of this scenario;
160+
// - C++26 wording for Library Hardening precludes a conforming Hardened implementation from using the Contracts
161+
// `ignore` semantic when evaluating hardened preconditions in the Library. Libc++ allows using this semantic for
162+
// hardened preconditions, however, be aware that using `ignore` does not produce a conforming "Hardened"
163+
// implementation, unlike the other semantics above.
164+
// clang-format off
165+
# define _LIBCPP_ASSERTION_SEMANTIC_IGNORE (1 << 1)
166+
# define _LIBCPP_ASSERTION_SEMANTIC_OBSERVE (1 << 2)
167+
# define _LIBCPP_ASSERTION_SEMANTIC_QUICK_ENFORCE (1 << 3)
168+
# define _LIBCPP_ASSERTION_SEMANTIC_ENFORCE (1 << 4)
169+
// clang-format on
170+
171+
// Allow users to define an arbitrary assertion semantic; otherwise, use the default mapping from modes to semantics.
172+
// The default is for production-capable modes to use `quick-enforce` (i.e., trap) and for the `debug` mode to use
173+
// `enforce` (i.e., log and abort).
174+
# ifndef _LIBCPP_ASSERTION_SEMANTIC
175+
176+
# if _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG
177+
# define _LIBCPP_ASSERTION_SEMANTIC _LIBCPP_ASSERTION_SEMANTIC_ENFORCE
178+
# else
179+
# define _LIBCPP_ASSERTION_SEMANTIC _LIBCPP_ASSERTION_SEMANTIC_QUICK_ENFORCE
180+
# endif
181+
182+
# endif // _LIBCPP_ASSERTION_SEMANTIC
183+
150184
// } HARDENING
151185

152186
# define _LIBCPP_TOSTRING2(x) #x

libcxx/test/libcxx/containers/views/mdspan/extents/assert.ctor_from_array.pass.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,17 @@ int main(int, char**) {
4343
}
4444
// mismatch of static extent
4545
{
46-
TEST_LIBCPP_ASSERT_FAILURE(([] { std::extents<int, D, 5> e1(std::array{1000, 3}); }()),
46+
TEST_LIBCPP_ASSERT_FAILURE(([] { [[maybe_unused]] std::extents<int, D, 5> e1(std::array{1000, 3}); }()),
4747
"extents construction: mismatch of provided arguments with static extents.");
4848
}
4949
// value out of range
5050
{
51-
TEST_LIBCPP_ASSERT_FAILURE(([] { std::extents<signed char, D, 5> e1(std::array{1000, 5}); }()),
51+
TEST_LIBCPP_ASSERT_FAILURE(([] { [[maybe_unused]] std::extents<signed char, D, 5> e1(std::array{1000, 5}); }()),
5252
"extents ctor: arguments must be representable as index_type and nonnegative");
5353
}
5454
// negative value
5555
{
56-
TEST_LIBCPP_ASSERT_FAILURE(([] { std::extents<signed char, D, 5> e1(std::array{-1, 5}); }()),
56+
TEST_LIBCPP_ASSERT_FAILURE(([] { [[maybe_unused]] std::extents<signed char, D, 5> e1(std::array{-1, 5}); }()),
5757
"extents ctor: arguments must be representable as index_type and nonnegative");
5858
}
5959
return 0;

libcxx/test/libcxx/containers/views/mdspan/extents/assert.ctor_from_integral.pass.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,17 @@ int main(int, char**) {
4545
}
4646
// mismatch of static extent
4747
{
48-
TEST_LIBCPP_ASSERT_FAILURE(([] { std::extents<int, D, 5> e1(1000, 3); }()),
48+
TEST_LIBCPP_ASSERT_FAILURE(([] { [[maybe_unused]] std::extents<int, D, 5> e1(1000, 3); }()),
4949
"extents construction: mismatch of provided arguments with static extents.");
5050
}
5151
// value out of range
5252
{
53-
TEST_LIBCPP_ASSERT_FAILURE(([] { std::extents<signed char, D, 5> e1(1000, 5); }()),
53+
TEST_LIBCPP_ASSERT_FAILURE(([] { [[maybe_unused]] std::extents<signed char, D, 5> e1(1000, 5); }()),
5454
"extents ctor: arguments must be representable as index_type and nonnegative");
5555
}
5656
// negative value
5757
{
58-
TEST_LIBCPP_ASSERT_FAILURE(([] { std::extents<signed char, D, 5> e1(-1, 5); }()),
58+
TEST_LIBCPP_ASSERT_FAILURE(([] { [[maybe_unused]] std::extents<signed char, D, 5> e1(-1, 5); }()),
5959
"extents ctor: arguments must be representable as index_type and nonnegative");
6060
}
6161
return 0;

libcxx/test/libcxx/containers/views/mdspan/layout_left/assert.conversion.pass.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ int main(int, char**) {
4444
{
4545
TEST_LIBCPP_ASSERT_FAILURE(
4646
([=] {
47-
std::layout_left::mapping<std::extents<signed char, D>> m(
47+
[[maybe_unused]] std::layout_left::mapping<std::extents<signed char, D>> m(
4848
std::layout_left::mapping<std::extents<int, D>>(std::extents<int, D>(500)));
4949
}()),
5050
"extents ctor: arguments must be representable as index_type and nonnegative");
@@ -55,7 +55,7 @@ int main(int, char**) {
5555
[[maybe_unused]] std::extents<signed char, D, 5> e(arg_exts);
5656
// but the product is not, so we can't use it for layout_left
5757
TEST_LIBCPP_ASSERT_FAILURE(
58-
([=] { std::layout_left::mapping<std::extents<signed char, D, 5>> m(arg); }()),
58+
([=] { [[maybe_unused]] std::layout_left::mapping<std::extents<signed char, D, 5>> m(arg); }()),
5959
"layout_left::mapping converting ctor: other.required_span_size() must be representable as index_type.");
6060
}
6161
return 0;

libcxx/test/libcxx/containers/views/mdspan/layout_left/assert.ctor.extents.pass.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@ int main(int, char**) {
3131
{
3232
// the extents are representable but the product is not, so we can't use it for layout_left
3333
TEST_LIBCPP_ASSERT_FAILURE(
34-
([=] { std::layout_left::mapping<std::extents<signed char, D, 5>> m(std::extents<signed char, D, 5>(100)); }()),
34+
([=] {
35+
[[maybe_unused]] std::layout_left::mapping<std::extents<signed char, D, 5>> m(
36+
std::extents<signed char, D, 5>(100));
37+
}()),
3538
"layout_left::mapping extents ctor: product of extents must be representable as index_type.");
3639
}
3740
return 0;

libcxx/test/libcxx/containers/views/mdspan/layout_left/assert.ctor.layout_right.pass.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,14 @@ int main(int, char**) {
3939
}
4040
// mismatch of static extent
4141
{
42-
TEST_LIBCPP_ASSERT_FAILURE(([=] { std::layout_left::mapping<std::extents<int, 3>> m(arg); }()),
42+
TEST_LIBCPP_ASSERT_FAILURE(([=] { [[maybe_unused]] std::layout_left::mapping<std::extents<int, 3>> m(arg); }()),
4343
"extents construction: mismatch of provided arguments with static extents.");
4444
}
4545
// non-representability of extents itself
4646
{
4747
TEST_LIBCPP_ASSERT_FAILURE(
4848
([=] {
49-
std::layout_left::mapping<std::extents<signed char, D>> m(
49+
[[maybe_unused]] std::layout_left::mapping<std::extents<signed char, D>> m(
5050
std::layout_right::mapping<std::extents<int, D>>(std::extents<int, D>(500)));
5151
}()),
5252
"extents ctor: arguments must be representable as index_type and nonnegative");

libcxx/test/libcxx/containers/views/mdspan/layout_right/assert.conversion.pass.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@ int main(int, char**) {
3737
}
3838
// mismatch of static extent
3939
{
40-
TEST_LIBCPP_ASSERT_FAILURE(([=] { std::layout_right::mapping<std::extents<int, D, 3>> m(arg); }()),
40+
TEST_LIBCPP_ASSERT_FAILURE(([=] { [[maybe_unused]] std::layout_right::mapping<std::extents<int, D, 3>> m(arg); }()),
4141
"extents construction: mismatch of provided arguments with static extents.");
4242
}
4343
// non-representability of extents itself
4444
{
4545
TEST_LIBCPP_ASSERT_FAILURE(
4646
([=] {
47-
std::layout_right::mapping<std::extents<signed char, D>> m(
47+
[[maybe_unused]] std::layout_right::mapping<std::extents<signed char, D>> m(
4848
std::layout_right::mapping<std::extents<int, D>>(std::extents<int, D>(500)));
4949
}()),
5050
"extents ctor: arguments must be representable as index_type and nonnegative");
@@ -55,7 +55,7 @@ int main(int, char**) {
5555
[[maybe_unused]] std::extents<signed char, D, 5> e(arg_exts);
5656
// but the product is not, so we can't use it for layout_right
5757
TEST_LIBCPP_ASSERT_FAILURE(
58-
([=] { std::layout_right::mapping<std::extents<signed char, D, 5>> m(arg); }()),
58+
([=] { [[maybe_unused]] std::layout_right::mapping<std::extents<signed char, D, 5>> m(arg); }()),
5959
"layout_right::mapping converting ctor: other.required_span_size() must be representable as index_type.");
6060
}
6161
return 0;

libcxx/test/libcxx/containers/views/mdspan/layout_right/assert.ctor.extents.pass.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ int main(int, char**) {
3232
// the extents are representable but the product is not, so we can't use it for layout_right
3333
TEST_LIBCPP_ASSERT_FAILURE(
3434
([=] {
35-
std::layout_right::mapping<std::extents<signed char, D, 5>> m(std::extents<signed char, D, 5>(100));
35+
[[maybe_unused]] std::layout_right::mapping<std::extents<signed char, D, 5>> m(
36+
std::extents<signed char, D, 5>(100));
3637
}()),
3738
"layout_right::mapping extents ctor: product of extents must be representable as index_type.");
3839
}

0 commit comments

Comments
 (0)