Skip to content

Commit 6943f1b

Browse files
var-consttru
authored andcommitted
[libc++][hardening] Introduce assertion semantics. (#149459)
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). (cherry picked from commit 3eee9fc)
1 parent 8c3ef23 commit 6943f1b

File tree

15 files changed

+353
-52
lines changed

15 files changed

+353
-52
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: 68 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 assertion semantic used by the
78+
library. This can be done similarly to setting the hardening mode; please refer
79+
to the :ref:`relevant section <assertion-semantics>`.
80+
7581
Notes for vendors
7682
-----------------
7783

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

265333
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: 66 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,30 @@
3838
# define _LIBCPP_FREESTANDING
3939
# endif
4040

41+
// NOLINTNEXTLINE(libcpp-cpp-version-check)
42+
# if __cplusplus < 201103L
43+
# define _LIBCPP_CXX03_LANG
44+
# endif
45+
46+
# if __has_feature(experimental_library)
47+
# ifndef _LIBCPP_ENABLE_EXPERIMENTAL
48+
# define _LIBCPP_ENABLE_EXPERIMENTAL
49+
# endif
50+
# endif
51+
52+
// Incomplete features get their own specific disabling flags. This makes it
53+
// easier to grep for target specific flags once the feature is complete.
54+
# if defined(_LIBCPP_ENABLE_EXPERIMENTAL) || defined(_LIBCPP_BUILDING_LIBRARY)
55+
# define _LIBCPP_HAS_EXPERIMENTAL_LIBRARY 1
56+
# else
57+
# define _LIBCPP_HAS_EXPERIMENTAL_LIBRARY 0
58+
# endif
59+
60+
# define _LIBCPP_HAS_EXPERIMENTAL_PSTL _LIBCPP_HAS_EXPERIMENTAL_LIBRARY
61+
# define _LIBCPP_HAS_EXPERIMENTAL_TZDB _LIBCPP_HAS_EXPERIMENTAL_LIBRARY
62+
# define _LIBCPP_HAS_EXPERIMENTAL_SYNCSTREAM _LIBCPP_HAS_EXPERIMENTAL_LIBRARY
63+
# define _LIBCPP_HAS_EXPERIMENTAL_HARDENING_OBSERVE_SEMANTIC _LIBCPP_HAS_EXPERIMENTAL_LIBRARY
64+
4165
// HARDENING {
4266

4367
// TODO: Remove in LLVM 21. We're making this an error to catch folks who might not have migrated.
@@ -147,16 +171,53 @@ _LIBCPP_HARDENING_MODE_EXTENSIVE, \
147171
_LIBCPP_HARDENING_MODE_DEBUG
148172
# endif
149173

174+
// Hardening assertion semantics generally mirror the evaluation semantics of C++26 Contracts:
175+
// - `ignore` evaluates the assertion but doesn't do anything if it fails (note that it differs from the Contracts
176+
// `ignore` semantic which wouldn't evaluate the assertion at all);
177+
// - `observe` logs an error (indicating, if possible, that the error is fatal) and continues execution;
178+
// - `quick-enforce` terminates the program as fast as possible (via trapping);
179+
// - `enforce` logs an error and then terminates the program.
180+
//
181+
// Notes:
182+
// - Continuing execution after a hardening check fails results in undefined behavior; the `observe` semantic is meant
183+
// to make adopting hardening easier but should not be used outside of this scenario;
184+
// - C++26 wording for Library Hardening precludes a conforming Hardened implementation from using the Contracts
185+
// `ignore` semantic when evaluating hardened preconditions in the Library. Libc++ allows using this semantic for
186+
// hardened preconditions, however, be aware that using `ignore` does not produce a conforming "Hardened"
187+
// implementation, unlike the other semantics above.
188+
// clang-format off
189+
# define _LIBCPP_ASSERTION_SEMANTIC_IGNORE (1 << 1)
190+
# define _LIBCPP_ASSERTION_SEMANTIC_OBSERVE (1 << 2)
191+
# define _LIBCPP_ASSERTION_SEMANTIC_QUICK_ENFORCE (1 << 3)
192+
# define _LIBCPP_ASSERTION_SEMANTIC_ENFORCE (1 << 4)
193+
// clang-format on
194+
195+
// Allow users to define an arbitrary assertion semantic; otherwise, use the default mapping from modes to semantics.
196+
// The default is for production-capable modes to use `quick-enforce` (i.e., trap) and for the `debug` mode to use
197+
// `enforce` (i.e., log and abort).
198+
# ifndef _LIBCPP_ASSERTION_SEMANTIC
199+
200+
# if _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG
201+
# define _LIBCPP_ASSERTION_SEMANTIC _LIBCPP_ASSERTION_SEMANTIC_ENFORCE
202+
# else
203+
# define _LIBCPP_ASSERTION_SEMANTIC _LIBCPP_ASSERTION_SEMANTIC_QUICK_ENFORCE
204+
# endif
205+
206+
# else
207+
# if !_LIBCPP_HAS_EXPERIMENTAL_LIBRARY
208+
# error "Assertion semantics are an experimental feature."
209+
# endif
210+
# if defined(_LIBCPP_CXX03_LANG)
211+
# error "Assertion semantics are not available in the C++03 mode."
212+
# endif
213+
214+
# endif // _LIBCPP_ASSERTION_SEMANTIC
215+
150216
// } HARDENING
151217

