[libcxx-commits] [libcxx] [libc++][hardening] Rework how the assertion handler can be overridden. (PR #77883)

Konstantin Varlamov via libcxx-commits libcxx-commits at lists.llvm.org
Thu Jan 11 23:24:41 PST 2024


https://github.com/var-const created https://github.com/llvm/llvm-project/pull/77883

Previously there were two ways to override the verbose abort function
which gets called when a hardening assertion is triggered:
- compile-time: define the `_LIBCPP_VERBOSE_ABORT` macro;
- link-time: provide a definition of `__libcpp_verbose_abort` function.

This patch adds a new configure-time approach: a vendor can provide
a path to a custom header file whose contents will get injected into the
build by CMake. The header must provide a definition of the
`_LIBCPP_ASSERTION_HANDLER` macro which is what will get called should
a hardening assertion fail. As of this patch, overriding
`_LIBCPP_VERBOSE_ABORT` will still work, but the previous mechanisms
will be effectively removed in a follow-up patch, making the
configure-time mechanism the sole way of overriding the default handler.

Note that `_LIBCPP_ASSERTION_HANDLER` only gets invoked when a hardening
assertion fails. It does not affect other cases where
`_LIBCPP_VERBOSE_ABORT` is currently used (e.g. when an exception is
thrown in the `-fno-exceptions` mode).

The library provides a default version of the custom header file that
will get used if it's not overridden by the vendor. That allows us to
always test the override mechanism and reduces the difference in
configuration between the pristine version of the library and
a platform-specific version.


>From e8e95c57dc5599fc1d389bdce250c3a6cfad46b2 Mon Sep 17 00:00:00 2001
From: Konstantin Varlamov <varconsteq at gmail.com>
Date: Thu, 11 Jan 2024 17:05:58 -0800
Subject: [PATCH] [libc++][hardening] Rework how the assertion handler can be
 overridden.

Previously there were two ways to override the verbose abort function
which gets called when a hardening assertion is triggered:
- compile-time: define the `_LIBCPP_VERBOSE_ABORT` macro;
- link-time: provide a definition of `__libcpp_verbose_abort` function.

This patch adds a new configure-time approach: a vendor can provide
a path to a custom header file whose contents will get injected into the
build by CMake. The header must provide a definition of the
`_LIBCPP_ASSERTION_HANDLER` macro which is what will get called should
a hardening assertion fail. As of this patch, overriding
`_LIBCPP_VERBOSE_ABORT` will still work, but the previous mechanisms
will be effectively removed in a follow-up patch, making the
configure-time mechanism the sole way of overriding the default handler.

Note that `_LIBCPP_ASSERTION_HANDLER` only gets invoked when a hardening
assertion fails. It does not affect other cases where
`_LIBCPP_VERBOSE_ABORT` is currently used (e.g. when an exception is
thrown in the `-fno-exceptions` mode).

The library provides a default version of the custom header file that
will get used if it's not overridden by the vendor. That allows us to
always test the override mechanism and reduces the difference in
configuration between the pristine version of the library and
a platform-specific version.
---
 libcxx/CMakeLists.txt                           |  7 +++++++
 libcxx/include/CMakeLists.txt                   |  9 +++++++++
 libcxx/include/__assert                         |  4 ++--
 libcxx/include/__assertion_handler.in           | 13 +++++++++++++
 libcxx/test/libcxx/lint/lint_headers.sh.py      |  1 +
 libcxx/utils/generate_iwyu_mapping.py           |  2 ++
 libcxx/utils/libcxx/header_information.py       |  3 +++
 libcxx/vendor/llvm/default_assertion_handler.in | 14 ++++++++++++++
 8 files changed, 51 insertions(+), 2 deletions(-)
 create mode 100644 libcxx/include/__assertion_handler.in
 create mode 100644 libcxx/vendor/llvm/default_assertion_handler.in

diff --git a/libcxx/CMakeLists.txt b/libcxx/CMakeLists.txt
index 75cb63222da35c..b09836a6ab69c8 100644
--- a/libcxx/CMakeLists.txt
+++ b/libcxx/CMakeLists.txt
@@ -69,6 +69,13 @@ if (NOT "${LIBCXX_HARDENING_MODE}" IN_LIST LIBCXX_SUPPORTED_HARDENING_MODES)
   message(FATAL_ERROR
     "Unsupported hardening mode: '${LIBCXX_HARDENING_MODE}'. Supported values are ${LIBCXX_SUPPORTED_HARDENING_MODES}.")
 endif()
+set(LIBCXX_ASSERTION_HANDLER_FILE
+  "${CMAKE_CURRENT_SOURCE_DIR}/vendor/llvm/default_assertion_handler.in"
+  CACHE STRING
+  "Specify the path to a header that contains a custom implementation of the
+   assertion handler that gets invoked when a hardening assertion fails. If
+   provided, the contents of this header will get injected into the library code
+   and override the default assertion handler.")
 option(LIBCXX_ENABLE_RANDOM_DEVICE
   "Whether to include support for std::random_device in the library. Disabling
    this can be useful when building the library for platforms that don't have
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 0fe3ab44d2466e..efbce0fee847d7 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -1019,9 +1019,12 @@ foreach(feature LIBCXX_ENABLE_FILESYSTEM LIBCXX_ENABLE_LOCALIZATION LIBCXX_ENABL
 endforeach()
 
 configure_file("__config_site.in" "${LIBCXX_GENERATED_INCLUDE_TARGET_DIR}/__config_site" @ONLY)
+file(READ ${LIBCXX_ASSERTION_HANDLER_FILE} _LIBCPP_ASSERTION_HANDLER_OVERRIDE)
+configure_file("__assertion_handler.in" "${LIBCXX_GENERATED_INCLUDE_TARGET_DIR}/__assertion_handler" @ONLY)
 configure_file("module.modulemap.in" "${LIBCXX_GENERATED_INCLUDE_DIR}/module.modulemap" @ONLY)
 
 set(_all_includes "${LIBCXX_GENERATED_INCLUDE_TARGET_DIR}/__config_site"
+                  "${LIBCXX_GENERATED_INCLUDE_TARGET_DIR}/__assertion_handler"
                   "${LIBCXX_GENERATED_INCLUDE_DIR}/module.modulemap")
 foreach(f ${files})
   set(src "${CMAKE_CURRENT_SOURCE_DIR}/${f}")
@@ -1059,6 +1062,12 @@ if (LIBCXX_INSTALL_HEADERS)
     PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ
     COMPONENT cxx-headers)
 
+  # Install the generated __assertion_handler file to the per-target include dir.
+  install(FILES "${LIBCXX_GENERATED_INCLUDE_TARGET_DIR}/__assertion_handler"
+    DESTINATION "${LIBCXX_INSTALL_INCLUDE_TARGET_DIR}"
+    PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ
+    COMPONENT cxx-headers)
+
   # Install the generated modulemap file to the generic include dir.
   install(FILES "${LIBCXX_GENERATED_INCLUDE_DIR}/module.modulemap"
     DESTINATION "${LIBCXX_INSTALL_INCLUDE_DIR}"
diff --git a/libcxx/include/__assert b/libcxx/include/__assert
index d4af7e6c7192ab..3c49c96f65a3e4 100644
--- a/libcxx/include/__assert
+++ b/libcxx/include/__assert
@@ -10,8 +10,8 @@
 #ifndef _LIBCPP___ASSERT
 #define _LIBCPP___ASSERT
 
+#include <__assertion_handler>
 #include <__config>
-#include <__verbose_abort>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -20,7 +20,7 @@
 #define _LIBCPP_ASSERT(expression, message)                                                                            \
   (__builtin_expect(static_cast<bool>(expression), 1)                                                                  \
        ? (void)0                                                                                                       \
-       : _LIBCPP_VERBOSE_ABORT(                                                                                        \
+       : _LIBCPP_ASSERTION_HANDLER(                                                                                    \
              "%s:%d: assertion %s failed: %s\n", __builtin_FILE(), __builtin_LINE(), #expression, message))
 
 // TODO: __builtin_assume can currently inhibit optimizations. Until this has been fixed and we can add
diff --git a/libcxx/include/__assertion_handler.in b/libcxx/include/__assertion_handler.in
new file mode 100644
index 00000000000000..59342a3b0be891
--- /dev/null
+++ b/libcxx/include/__assertion_handler.in
@@ -0,0 +1,13 @@
+// -*- C++ -*-
+#ifndef _LIBCPP___ASSERTION_HANDLER
+#define _LIBCPP___ASSERTION_HANDLER
+
+#include <__config>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+ at _LIBCPP_ASSERTION_HANDLER_OVERRIDE@
+
+#endif // _LIBCPP___ASSERTION_HANDLER
diff --git a/libcxx/test/libcxx/lint/lint_headers.sh.py b/libcxx/test/libcxx/lint/lint_headers.sh.py
index ab237c968da7e1..aee10e13b9560c 100644
--- a/libcxx/test/libcxx/lint/lint_headers.sh.py
+++ b/libcxx/test/libcxx/lint/lint_headers.sh.py
@@ -14,6 +14,7 @@ def exclude_from_consideration(path):
         or path.endswith(".modulemap.in")
         or os.path.basename(path) == "__config"
         or os.path.basename(path) == "__config_site.in"
+        or os.path.basename(path) == "__assertion_handler.in"
         or os.path.basename(path) == "libcxx.imp"
         or os.path.basename(path).startswith("__pstl")
         or not os.path.isfile(path)  # TODO: Remove once PSTL integration is finished
diff --git a/libcxx/utils/generate_iwyu_mapping.py b/libcxx/utils/generate_iwyu_mapping.py
index 343538a6cae481..d5d1577e4a2ad6 100644
--- a/libcxx/utils/generate_iwyu_mapping.py
+++ b/libcxx/utils/generate_iwyu_mapping.py
@@ -43,6 +43,8 @@ def generate_map(include):
         public = []
         if i == "__assert":
             continue
+        elif i == "__assertion_handler.in":
+            continue
         elif i == "__availability":
             continue
         elif i == "__bit_reference":
diff --git a/libcxx/utils/libcxx/header_information.py b/libcxx/utils/libcxx/header_information.py
index 54e18b5ea533dd..326931b0081c00 100644
--- a/libcxx/utils/libcxx/header_information.py
+++ b/libcxx/utils/libcxx/header_information.py
@@ -168,6 +168,9 @@ def is_modulemap_header(header):
     if header == "__config_site":
         return False
 
+    if header == "__assertion_handler":
+        return False
+
     # exclude libc++abi files
     if header in ["cxxabi.h", "__cxxabi_config.h"]:
         return False
diff --git a/libcxx/vendor/llvm/default_assertion_handler.in b/libcxx/vendor/llvm/default_assertion_handler.in
new file mode 100644
index 00000000000000..2422b6dadbe644
--- /dev/null
+++ b/libcxx/vendor/llvm/default_assertion_handler.in
@@ -0,0 +1,14 @@
+// -*- C++ -*-
+#include <__config>
+#include <__verbose_abort>
+
+#if _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG
+
+#define _LIBCPP_ASSERTION_HANDLER(...) _LIBCPP_VERBOSE_ABORT(__VA_ARGS__)
+
+#else
+
+// TODO(hardening): in production, trap rather than abort.
+#define _LIBCPP_ASSERTION_HANDLER(...) _LIBCPP_VERBOSE_ABORT(__VA_ARGS__)
+
+#endif // #if _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG



More information about the libcxx-commits mailing list