Skip to content

Commit 77aca80

Browse files
var-consttru
authored andcommitted
[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 4072a6b commit 77aca80

File tree

16 files changed

+348
-33
lines changed

16 files changed

+348
-33
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: 85 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,19 @@ 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+
.. warning::
78+
79+
Assertion semantics are currently an experimental feature.
80+
81+
.. note::
82+
83+
Assertion semantics are not available in the C++03 mode.
84+
85+
In some cases, users might want to override the assertion semantic used by the
86+
library. This can be done similarly to setting the hardening mode; please refer
87+
to the :ref:`relevant section <assertion-semantics>`. Note that this feature is
88+
currently experimental.
89+
7590
Notes for vendors
7691
-----------------
7792

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

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

265350
Overriding the assertion failure handler

libcxx/docs/ReleaseNotes/21.rst

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

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

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

libcxx/docs/UserDocumentation.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ when ``-fexperimental-library`` is passed:
7272
* ``std::chrono::tzdb`` and related time zone functionality
7373
* ``<syncstream>``
7474

75+
Additionally, assertion semantics are an experimental feature that can be used
76+
to customize the behavior of Hardening (see :ref:`here <assertion-semantics>`).
77+
Assertion semantics mirror the evaluation semantics of C++26 Contracts but are
78+
not a standard feature.
79+
7580
.. note::
7681
Experimental libraries are experimental.
7782
* The contents of the ``<experimental/...>`` headers and the associated static

libcxx/include/__config

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,48 @@ _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+
# else
183+
# if !_LIBCPP_HAS_EXPERIMENTAL_LIBRARY
184+
# error "Assertion semantics are an experimental feature."
185+
# endif // ! _LIBCPP_HAS_EXPERIMENTAL_LIBRARY
186+
# if defined(_LIBCPP_CXX03_LANG)
187+
# error "Assertion semantics are not available in the C++03 mode."
188+
# endif // defined(_LIBCPP_CXX03_LANG)
189+
190+
# endif // _LIBCPP_ASSERTION_SEMANTIC
191+
150192
// } HARDENING
151193

152194
# define _LIBCPP_TOSTRING2(x) #x

libcxx/test/libcxx/thread/thread.barrier/assert.arrive.pass.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
// UNSUPPORTED: no-threads
99
// UNSUPPORTED: c++03, c++11, c++14, c++17
1010
// REQUIRES: libcpp-hardening-mode={{extensive|debug}}
11+
// Without the assertion, the test will most likely time out.
12+
// UNSUPPORTED: libcpp-assertion-semantic={{ignore|observe}}
1113

1214
// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
1315

libcxx/test/libcxx/thread/thread.latch/assert.arrive_and_wait.pass.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
// REQUIRES: has-unix-headers
2020
// REQUIRES: libcpp-hardening-mode={{extensive|debug}}
21+
// Without the assertion, the test will most likely time out.
22+
// UNSUPPORTED: libcpp-assertion-semantic={{ignore|observe}}
2123
// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
2224

2325
#include <latch>

libcxx/test/libcxx/thread/thread.latch/assert.ctor.pass.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@
2424

2525
#include "check_assertion.h"
2626

27-
int main(int, char **) {
27+
int main(int, char**) {
2828
{
29-
TEST_LIBCPP_ASSERT_FAILURE([]{ std::latch l(-1); }(),
30-
"latch::latch(ptrdiff_t): latch cannot be "
31-
"initialized with a negative value");
29+
TEST_LIBCPP_ASSERT_FAILURE(
30+
[] { [[maybe_unused]] std::latch l(-1); }(),
31+
"latch::latch(ptrdiff_t): latch cannot be "
32+
"initialized with a negative value");
3233
}
3334

3435
// We can't check the precondition for max() because there's no value

libcxx/test/libcxx/thread/thread.semaphore/assert.ctor.pass.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
int main(int, char**) {
2727
{
2828
TEST_LIBCPP_ASSERT_FAILURE(
29-
[] { std::counting_semaphore<> s(-1); }(),
29+
[] { [[maybe_unused]] std::counting_semaphore<> s(-1); }(),
3030
"counting_semaphore::counting_semaphore(ptrdiff_t): counting_semaphore cannot be "
3131
"initialized with a negative value");
3232
}

0 commit comments

Comments
 (0)