diff --git a/libcxx/CMakeLists.txt b/libcxx/CMakeLists.txt --- a/libcxx/CMakeLists.txt +++ b/libcxx/CMakeLists.txt @@ -62,6 +62,16 @@ available on the platform. This includes things like most parts of and others like " ON) option(LIBCXX_INCLUDE_TESTS "Build the libc++ tests." ${LLVM_INCLUDE_TESTS}) +set(LIBCXX_SUPPORTED_HARDENING_MODES unchecked hardened debug) +set(LIBCXX_HARDENING_MODE "unchecked" CACHE STRING + "Specify the default hardening mode to use. This mode will be used inside the + compiled library and will be the default when compiling user code. Note that + users can override this setting in their own code. This does not affect the + ABI. Supported values are ${LIBCXX_SUPPORTED_HARDENING_MODES}.") +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() 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 @@ -787,6 +797,16 @@ else() config_define(0 _LIBCPP_ENABLE_ASSERTIONS_DEFAULT) endif() +if (LIBCXX_HARDENING_MODE STREQUAL "hardened") + config_define(1 _LIBCPP_ENABLE_HARDENED_MODE_DEFAULT) + config_define(0 _LIBCPP_ENABLE_DEBUG_MODE_DEFAULT) +elseif (LIBCXX_HARDENING_MODE STREQUAL "debug") + config_define(0 _LIBCPP_ENABLE_HARDENED_MODE_DEFAULT) + config_define(1 _LIBCPP_ENABLE_DEBUG_MODE_DEFAULT) +elseif (LIBCXX_HARDENING_MODE STREQUAL "unchecked") + config_define(0 _LIBCPP_ENABLE_HARDENED_MODE_DEFAULT) + config_define(0 _LIBCPP_ENABLE_DEBUG_MODE_DEFAULT) +endif() if (LIBCXX_PSTL_CPU_BACKEND STREQUAL "serial") config_define(1 _LIBCPP_PSTL_CPU_BACKEND_SERIAL) diff --git a/libcxx/cmake/caches/Generic-debug-mode.cmake b/libcxx/cmake/caches/Generic-debug-mode.cmake new file mode 100644 --- /dev/null +++ b/libcxx/cmake/caches/Generic-debug-mode.cmake @@ -0,0 +1 @@ +set(LIBCXX_HARDENING_MODE "debug" CACHE STRING "") diff --git a/libcxx/cmake/caches/Generic-hardened-mode.cmake b/libcxx/cmake/caches/Generic-hardened-mode.cmake new file mode 100644 --- /dev/null +++ b/libcxx/cmake/caches/Generic-hardened-mode.cmake @@ -0,0 +1 @@ +set(LIBCXX_HARDENING_MODE "hardened" CACHE STRING "") diff --git a/libcxx/docs/HardenedMode.rst b/libcxx/docs/HardenedMode.rst new file mode 100644 --- /dev/null +++ b/libcxx/docs/HardenedMode.rst @@ -0,0 +1,41 @@ +============= +Hardened Mode +============= + +.. contents:: + :local: + +.. _using-hardened-mode: + +Using the hardened mode +======================= + +The hardened mode enables a set of security-critical assertions that prevent +undefined behavior caused by violating preconditions of the standard library. +These assertions can be done with relatively little overhead in constant time +and are intended to be used in production. + +In addition to the hardened mode, libc++ also provides the debug mode which +contains all the checks from the hardened mode and additionally more expensive +checks that may affect the complexity of algorithms. The debug mode is intended +to be used for testing, not in production. + +Vendors can set the default hardened mode by using the ``LIBCXX_HARDENING_MODE`` +CMake variable. Setting ``LIBCXX_HARDENING_MODE`` to ``hardened`` enables the +hardened mode, and similarly setting the variable to ``debug`` enables the debug +mode. The default value is ``unchecked`` which doesn't enable the hardened mode. +Users can control whether the hardened mode or the debug mode is enabled +on a per translation unit basis by setting the ``_LIBCPP_ENABLE_HARDENED_MODE`` +or ``_LIBCPP_ENABLE_DEBUG_MODE`` macro to ``1``. + +The hardened mode requires ``LIBCXX_ENABLE_ASSERTIONS`` to work. If +``LIBCXX_ENABLE_ASSERTIONS`` was not set explicitly, enabling the hardened mode +(or the debug mode) will implicitly enable ``LIBCXX_ENABLE_ASSERTIONS``. If +``LIBCXX_ENABLE_ASSERTIONS`` was explicitly disabled, this will effectively +disable the hardened mode. + +Enabling the hardened mode (or the debug mode) has no impact on the ABI. + +Iterator bounds checking +------------------------ +TODO(hardening) diff --git a/libcxx/docs/ReleaseNotes.rst b/libcxx/docs/ReleaseNotes.rst --- a/libcxx/docs/ReleaseNotes.rst +++ b/libcxx/docs/ReleaseNotes.rst @@ -72,14 +72,25 @@ Anything that does not rely on having an actual filesystem available will now work, such as ``std::filesystem::path``, ``std::filesystem::perms`` and similar classes. +- The library now provides a hardened mode under which common cases of library undefined behavior will be turned into + a reliable program termination. Vendors can configure whether the hardened mode is enabled by default with the + ``LIBCXX_HARDENING_MODE`` variable at CMake configuration time. Users can control whether the hardened mode is + enabled on a per translation unit basis using the ``-D_LIBCPP_ENABLE_HARDENED_MODE=1`` macro. See + ``libcxx/docs/HardenedMode.rst`` for more details. + +- The library now provides a debug mode which is a superset of the hardened mode, additionally enabling more expensive + checks that are not suitable to be used in production. This replaces the legacy debug mode that was removed in this + release. Unlike the legacy debug mode, this doesn't affect the ABI and doesn't require locking. Vendors can configure + whether the debug mode is enabled by default with the ``LIBCXX_HARDENING_MODE`` variable at CMake configuration time. + Users can control whether the debug mode is enabled on a per translation unit basis using the + ``-D_LIBCPP_ENABLE_DEBUG_MODE=1`` macro. See ``libcxx/docs/HardenedMode.rst`` for more details. + Deprecations and Removals ------------------------- -- The legacy debug mode has been removed in this release. Defining the macro - `_LIBCPP_ENABLE_DEBUG_MODE` is now a no-op, and the `LIBCXX_ENABLE_DEBUG_MODE` - CMake variable has been removed. The legacy debug mode will be replaced by - finer-grained hardened modes. For additional context, refer to the `Discourse - post +- The legacy debug mode has been removed in this release. Setting the macro ``_LIBCPP_ENABLE_DEBUG_MODE`` to ``1`` now + enables the new debug mode which is part of hardening (see the "Improvements and New Features" section above). The + ``LIBCXX_ENABLE_DEBUG_MODE`` CMake variable has been removed. For additional context, refer to the `Discourse post `_. - The ```` header has been removed in this release. The ```` header diff --git a/libcxx/docs/index.rst b/libcxx/docs/index.rst --- a/libcxx/docs/index.rst +++ b/libcxx/docs/index.rst @@ -40,6 +40,7 @@ TestingLibcxx Contributing Modules + HardenedMode ReleaseProcedure Status/Cxx14 Status/Cxx17 diff --git a/libcxx/include/__algorithm/comp_ref_type.h b/libcxx/include/__algorithm/comp_ref_type.h --- a/libcxx/include/__algorithm/comp_ref_type.h +++ b/libcxx/include/__algorithm/comp_ref_type.h @@ -65,8 +65,7 @@ // Pass the comparator by lvalue reference. Or in debug mode, using a // debugging wrapper that stores a reference. -// TODO(varconst): update to be used in the new debug mode (or delete entirely). -#ifdef _LIBCPP_ENABLE_DEBUG_MODE +#if _LIBCPP_ENABLE_DEBUG_MODE template using __comp_ref_type = __debug_less<_Comp>; #else diff --git a/libcxx/include/__algorithm/three_way_comp_ref_type.h b/libcxx/include/__algorithm/three_way_comp_ref_type.h --- a/libcxx/include/__algorithm/three_way_comp_ref_type.h +++ b/libcxx/include/__algorithm/three_way_comp_ref_type.h @@ -58,8 +58,7 @@ // Pass the comparator by lvalue reference. Or in debug mode, using a // debugging wrapper that stores a reference. -// TODO(varconst): update to be used in the new debug mode (or delete entirely). -# ifdef _LIBCPP_ENABLE_DEBUG_MODE +# if _LIBCPP_ENABLE_DEBUG_MODE template using __three_way_comp_ref_type = __debug_three_way_comp<_Comp>; # else diff --git a/libcxx/include/__config b/libcxx/include/__config --- a/libcxx/include/__config +++ b/libcxx/include/__config @@ -206,6 +206,71 @@ // } ABI +// HARDENING { + +// Enables the hardened mode which consists of all checks intended to be used in production. Hardened mode prioritizes +// security-critical checks that can be done with relatively little overhead in constant time. Mutually exclusive with +// `_LIBCPP_ENABLE_DEBUG_MODE`. +// +// #define _LIBCPP_ENABLE_HARDENED_MODE 1 + +// Enables the debug mode which contains all the checks from the hardened mode and additionally more expensive checks +// that may affect the complexity of algorithms. The debug mode is intended to be used for testing, not in production. +// Mutually exclusive with `_LIBCPP_ENABLE_HARDENED_MODE`. +// +// #define _LIBCPP_ENABLE_DEBUG_MODE 1 + +// Available checks: + +// TODO(hardening): add documentation for different checks here. + +# ifndef _LIBCPP_ENABLE_HARDENED_MODE +# define _LIBCPP_ENABLE_HARDENED_MODE _LIBCPP_ENABLE_HARDENED_MODE_DEFAULT +# endif +# if _LIBCPP_ENABLE_HARDENED_MODE != 0 && _LIBCPP_ENABLE_HARDENED_MODE != 1 +# error "_LIBCPP_ENABLE_HARDENED_MODE must be set to 0 or 1." +# endif + +# ifndef _LIBCPP_ENABLE_DEBUG_MODE +# define _LIBCPP_ENABLE_DEBUG_MODE _LIBCPP_ENABLE_DEBUG_MODE_DEFAULT +# endif +# if _LIBCPP_ENABLE_DEBUG_MODE != 0 && _LIBCPP_ENABLE_DEBUG_MODE != 1 +# error "_LIBCPP_ENABLE_DEBUG_MODE must be set to 0 or 1." +# endif + +# if _LIBCPP_ENABLE_HARDENED_MODE && _LIBCPP_ENABLE_DEBUG_MODE +# error "Only one of _LIBCPP_ENABLE_HARDENED_MODE and _LIBCPP_ENABLE_DEBUG_MODE can be enabled." +# endif + +// Hardened mode checks. +# if _LIBCPP_ENABLE_HARDENED_MODE + +// Automatically enable assertions in hardened mode (unless the user explicitly turned them off). +# ifndef _LIBCPP_ENABLE_ASSERTIONS +# define _LIBCPP_ENABLE_ASSERTIONS 1 +# endif + +// TODO(hardening): more checks to be added here... + +// Debug mode checks. +# elif _LIBCPP_ENABLE_DEBUG_MODE + +// Automatically enable assertions in debug mode (unless the user explicitly turned them off). +# ifndef _LIBCPP_ENABLE_ASSERTIONS +# define _LIBCPP_ENABLE_ASSERTIONS 1 +# endif + +// TODO(hardening): more checks to be added here... + +// Disable all checks if neither the hardened mode nor the debug mode is enabled. +# else + +// TODO: more checks to be added here... + +# endif // _LIBCPP_ENABLE_HARDENED_MODE + +// } HARDENING + # define _LIBCPP_TOSTRING2(x) #x # define _LIBCPP_TOSTRING(x) _LIBCPP_TOSTRING2(x) diff --git a/libcxx/include/__config_site.in b/libcxx/include/__config_site.in --- a/libcxx/include/__config_site.in +++ b/libcxx/include/__config_site.in @@ -34,6 +34,10 @@ #cmakedefine _LIBCPP_PSTL_CPU_BACKEND_SERIAL #cmakedefine _LIBCPP_PSTL_CPU_BACKEND_THREAD +// Hardening. +#cmakedefine01 _LIBCPP_ENABLE_HARDENED_MODE_DEFAULT +#cmakedefine01 _LIBCPP_ENABLE_DEBUG_MODE_DEFAULT + // __USE_MINGW_ANSI_STDIO gets redefined on MinGW #ifdef __clang__ # pragma clang diagnostic push diff --git a/libcxx/include/__tree b/libcxx/include/__tree --- a/libcxx/include/__tree +++ b/libcxx/include/__tree @@ -376,8 +376,9 @@ { _LIBCPP_ASSERT_UNCATEGORIZED(__root != nullptr, "Root node should not be null"); _LIBCPP_ASSERT_UNCATEGORIZED(__z != nullptr, "The node to remove should not be null"); - // TODO: Use in the new debug mode: - // _LIBCPP_DEBUG_ASSERT(std::__tree_invariant(__root), "The tree invariants should hold"); +#if _LIBCPP_ENABLE_DEBUG_MODE + _LIBCPP_ASSERT_UNCATEGORIZED(std::__tree_invariant(__root), "The tree invariants should hold"); +#endif // __z will be removed from the tree. Client still needs to destruct/deallocate it // __y is either __z, or if __z has two children, __tree_next(__z). // __y will have at most one child. diff --git a/libcxx/test/CMakeLists.txt b/libcxx/test/CMakeLists.txt --- a/libcxx/test/CMakeLists.txt +++ b/libcxx/test/CMakeLists.txt @@ -28,6 +28,8 @@ serialize_lit_param(enable_assertions True) endif() +serialize_lit_param(hardening_mode "\"${LIBCXX_HARDENING_MODE}\"") + if (CMAKE_CXX_COMPILER_TARGET) serialize_lit_param(target_triple "\"${CMAKE_CXX_COMPILER_TARGET}\"") else() diff --git a/libcxx/test/libcxx/algorithms/alg.sorting/alg.heap.operations/make.heap/complexity.pass.cpp b/libcxx/test/libcxx/algorithms/alg.sorting/alg.heap.operations/make.heap/complexity.pass.cpp --- a/libcxx/test/libcxx/algorithms/alg.sorting/alg.heap.operations/make.heap/complexity.pass.cpp +++ b/libcxx/test/libcxx/algorithms/alg.sorting/alg.heap.operations/make.heap/complexity.pass.cpp @@ -64,7 +64,9 @@ std::make_heap(v.begin(), v.end()); assert(stats.copied == 0); assert(stats.moved == 153'486); +#if !_LIBCPP_ENABLE_DEBUG_MODE assert(stats.compared == 188'285); +#endif assert(std::is_heap(v.begin(), v.end())); diff --git a/libcxx/test/libcxx/algorithms/alg.sorting/assert.sort.invalid_comparator.pass.cpp b/libcxx/test/libcxx/algorithms/alg.sorting/assert.sort.invalid_comparator.pass.cpp --- a/libcxx/test/libcxx/algorithms/alg.sorting/assert.sort.invalid_comparator.pass.cpp +++ b/libcxx/test/libcxx/algorithms/alg.sorting/assert.sort.invalid_comparator.pass.cpp @@ -12,6 +12,10 @@ // UNSUPPORTED: c++03, c++11, c++14, c++17 // XFAIL: availability-verbose_abort-missing // ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_ASSERTIONS=1 -D_LIBCPP_DEBUG_STRICT_WEAK_ORDERING_CHECK +// When the debug mode is enabled, this test fails because we actually catch on the fly that the comparator is not +// a strict-weak ordering before we catch that we'd dereference out-of-bounds inside std::sort, which leads to different +// errors than the ones tested below. +// XFAIL: libcpp-has-debug-mode // This test uses a specific combination of an invalid comparator and sequence of values to // ensure that our sorting functions do not go out-of-bounds and satisfy strict weak ordering in that case. diff --git a/libcxx/test/libcxx/assertions/modes/debug.pass.cpp b/libcxx/test/libcxx/assertions/modes/debug.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/assertions/modes/debug.pass.cpp @@ -0,0 +1,28 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// This test ensures that assertions trigger without the user having to do anything when the debug mode has been enabled +// by default. + +// UNSUPPORTED: !libcpp-has-debug-mode +// `check_assertion.h` is only available starting from C++11. +// UNSUPPORTED: c++03 +// `check_assertion.h` requires Unix headers. +// REQUIRES: has-unix-headers + +#include +#include "check_assertion.h" + +int main(int, char**) { + _LIBCPP_ASSERT_UNCATEGORIZED(true, "Should not fire"); + TEST_LIBCPP_ASSERT_FAILURE([] { + _LIBCPP_ASSERT_UNCATEGORIZED(false, "Should fire"); + }(), "Should fire"); + + return 0; +} diff --git a/libcxx/test/libcxx/assertions/modes/debug_mode_disabled_in_tu.pass.cpp b/libcxx/test/libcxx/assertions/modes/debug_mode_disabled_in_tu.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/assertions/modes/debug_mode_disabled_in_tu.pass.cpp @@ -0,0 +1,23 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// This test ensures that we can disable the debug mode on a per-TU basis regardless of how the library was built. + +// TODO(hardening): currently, explicitly enabling assertions enables all uncategorized assertions and overrides +// disabling the debug mode. +// UNSUPPORTED: libcpp-has-hardened-mode, libcpp-has-assertions +// ADDITIONAL_COMPILE_FLAGS: -Wno-macro-redefined -D_LIBCPP_ENABLE_DEBUG_MODE=0 + +#include + +int main(int, char**) { + _LIBCPP_ASSERT_UNCATEGORIZED(true, "Should not fire"); + _LIBCPP_ASSERT_UNCATEGORIZED(false, "Also should not fire"); + + return 0; +} diff --git a/libcxx/test/libcxx/assertions/modes/debug_mode_enabled_in_tu.pass.cpp b/libcxx/test/libcxx/assertions/modes/debug_mode_enabled_in_tu.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/assertions/modes/debug_mode_enabled_in_tu.pass.cpp @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// This test ensures that we can enable the debug mode on a per-TU basis regardless of how the library was built. + +// Hardened mode would additionally trigger the error that hardened and debug modes are mutually exclusive. +// UNSUPPORTED: libcpp-has-hardened-mode +// `check_assertion.h` is only available starting from C++11. +// UNSUPPORTED: c++03 +// `check_assertion.h` requires Unix headers. +// REQUIRES: has-unix-headers +// The ability to set a custom abort message is required to compare the assertion message. +// XFAIL: availability-verbose_abort-missing +// ADDITIONAL_COMPILE_FLAGS: -Wno-macro-redefined -D_LIBCPP_ENABLE_DEBUG_MODE=1 + +#include +#include "check_assertion.h" + +int main(int, char**) { + _LIBCPP_ASSERT_UNCATEGORIZED(true, "Should not fire"); + TEST_LIBCPP_ASSERT_FAILURE([] { + _LIBCPP_ASSERT_UNCATEGORIZED(false, "Should fire"); + }(), "Should fire"); + + return 0; +} diff --git a/libcxx/test/libcxx/assertions/modes/debug_mode_not_1_or_0.verify.cpp b/libcxx/test/libcxx/assertions/modes/debug_mode_not_1_or_0.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/assertions/modes/debug_mode_not_1_or_0.verify.cpp @@ -0,0 +1,19 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// This test verifies that setting the debug mode to a value other than `0` or `1` triggers a compile-time error. + +// Hardened mode would additionally trigger the error that hardened and debug modes are mutually exclusive. +// UNSUPPORTED: libcpp-has-hardened-mode +// Modules build produces a different error ("Could not build module 'std'"). +// UNSUPPORTED: modules-build +// ADDITIONAL_COMPILE_FLAGS: -Wno-macro-redefined -D_LIBCPP_ENABLE_DEBUG_MODE=2 + +#include + +// expected-error@*:* {{_LIBCPP_ENABLE_DEBUG_MODE must be set to 0 or 1.}} diff --git a/libcxx/test/libcxx/assertions/modes/debug_no_assertions.pass.cpp b/libcxx/test/libcxx/assertions/modes/debug_no_assertions.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/assertions/modes/debug_no_assertions.pass.cpp @@ -0,0 +1,21 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// Test that we can override whether assertions are enabled regardless of the hardening mode in use. + +// UNSUPPORTED: !libcpp-has-debug-mode +// ADDITIONAL_COMPILE_FLAGS: -Wno-macro-redefined -D_LIBCPP_ENABLE_ASSERTIONS=0 + +#include + +int main(int, char**) { + _LIBCPP_ASSERT_UNCATEGORIZED(true, "Should not fire"); + _LIBCPP_ASSERT_UNCATEGORIZED(false, "Also should not fire"); + + return 0; +} diff --git a/libcxx/test/libcxx/assertions/modes/hardened.pass.cpp b/libcxx/test/libcxx/assertions/modes/hardened.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/assertions/modes/hardened.pass.cpp @@ -0,0 +1,28 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// This test ensures that assertions trigger without the user having to do anything when the hardened mode has been +// enabled by default. + +// UNSUPPORTED: !libcpp-has-hardened-mode +// `check_assertion.h` is only available starting from C++11. +// UNSUPPORTED: c++03 +// `check_assertion.h` requires Unix headers. +// REQUIRES: has-unix-headers + +#include +#include "check_assertion.h" + +int main(int, char**) { + _LIBCPP_ASSERT_UNCATEGORIZED(true, "Should not fire"); + TEST_LIBCPP_ASSERT_FAILURE([] { + _LIBCPP_ASSERT_UNCATEGORIZED(false, "Should fire"); + }(), "Should fire"); + + return 0; +} diff --git a/libcxx/test/libcxx/assertions/modes/hardened_and_debug_mutually_exclusive.verify.cpp b/libcxx/test/libcxx/assertions/modes/hardened_and_debug_mutually_exclusive.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/assertions/modes/hardened_and_debug_mutually_exclusive.verify.cpp @@ -0,0 +1,17 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// This test verifies that `_LIBCPP_ENABLE_HARDENED_MODE` and `_LIBCPP_ENABLE_DEBUG_MODE` are mutually exclusive. + +// Modules build produces a different error ("Could not build module 'std'"). +// UNSUPPORTED: modules-build +// ADDITIONAL_COMPILE_FLAGS: -Wno-macro-redefined -D_LIBCPP_ENABLE_HARDENED_MODE=1 -D_LIBCPP_ENABLE_DEBUG_MODE=1 + +#include + +// expected-error@*:* {{Only one of _LIBCPP_ENABLE_HARDENED_MODE and _LIBCPP_ENABLE_DEBUG_MODE can be enabled.}} diff --git a/libcxx/test/libcxx/assertions/modes/hardened_mode_disabled_in_tu.pass.cpp b/libcxx/test/libcxx/assertions/modes/hardened_mode_disabled_in_tu.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/assertions/modes/hardened_mode_disabled_in_tu.pass.cpp @@ -0,0 +1,23 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// This test ensures that we can disable the hardened mode on a per-TU basis regardless of how the library was built. + +// TODO(hardening): currently, explicitly enabling assertions enables all uncategorized assertions and overrides +// disabling the hardened mode. +// UNSUPPORTED: libcpp-has-debug-mode, libcpp-has-assertions +// ADDITIONAL_COMPILE_FLAGS: -Wno-macro-redefined -D_LIBCPP_ENABLE_HARDENED_MODE=0 + +#include + +int main(int, char**) { + _LIBCPP_ASSERT_UNCATEGORIZED(true, "Should not fire"); + _LIBCPP_ASSERT_UNCATEGORIZED(false, "Also should not fire"); + + return 0; +} diff --git a/libcxx/test/libcxx/assertions/modes/hardened_mode_enabled_in_tu.pass.cpp b/libcxx/test/libcxx/assertions/modes/hardened_mode_enabled_in_tu.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/assertions/modes/hardened_mode_enabled_in_tu.pass.cpp @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// This test ensures that we can enable the hardened mode on a per-TU basis regardless of how the library was built. + +// Debug mode would additionally trigger the error that hardened and debug modes are mutually exclusive. +// UNSUPPORTED: libcpp-has-debug-mode +// `check_assertion.h` is only available starting from C++11. +// UNSUPPORTED: c++03 +// `check_assertion.h` requires Unix headers. +// REQUIRES: has-unix-headers +// The ability to set a custom abort message is required to compare the assertion message. +// XFAIL: availability-verbose_abort-missing +// ADDITIONAL_COMPILE_FLAGS: -Wno-macro-redefined -D_LIBCPP_ENABLE_HARDENED_MODE=1 + +#include +#include "check_assertion.h" + +int main(int, char**) { + _LIBCPP_ASSERT_UNCATEGORIZED(true, "Should not fire"); + TEST_LIBCPP_ASSERT_FAILURE([] { + _LIBCPP_ASSERT_UNCATEGORIZED(false, "Should fire"); + }(), "Should fire"); + + return 0; +} diff --git a/libcxx/test/libcxx/assertions/modes/hardened_mode_not_1_or_0.verify.cpp b/libcxx/test/libcxx/assertions/modes/hardened_mode_not_1_or_0.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/assertions/modes/hardened_mode_not_1_or_0.verify.cpp @@ -0,0 +1,19 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// This test verifies that setting the hardened mode to a value other than `0` or `1` triggers a compile-time error. + +// Debug mode would additionally trigger the error that hardened and debug modes are mutually exclusive. +// UNSUPPORTED: libcpp-has-debug-mode +// Modules build produces a different error ("Could not build module 'std'"). +// UNSUPPORTED: modules-build +// ADDITIONAL_COMPILE_FLAGS: -Wno-macro-redefined -D_LIBCPP_ENABLE_HARDENED_MODE=2 + +#include + +// expected-error@*:* {{_LIBCPP_ENABLE_HARDENED_MODE must be set to 0 or 1.}} diff --git a/libcxx/test/libcxx/assertions/modes/hardened_no_assertions.pass.cpp b/libcxx/test/libcxx/assertions/modes/hardened_no_assertions.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/assertions/modes/hardened_no_assertions.pass.cpp @@ -0,0 +1,21 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// Test that we can override whether assertions are enabled regardless of the hardening mode in use. + +// UNSUPPORTED: !libcpp-has-hardened-mode +// ADDITIONAL_COMPILE_FLAGS: -Wno-macro-redefined -D_LIBCPP_ENABLE_ASSERTIONS=0 + +#include + +int main(int, char**) { + _LIBCPP_ASSERT_UNCATEGORIZED(true, "Should not fire"); + _LIBCPP_ASSERT_UNCATEGORIZED(false, "Also should not fire"); + + return 0; +} diff --git a/libcxx/test/libcxx/assertions/modes/unchecked.pass.cpp b/libcxx/test/libcxx/assertions/modes/unchecked.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/assertions/modes/unchecked.pass.cpp @@ -0,0 +1,24 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// This test checks that if no hardening mode is defined (i.e., in the unchecked mode), by default assertions aren't +// triggered. + +// UNSUPPORTED: libcpp-has-hardened-mode, libcpp-has-debug-mode + +#include + +int main(int, char**) { + // TODO(hardening): remove the `#if` guard once `_LIBCPP_ENABLE_ASSERTIONS` no longer affects hardening modes. +#if !_LIBCPP_ENABLE_ASSERTIONS + _LIBCPP_ASSERT_UNCATEGORIZED(true, "Should not fire"); + _LIBCPP_ASSERT_UNCATEGORIZED(false, "Also should not fire"); +#endif + + return 0; +} diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.heap.operations/sort.heap/complexity.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.heap.operations/sort.heap/complexity.pass.cpp --- a/libcxx/test/std/algorithms/alg.sorting/alg.heap.operations/sort.heap/complexity.pass.cpp +++ b/libcxx/test/std/algorithms/alg.sorting/alg.heap.operations/sort.heap/complexity.pass.cpp @@ -68,9 +68,13 @@ std::sort_heap(first, last); LIBCPP_ASSERT(stats.copied == 0); LIBCPP_ASSERT(stats.moved <= 2 * n + n * logn); +#if !_LIBCPP_ENABLE_DEBUG_MODE LIBCPP_ASSERT(stats.compared <= n * logn); - LIBCPP_ASSERT(std::is_sorted(first, last)); + (void)debug_comparisons; +#else LIBCPP_ASSERT(stats.compared <= 2 * n * logn + debug_comparisons); +#endif + LIBCPP_ASSERT(std::is_sorted(first, last)); } return 0; } diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.heap.operations/sort.heap/ranges_sort_heap.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.heap.operations/sort.heap/ranges_sort_heap.pass.cpp --- a/libcxx/test/std/algorithms/alg.sorting/alg.heap.operations/sort.heap/ranges_sort_heap.pass.cpp +++ b/libcxx/test/std/algorithms/alg.sorting/alg.heap.operations/sort.heap/ranges_sort_heap.pass.cpp @@ -262,9 +262,13 @@ std::ranges::sort_heap(first, last, &MyInt::Comp); LIBCPP_ASSERT(stats.copied == 0); LIBCPP_ASSERT(stats.moved <= 2 * n + n * logn); +#if !_LIBCPP_ENABLE_DEBUG_MODE LIBCPP_ASSERT(stats.compared <= n * logn); - LIBCPP_ASSERT(std::is_sorted(first, last, &MyInt::Comp)); + (void)debug_comparisons; +#else LIBCPP_ASSERT(stats.compared <= 2 * n * logn + debug_comparisons); +#endif + LIBCPP_ASSERT(std::is_sorted(first, last, &MyInt::Comp)); } } diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.merge/inplace_merge_comp.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.merge/inplace_merge_comp.pass.cpp --- a/libcxx/test/std/algorithms/alg.sorting/alg.merge/inplace_merge_comp.pass.cpp +++ b/libcxx/test/std/algorithms/alg.sorting/alg.merge/inplace_merge_comp.pass.cpp @@ -79,7 +79,9 @@ assert(ia[0] == static_cast(N)-1); assert(ia[N-1] == 0); assert(std::is_sorted(ia, ia+N, std::greater())); +#if !_LIBCPP_ENABLE_DEBUG_MODE assert(pred.count() <= (N-1)); +#endif } delete [] ia; } diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.three.way/lexicographical_compare_three_way_comp.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.three.way/lexicographical_compare_three_way_comp.pass.cpp --- a/libcxx/test/std/algorithms/alg.sorting/alg.three.way/lexicographical_compare_three_way_comp.pass.cpp +++ b/libcxx/test/std/algorithms/alg.sorting/alg.three.way/lexicographical_compare_three_way_comp.pass.cpp @@ -156,7 +156,11 @@ // The comparator is invoked only `min(left.size(), right.size())` times test_lexicographical_compare( std::array{0, 1, 2}, std::array{0, 1, 2, 3}, compare_last_digit_counting, std::strong_ordering::less); +#if !_LIBCPP_ENABLE_DEBUG_MODE assert(compare_invocation_count <= 3); +#else + assert(compare_invocation_count <= 6); +#endif } // Check that it works with proxy iterators diff --git a/libcxx/test/support/container_debug_tests.h b/libcxx/test/support/container_debug_tests.h --- a/libcxx/test/support/container_debug_tests.h +++ b/libcxx/test/support/container_debug_tests.h @@ -14,7 +14,7 @@ #error This header may only be used for libc++ tests #endif -#ifndef _LIBCPP_ENABLE_DEBUG_MODE +#if !_LIBCPP_ENABLE_DEBUG_MODE #error The library must be built with the debug mode enabled in order to use this header #endif diff --git a/libcxx/utils/ci/buildkite-pipeline.yml b/libcxx/utils/ci/buildkite-pipeline.yml --- a/libcxx/utils/ci/buildkite-pipeline.yml +++ b/libcxx/utils/ci/buildkite-pipeline.yml @@ -486,6 +486,42 @@ limit: 2 timeout_in_minutes: 120 + - label: "Hardened mode" + command: "libcxx/utils/ci/run-buildbot generic-hardened-mode" + artifact_paths: + - "**/test-results.xml" + - "**/*.abilist" + env: + CC: "clang-${LLVM_HEAD_VERSION}" + CXX: "clang++-${LLVM_HEAD_VERSION}" + ENABLE_CLANG_TIDY: "On" + agents: + queue: "libcxx-builders" + os: "linux" + retry: + automatic: + - exit_status: -1 # Agent was lost + limit: 2 + timeout_in_minutes: 120 + + - label: "Debug mode" + command: "libcxx/utils/ci/run-buildbot generic-debug-mode" + artifact_paths: + - "**/test-results.xml" + - "**/*.abilist" + env: + CC: "clang-${LLVM_HEAD_VERSION}" + CXX: "clang++-${LLVM_HEAD_VERSION}" + ENABLE_CLANG_TIDY: "On" + agents: + queue: "libcxx-builders" + os: "linux" + retry: + automatic: + - exit_status: -1 # Agent was lost + limit: 2 + timeout_in_minutes: 120 + - label: "With LLVM's libunwind" command: "libcxx/utils/ci/run-buildbot generic-with_llvm_unwinder" artifact_paths: diff --git a/libcxx/utils/ci/run-buildbot b/libcxx/utils/ci/run-buildbot --- a/libcxx/utils/ci/run-buildbot +++ b/libcxx/utils/ci/run-buildbot @@ -387,6 +387,18 @@ check-runtimes check-abi-list ;; +generic-hardened-mode) + clean + generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-hardened-mode.cmake" + check-runtimes + check-abi-list +;; +generic-debug-mode) + clean + generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-debug-mode.cmake" + check-runtimes + check-abi-list +;; generic-with_llvm_unwinder) clean generate-cmake -DLIBCXXABI_USE_LLVM_UNWINDER=ON diff --git a/libcxx/utils/libcxx/test/params.py b/libcxx/utils/libcxx/test/params.py --- a/libcxx/utils/libcxx/test/params.py +++ b/libcxx/utils/libcxx/test/params.py @@ -294,6 +294,23 @@ AddFeature("libcpp-has-assertions"), ], ), + Parameter( + name="hardening_mode", + choices=["unchecked", "hardened", "debug"], + type=str, + default="unchecked", + help="Whether to enable the hardened mode or the debug mode when compiling the test suite. This is only " + "meaningful when running the tests against libc++.", + actions=lambda hardening_mode: filter( + None, + [ + AddCompileFlag("-D_LIBCPP_ENABLE_HARDENED_MODE=1") if hardening_mode == "hardened" else None, + AddCompileFlag("-D_LIBCPP_ENABLE_DEBUG_MODE=1") if hardening_mode == "debug" else None, + AddFeature("libcpp-has-hardened-mode") if hardening_mode == "hardened" else None, + AddFeature("libcpp-has-debug-mode") if hardening_mode == "debug" else None, + ], + ), + ), Parameter( name="additional_features", type=list,