Skip to content

Commit e4e7c6c

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 users).
1 parent 48c191d commit e4e7c6c

28 files changed

+353
-69
lines changed

.github/workflows/libcxx-build-and-test.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ jobs:
128128
'generic-abi-unstable',
129129
'generic-hardening-mode-debug',
130130
'generic-hardening-mode-extensive',
131+
'generic-hardening-mode-extensive-observe-semantic',
131132
'generic-hardening-mode-fast',
132133
'generic-hardening-mode-fast-with-abi-breaks',
133134
'generic-merged',
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
set(LIBCXX_HARDENING_MODE "extensive" CACHE STRING "")
2+
set(LIBCXX_TEST_PARAMS "assertion_semantic=observe" CACHE STRING "")

libcxx/docs/Hardening.rst

Lines changed: 61 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,11 @@ 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 assertion semantic used by the
78+
library.
79+
This can be done similarly to setting the hardening mode; please refer to the
80+
:ref:`relevant section <assertion-semantics>`.
81+
7582
Notes for vendors
7683
-----------------
7784

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

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

265326
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/include/__log_hardening_failure

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ void __use(const char*);
4242

4343
# endif // !defined(_LIBCPP_LOG_HARDENING_FAILURE)
4444

45-
#endif // _LIBCPP_HAS_EXPERIMENTAL_HARDENING_OBSERVE_SEMANTIC
46-
4745
_LIBCPP_END_NAMESPACE_STD
4846

47+
#endif // _LIBCPP_HAS_EXPERIMENTAL_HARDENING_OBSERVE_SEMANTIC
48+
4949
#endif // _LIBCPP___LOG_HARDENING_FAILURE

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;

0 commit comments

Comments
 (0)