152218
# define _LIBCPP_TOSTRING2(x) #x
153219
# define _LIBCPP_TOSTRING(x) _LIBCPP_TOSTRING2(x)
154220

155-
// NOLINTNEXTLINE(libcpp-cpp-version-check)
156-
# if __cplusplus < 201103L
157-
# define _LIBCPP_CXX03_LANG
158-
# endif
159-
160221
# ifndef __has_constexpr_builtin
161222
# define __has_constexpr_builtin(x) 0
162223
# endif
@@ -190,25 +251,6 @@ _LIBCPP_HARDENING_MODE_DEBUG
190251
# define _LIBCPP_ABI_VCRUNTIME
191252
# endif
192253

193-
# if __has_feature(experimental_library)
194-
# ifndef _LIBCPP_ENABLE_EXPERIMENTAL
195-
# define _LIBCPP_ENABLE_EXPERIMENTAL
196-
# endif
197-
# endif
198-
199-
// Incomplete features get their own specific disabling flags. This makes it
200-
// easier to grep for target specific flags once the feature is complete.
201-
# if defined(_LIBCPP_ENABLE_EXPERIMENTAL) || defined(_LIBCPP_BUILDING_LIBRARY)
202-
# define _LIBCPP_HAS_EXPERIMENTAL_LIBRARY 1
203-
# else
204-
# define _LIBCPP_HAS_EXPERIMENTAL_LIBRARY 0
205-
# endif
206-
207-
# define _LIBCPP_HAS_EXPERIMENTAL_PSTL _LIBCPP_HAS_EXPERIMENTAL_LIBRARY
208-
# define _LIBCPP_HAS_EXPERIMENTAL_TZDB _LIBCPP_HAS_EXPERIMENTAL_LIBRARY
209-
# define _LIBCPP_HAS_EXPERIMENTAL_SYNCSTREAM _LIBCPP_HAS_EXPERIMENTAL_LIBRARY
210-
# define _LIBCPP_HAS_EXPERIMENTAL_HARDENING_OBSERVE_SEMANTIC _LIBCPP_HAS_EXPERIMENTAL_LIBRARY
211-
212254
# if defined(__MVS__)
213255
# include <features.h> // for __NATIVE_ASCII_F
214256
# endif

libcxx/include/__cxx03/__config

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,10 @@ _LIBCPP_HARDENING_MODE_EXTENSIVE, \
152152
_LIBCPP_HARDENING_MODE_DEBUG
153153
# endif
154154

155+
# ifdef _LIBCPP_ASSERTION_SEMANTIC
156+
# error "Assertion semantics are not available in the C++03 mode."
157+
# endif
158+
155159
// } HARDENING
156160

157161
# 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>

0 commit comments

Comments
 (0)