From 314526557ec66ee627ae8a1c03f6ccc610668fdb Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Mon, 22 Jan 2024 22:33:04 -0500 Subject: [PATCH] [libc++] Fix the behavior of throwing `operator new` under -fno-exceptions (#69498) In D144319, Clang tried to land a change that would cause some functions that are not supposed to return nullptr to optimize better. As reported in https://reviews.llvm.org/D144319#4203982, libc++ started seeing failures in its CI shortly after this change was landed. As explained in D146379, the reason for these failures is that libc++'s throwing `operator new` can in fact return nullptr when compiled with exceptions disabled. However, this contradicts the Standard, which clearly says that the throwing version of `operator new(size_t)` should never return nullptr. This is actually a long standing issue. I've previously seen a case where LTO would optimize incorrectly based on the assumption that `operator new` doesn't return nullptr, an assumption that was violated in that case because libc++.dylib was compiled with -fno-exceptions. Unfortunately, fixing this is kind of tricky. The Standard has a few requirements for the allocation functions, some of which are impossible to satisfy under -fno-exceptions: 1. `operator new(size_t)` must never return nullptr 2. `operator new(size_t, nothrow_t)` must call the throwing version and return nullptr on failure to allocate 3. We can't throw exceptions when compiled with -fno-exceptions In the case where exceptions are enabled, things work nicely. `new(size_t)` throws and `new(size_t, nothrow_t)` uses a try-catch to return nullptr. However, when compiling the library with -fno-exceptions, we can't throw an exception from `new(size_t)`, and we can't catch anything from `new(size_t, nothrow_t)`. The only thing we can do from `new(size_t)` is actually abort the program, which does not make it possible for `new(size_t, nothrow_t)` to catch something and return nullptr. This patch makes the following changes: 1. When compiled with -fno-exceptions, the throwing version of `operator new` will now abort on failure instead of returning nullptr on failure. This resolves the issue that the compiler could mis-compile based on the assumption that nullptr is never returned. This constitutes an API and ABI breaking change for folks compiling the library with -fno-exceptions (which is not the general public, who merely uses libc++ headers but use a shared library that has already been compiled). This should mostly impact vendors and other folks who compile libc++.dylib themselves. 2. When the library is compiled with -fexceptions, the nothrow version of `operator new` has no change. When the library is compiled with -fno-exceptions, the nothrow version of `operator new` will now check whether the throwing version of `operator new` has been overridden. If it has not been overridden, then it will use an implementation equivalent to that of the throwing `operator new`, except it will return nullptr on failure to allocate (instead of terminating). However, if the throwing `operator new` has been overridden, it is now an error NOT to also override the nothrow `operator new`. Indeed, there is no way for us to implement a valid nothrow `operator new` without knowing the exact implementation of the throwing version. In summary, this change will impact people who fall into the following intersection of conditions: - They use the libc++ shared/static library built with `-fno-exceptions` - They do not override `operator new(..., std::nothrow_t)` - They override `operator new(...)` (the throwing version) - They use `operator new(..., std::nothrow_t)` We believe this represents a small number of people. Fixes #60129 rdar://103958777 Differential Revision: https://reviews.llvm.org/D150610 --- libcxx/docs/ReleaseNotes/18.rst | 23 ++ libcxx/src/include/overridable_function.h | 119 ++++++++++ libcxx/src/new.cpp | 103 ++++++--- ...new_not_overridden_fno_exceptions.pass.cpp | 58 +++++ .../new_dont_return_nullptr.pass.cpp | 37 ++++ ...ze_align_nothrow.replace.indirect.pass.cpp | 4 + ...new.size_nothrow.replace.indirect.pass.cpp | 4 + ...ze_align_nothrow.replace.indirect.pass.cpp | 4 + ...new.size_nothrow.replace.indirect.pass.cpp | 4 + libcxx/test/support/check_assertion.h | 55 ++++- libcxx/test/support/count_new.h | 203 ++++++++++++------ .../test_check_assertion.pass.cpp | 15 +- libcxxabi/src/stdlib_new_delete.cpp | 172 +++++++++------ 13 files changed, 638 insertions(+), 163 deletions(-) create mode 100644 libcxx/src/include/overridable_function.h create mode 100644 libcxx/test/libcxx/language.support/support.dynamic/assert.nothrow_new_not_overridden_fno_exceptions.pass.cpp create mode 100644 libcxx/test/libcxx/language.support/support.dynamic/new_dont_return_nullptr.pass.cpp diff --git a/libcxx/docs/ReleaseNotes/18.rst b/libcxx/docs/ReleaseNotes/18.rst index fd882bafe19a..fb3d2af544c2 100644 --- a/libcxx/docs/ReleaseNotes/18.rst +++ b/libcxx/docs/ReleaseNotes/18.rst @@ -237,6 +237,29 @@ LLVM 20 ABI Affecting Changes --------------------- +- When the shared/static library is built with ``-fno-exceptions``, the behavior of ``operator new`` was changed + to make it standards-conforming. In LLVM 17 and before, the throwing versions of ``operator new`` would return + ``nullptr`` upon failure to allocate, when the shared/static library was built with exceptions disabled. This + was non-conforming, since the throwing versions of ``operator new`` are never expected to return ``nullptr``, and + this non-conformance could actually lead to miscompiles in subtle cases. + + Starting in LLVM 18, the throwing versions of ``operator new`` will abort the program when they fail to allocate + if the shared/static library has been built with ``-fno-exceptions``. This is consistent with the behavior of all + other potentially-throwing functions in the library, which abort the program instead of throwing when ``-fno-exceptions`` + is used. + + Furthermore, when the shared/static library is built with ``-fno-exceptions``, users who override the throwing + version of ``operator new`` will now need to also override the ``std::nothrow_t`` version of ``operator new`` if + they want to use it. Indeed, this is because there is no way to implement a conforming ``operator new(nothrow)`` + from a conforming potentially-throwing ``operator new`` when compiled with ``-fno-exceptions``. In that case, using + ``operator new(nothrow)`` without overriding it explicitly but after overriding the throwing ``operator new`` will + result in an error. + + Note that this change only impacts vendors/users that build the shared/static library themselves and pass + ``-DLIBCXX_ENABLE_EXCEPTIONS=OFF``, which is not the default configuration. If you are using the default + configuration of the library, the libc++ shared/static library will be built with exceptions enabled, and + there is no change between LLVM 17 and LLVM 18, even for users who build their own code using ``-fno-exceptions``. + - The symbol of a non-visible function part of ``std::system_error`` was removed. This is not a breaking change as the private function ``__init`` was never referenced internally outside of the dylib. diff --git a/libcxx/src/include/overridable_function.h b/libcxx/src/include/overridable_function.h new file mode 100644 index 000000000000..7b0fba10f47d --- /dev/null +++ b/libcxx/src/include/overridable_function.h @@ -0,0 +1,119 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_SRC_INCLUDE_OVERRIDABLE_FUNCTION_H +#define _LIBCPP_SRC_INCLUDE_OVERRIDABLE_FUNCTION_H + +#include <__config> +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +// +// This file provides the std::__is_function_overridden utility, which allows checking +// whether an overridable function (typically a weak symbol) like `operator new` +// has been overridden by a user or not. +// +// This is a low-level utility which does not work on all platforms, since it needs +// to make assumptions about the object file format in use. Furthermore, it requires +// the "base definition" of the function (the one we want to check whether it has been +// overridden) to be annotated with the _LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE macro. +// +// This currently works with Mach-O files (used on Darwin) and with ELF files (used on Linux +// and others). On platforms where we know how to implement this detection, the macro +// _LIBCPP_CAN_DETECT_OVERRIDDEN_FUNCTION is defined to 1, and it is defined to 0 on +// other platforms. The _LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE macro is defined to +// nothing on unsupported platforms so that it can be used to decorate functions regardless +// of whether detection is actually supported. +// +// How does this work? +// ------------------- +// +// Let's say we want to check whether a weak function `f` has been overridden by the user. +// The general mechanism works by placing `f`'s definition (in the libc++ built library) +// inside a special section, which we do using the `__section__` attribute via the +// _LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE macro. +// +// Then, when comes the time to check whether the function has been overridden, we take +// the address of the function and we check whether it falls inside the special function +// we created. This can be done by finding pointers to the start and the end of the section +// (which is done differently for ELF and Mach-O), and then checking whether `f` falls +// within those bounds. If it falls within those bounds, then `f` is still inside the +// special section and so it is the version we defined in the libc++ built library, i.e. +// it was not overridden. Otherwise, it was overridden by the user because it falls +// outside of the section. +// +// Important note +// -------------- +// +// This mechanism should never be used outside of the libc++ built library. In particular, +// attempting to use this within the libc++ headers will not work at all because we don't +// want to be defining special sections inside user's executables which use our headers. +// This is provided inside libc++'s include tree solely to make it easier to share with +// libc++abi, which needs the same mechanism. +// + +#if defined(_LIBCPP_OBJECT_FORMAT_MACHO) + +# define _LIBCPP_CAN_DETECT_OVERRIDDEN_FUNCTION 1 +# define _LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE \ + __attribute__((__section__("__TEXT,__lcxx_override,regular,pure_instructions"))) + +_LIBCPP_BEGIN_NAMESPACE_STD +template +_LIBCPP_HIDE_FROM_ABI bool __is_function_overridden(_Ret (*__fptr)(_Args...)) noexcept { + // Declare two dummy bytes and give them these special `__asm` values. These values are + // defined by the linker, which means that referring to `&__lcxx_override_start` will + // effectively refer to the address where the section starts (and same for the end). + extern char __lcxx_override_start __asm("section$start$__TEXT$__lcxx_override"); + extern char __lcxx_override_end __asm("section$end$__TEXT$__lcxx_override"); + + // Now get a uintptr_t out of these locations, and out of the function pointer. + uintptr_t __start = reinterpret_cast(&__lcxx_override_start); + uintptr_t __end = reinterpret_cast(&__lcxx_override_end); + uintptr_t __ptr = reinterpret_cast(__fptr); + + // Finally, the function was overridden if it falls outside of the section's bounds. + return __ptr < __start || __ptr > __end; +} +_LIBCPP_END_NAMESPACE_STD + +#elif defined(_LIBCPP_OBJECT_FORMAT_ELF) + +# define _LIBCPP_CAN_DETECT_OVERRIDDEN_FUNCTION 1 +# define _LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE __attribute__((__section__("__lcxx_override"))) + +// This is very similar to what we do for Mach-O above. The ELF linker will implicitly define +// variables with those names corresponding to the start and the end of the section. +// +// See https://stackoverflow.com/questions/16552710/how-do-you-get-the-start-and-end-addresses-of-a-custom-elf-section +extern char __start___lcxx_override; +extern char __stop___lcxx_override; + +_LIBCPP_BEGIN_NAMESPACE_STD +template +_LIBCPP_HIDE_FROM_ABI bool __is_function_overridden(_Ret (*__fptr)(_Args...)) noexcept { + uintptr_t __start = reinterpret_cast(&__start___lcxx_override); + uintptr_t __end = reinterpret_cast(&__stop___lcxx_override); + uintptr_t __ptr = reinterpret_cast(__fptr); + + return __ptr < __start || __ptr > __end; +} +_LIBCPP_END_NAMESPACE_STD + +#else + +# define _LIBCPP_CAN_DETECT_OVERRIDDEN_FUNCTION 0 +# define _LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE /* nothing */ + +#endif + +#endif // _LIBCPP_SRC_INCLUDE_OVERRIDABLE_FUNCTION_H diff --git a/libcxx/src/new.cpp b/libcxx/src/new.cpp index cb8b4aae8d5f..0869d90661dd 100644 --- a/libcxx/src/new.cpp +++ b/libcxx/src/new.cpp @@ -6,7 +6,9 @@ // //===----------------------------------------------------------------------===// +#include "include/overridable_function.h" #include <__memory/aligned_alloc.h> +#include #include #include @@ -15,6 +17,10 @@ // The code below is copied as-is into libc++abi's libcxxabi/src/stdlib_new_delete.cpp // file. The version in this file is the canonical one. +inline void __throw_bad_alloc_shim() { std::__throw_bad_alloc(); } + +# define _LIBCPP_ASSERT_SHIM(expr, str) _LIBCPP_ASSERT(expr, str) + // ------------------ BEGIN COPY ------------------ // Implement all new and delete operators as weak definitions // in this shared library, so that they can be overridden by programs @@ -36,41 +42,63 @@ static void* operator_new_impl(std::size_t size) { return p; } -_LIBCPP_WEAK void* operator new(std::size_t size) _THROW_BAD_ALLOC { +_LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE _LIBCPP_WEAK void* operator new(std::size_t size) _THROW_BAD_ALLOC { void* p = operator_new_impl(size); -# ifndef _LIBCPP_HAS_NO_EXCEPTIONS if (p == nullptr) - throw std::bad_alloc(); -# endif + __throw_bad_alloc_shim(); return p; } _LIBCPP_WEAK void* operator new(size_t size, const std::nothrow_t&) noexcept { +# ifdef _LIBCPP_HAS_NO_EXCEPTIONS +# if _LIBCPP_CAN_DETECT_OVERRIDDEN_FUNCTION + _LIBCPP_ASSERT_SHIM( + !std::__is_function_overridden(static_cast(&operator new)), + "libc++ was configured with exceptions disabled and `operator new(size_t)` has been overridden, " + "but `operator new(size_t, nothrow_t)` has not been overridden. This is problematic because " + "`operator new(size_t, nothrow_t)` must call `operator new(size_t)`, which will terminate in case " + "it fails to allocate, making it impossible for `operator new(size_t, nothrow_t)` to fulfill its " + "contract (since it should return nullptr upon failure). Please make sure you override " + "`operator new(size_t, nothrow_t)` as well."); +# endif + + return operator_new_impl(size); +# else void* p = nullptr; -# ifndef _LIBCPP_HAS_NO_EXCEPTIONS try { -# endif // _LIBCPP_HAS_NO_EXCEPTIONS p = ::operator new(size); -# ifndef _LIBCPP_HAS_NO_EXCEPTIONS } catch (...) { } -# endif // _LIBCPP_HAS_NO_EXCEPTIONS return p; +# endif } -_LIBCPP_WEAK void* operator new[](size_t size) _THROW_BAD_ALLOC { return ::operator new(size); } +_LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE _LIBCPP_WEAK void* operator new[](size_t size) _THROW_BAD_ALLOC { + return ::operator new(size); +} _LIBCPP_WEAK void* operator new[](size_t size, const std::nothrow_t&) noexcept { +# ifdef _LIBCPP_HAS_NO_EXCEPTIONS +# if _LIBCPP_CAN_DETECT_OVERRIDDEN_FUNCTION + _LIBCPP_ASSERT_SHIM( + !std::__is_function_overridden(static_cast(&operator new[])), + "libc++ was configured with exceptions disabled and `operator new[](size_t)` has been overridden, " + "but `operator new[](size_t, nothrow_t)` has not been overridden. This is problematic because " + "`operator new[](size_t, nothrow_t)` must call `operator new[](size_t)`, which will terminate in case " + "it fails to allocate, making it impossible for `operator new[](size_t, nothrow_t)` to fulfill its " + "contract (since it should return nullptr upon failure). Please make sure you override " + "`operator new[](size_t, nothrow_t)` as well."); +# endif + + return operator_new_impl(size); +# else void* p = nullptr; -# ifndef _LIBCPP_HAS_NO_EXCEPTIONS try { -# endif // _LIBCPP_HAS_NO_EXCEPTIONS p = ::operator new[](size); -# ifndef _LIBCPP_HAS_NO_EXCEPTIONS } catch (...) { } -# endif // _LIBCPP_HAS_NO_EXCEPTIONS return p; +# endif } _LIBCPP_WEAK void operator delete(void* ptr) noexcept { std::free(ptr); } @@ -107,43 +135,66 @@ static void* operator_new_aligned_impl(std::size_t size, std::align_val_t alignm return p; } -_LIBCPP_WEAK void* operator new(std::size_t size, std::align_val_t alignment) _THROW_BAD_ALLOC { +_LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE _LIBCPP_WEAK void* +operator new(std::size_t size, std::align_val_t alignment) _THROW_BAD_ALLOC { void* p = operator_new_aligned_impl(size, alignment); -# ifndef _LIBCPP_HAS_NO_EXCEPTIONS if (p == nullptr) - throw std::bad_alloc(); -# endif + __throw_bad_alloc_shim(); return p; } _LIBCPP_WEAK void* operator new(size_t size, std::align_val_t alignment, const std::nothrow_t&) noexcept { +# ifdef _LIBCPP_HAS_NO_EXCEPTIONS +# if _LIBCPP_CAN_DETECT_OVERRIDDEN_FUNCTION + _LIBCPP_ASSERT_SHIM( + !std::__is_function_overridden(static_cast(&operator new)), + "libc++ was configured with exceptions disabled and `operator new(size_t, align_val_t)` has been overridden, " + "but `operator new(size_t, align_val_t, nothrow_t)` has not been overridden. This is problematic because " + "`operator new(size_t, align_val_t, nothrow_t)` must call `operator new(size_t, align_val_t)`, which will " + "terminate in case it fails to allocate, making it impossible for `operator new(size_t, align_val_t, nothrow_t)` " + "to fulfill its contract (since it should return nullptr upon failure). Please make sure you override " + "`operator new(size_t, align_val_t, nothrow_t)` as well."); +# endif + + return operator_new_aligned_impl(size, alignment); +# else void* p = nullptr; -# ifndef _LIBCPP_HAS_NO_EXCEPTIONS try { -# endif // _LIBCPP_HAS_NO_EXCEPTIONS p = ::operator new(size, alignment); -# ifndef _LIBCPP_HAS_NO_EXCEPTIONS } catch (...) { } -# endif // _LIBCPP_HAS_NO_EXCEPTIONS return p; +# endif } -_LIBCPP_WEAK void* operator new[](size_t size, std::align_val_t alignment) _THROW_BAD_ALLOC { +_LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE _LIBCPP_WEAK void* +operator new[](size_t size, std::align_val_t alignment) _THROW_BAD_ALLOC { return ::operator new(size, alignment); } _LIBCPP_WEAK void* operator new[](size_t size, std::align_val_t alignment, const std::nothrow_t&) noexcept { +# ifdef _LIBCPP_HAS_NO_EXCEPTIONS +# if _LIBCPP_CAN_DETECT_OVERRIDDEN_FUNCTION + _LIBCPP_ASSERT_SHIM( + !std::__is_function_overridden(static_cast(&operator new[])), + "libc++ was configured with exceptions disabled and `operator new[](size_t, align_val_t)` has been overridden, " + "but `operator new[](size_t, align_val_t, nothrow_t)` has not been overridden. This is problematic because " + "`operator new[](size_t, align_val_t, nothrow_t)` must call `operator new[](size_t, align_val_t)`, which will " + "terminate in case it fails to allocate, making it impossible for `operator new[](size_t, align_val_t, " + "nothrow_t)` to fulfill its contract (since it should return nullptr upon failure). Please make sure you " + "override " + "`operator new[](size_t, align_val_t, nothrow_t)` as well."); +# endif + + return operator_new_aligned_impl(size, alignment); +# else void* p = nullptr; -# ifndef _LIBCPP_HAS_NO_EXCEPTIONS try { -# endif // _LIBCPP_HAS_NO_EXCEPTIONS p = ::operator new[](size, alignment); -# ifndef _LIBCPP_HAS_NO_EXCEPTIONS } catch (...) { } -# endif // _LIBCPP_HAS_NO_EXCEPTIONS return p; +# endif } _LIBCPP_WEAK void operator delete(void* ptr, std::align_val_t) noexcept { std::__libcpp_aligned_free(ptr); } diff --git a/libcxx/test/libcxx/language.support/support.dynamic/assert.nothrow_new_not_overridden_fno_exceptions.pass.cpp b/libcxx/test/libcxx/language.support/support.dynamic/assert.nothrow_new_not_overridden_fno_exceptions.pass.cpp new file mode 100644 index 000000000000..fb26bdf49cc3 --- /dev/null +++ b/libcxx/test/libcxx/language.support/support.dynamic/assert.nothrow_new_not_overridden_fno_exceptions.pass.cpp @@ -0,0 +1,58 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// void* operator new(std::size_t, const std::nothrow_t&); +// void* operator new(std::size_t, std::align_val_t, const std::nothrow_t&); +// void* operator new[](std::size_t, const std::nothrow_t&); +// void* operator new[](std::size_t, std::align_val_t, const std::nothrow_t&); + +// This test ensures that we catch the case where `new` has been overridden but `new(nothrow)` +// has not been overridden, and the library is compiled with -fno-exceptions. +// +// In that case, it is impossible for libc++ to provide a Standards conforming implementation +// of `new(nothrow)`, so the only viable option is to terminate the program. + +// REQUIRES: has-unix-headers +// UNSUPPORTED: c++03 + +// We only know how to diagnose this on platforms that use the ELF or Mach-O object file formats. +// XFAIL: target={{.+}}-windows-{{.+}} + +// TODO: We currently don't have a way to express that the built library was +// compiled with -fno-exceptions, so if the library was built with support +// for exceptions but we run the test suite without exceptions, this will +// spuriously fail. +// REQUIRES: no-exceptions + +#include +#include + +#include "check_assertion.h" + +// Override the throwing versions of operator new, but not the nothrow versions. +alignas(32) char DummyData[32 * 3]; +void* operator new(std::size_t) { return DummyData; } +void* operator new(std::size_t, std::align_val_t) { return DummyData; } +void* operator new[](std::size_t) { return DummyData; } +void* operator new[](std::size_t, std::align_val_t) { return DummyData; } + +void operator delete(void*) noexcept {} +void operator delete(void*, std::align_val_t) noexcept {} +void operator delete[](void*) noexcept {} +void operator delete[](void*, std::align_val_t) noexcept {} + +int main(int, char**) { + std::size_t size = 3; + std::align_val_t align = static_cast(32); + EXPECT_ANY_DEATH((void)operator new(size, std::nothrow)); + EXPECT_ANY_DEATH((void)operator new(size, align, std::nothrow)); + EXPECT_ANY_DEATH((void)operator new[](size, std::nothrow)); + EXPECT_ANY_DEATH((void)operator new[](size, align, std::nothrow)); + + return 0; +} diff --git a/libcxx/test/libcxx/language.support/support.dynamic/new_dont_return_nullptr.pass.cpp b/libcxx/test/libcxx/language.support/support.dynamic/new_dont_return_nullptr.pass.cpp new file mode 100644 index 000000000000..02b20a8c98b2 --- /dev/null +++ b/libcxx/test/libcxx/language.support/support.dynamic/new_dont_return_nullptr.pass.cpp @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// void* operator new(std::size_t); +// void* operator new(std::size_t, std::align_val_t); +// void* operator new[](std::size_t); +// void* operator new[](std::size_t, std::align_val_t); + +// This test ensures that we abort the program instead of returning nullptr +// when we fail to satisfy the allocation request. The throwing versions of +// `operator new` must never return nullptr on failure to allocate (per the +// Standard) and the compiler actually relies on that for optimizations. +// Returning nullptr from the throwing `operator new` can basically result +// in miscompiles. + +// REQUIRES: has-unix-headers +// REQUIRES: no-exceptions +// UNSUPPORTED: c++03, c++11, c++14 + +#include +#include +#include + +#include "check_assertion.h" + +int main(int, char**) { + EXPECT_ANY_DEATH((void)operator new(std::numeric_limits::max())); + EXPECT_ANY_DEATH((void)operator new(std::numeric_limits::max(), static_cast(32))); + EXPECT_ANY_DEATH((void)operator new[](std::numeric_limits::max())); + EXPECT_ANY_DEATH((void)operator new[](std::numeric_limits::max(), static_cast(32))); + return 0; +} diff --git a/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.array/new.size_align_nothrow.replace.indirect.pass.cpp b/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.array/new.size_align_nothrow.replace.indirect.pass.cpp index df8a651932ce..f6959172ea24 100644 --- a/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.array/new.size_align_nothrow.replace.indirect.pass.cpp +++ b/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.array/new.size_align_nothrow.replace.indirect.pass.cpp @@ -11,6 +11,10 @@ // Test that we can replace the operator by replacing `operator new[](std::size_t, std::align_val_t)` // (the throwing version). +// This doesn't work when the shared library was built with exceptions disabled, because +// we can't implement the non-throwing new from the throwing new in that case. +// XFAIL: no-exceptions + // UNSUPPORTED: c++03, c++11, c++14 // UNSUPPORTED: sanitizer-new-delete diff --git a/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.array/new.size_nothrow.replace.indirect.pass.cpp b/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.array/new.size_nothrow.replace.indirect.pass.cpp index 70d891b2a82c..84bfe8205b60 100644 --- a/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.array/new.size_nothrow.replace.indirect.pass.cpp +++ b/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.array/new.size_nothrow.replace.indirect.pass.cpp @@ -10,6 +10,10 @@ // Test that we can replace the operator by replacing `operator new[](std::size_t)` (the throwing version). +// This doesn't work when the shared library was built with exceptions disabled, because +// we can't implement the non-throwing new from the throwing new in that case. +// XFAIL: no-exceptions + // UNSUPPORTED: sanitizer-new-delete // XFAIL: libcpp-no-vcruntime // XFAIL: LIBCXX-AIX-FIXME diff --git a/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.single/new.size_align_nothrow.replace.indirect.pass.cpp b/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.single/new.size_align_nothrow.replace.indirect.pass.cpp index a68cdab54528..2e7fa132890b 100644 --- a/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.single/new.size_align_nothrow.replace.indirect.pass.cpp +++ b/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.single/new.size_align_nothrow.replace.indirect.pass.cpp @@ -10,6 +10,10 @@ // Test that we can replace the operator by replacing `operator new(std::size_t, std::align_val_t)` (the throwing version). +// This doesn't work when the shared library was built with exceptions disabled, because +// we can't implement the non-throwing new from the throwing new in that case. +// XFAIL: no-exceptions + // UNSUPPORTED: c++03, c++11, c++14 // UNSUPPORTED: sanitizer-new-delete diff --git a/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.single/new.size_nothrow.replace.indirect.pass.cpp b/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.single/new.size_nothrow.replace.indirect.pass.cpp index 64edbfd7e9af..8b5019cf7eb6 100644 --- a/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.single/new.size_nothrow.replace.indirect.pass.cpp +++ b/libcxx/test/std/language.support/support.dynamic/new.delete/new.delete.single/new.size_nothrow.replace.indirect.pass.cpp @@ -10,6 +10,10 @@ // Test that we can replace the operator by replacing `operator new(std::size_t)` (the throwing version). +// This doesn't work when the shared library was built with exceptions disabled, because +// we can't implement the non-throwing new from the throwing new in that case. +// XFAIL: no-exceptions + // UNSUPPORTED: sanitizer-new-delete // XFAIL: libcpp-no-vcruntime // XFAIL: LIBCXX-AIX-FIXME diff --git a/libcxx/test/support/check_assertion.h b/libcxx/test/support/check_assertion.h index 1811df5f474f..c1d0bdcb33ec 100644 --- a/libcxx/test/support/check_assertion.h +++ b/libcxx/test/support/check_assertion.h @@ -9,7 +9,9 @@ #ifndef TEST_SUPPORT_CHECK_ASSERTION_H #define TEST_SUPPORT_CHECK_ASSERTION_H +#include #include +#include #include #include #include @@ -85,6 +87,7 @@ Matcher MakeAnyMatcher() { enum class DeathCause { // Valid causes VerboseAbort = 1, + StdAbort, StdTerminate, Trap, // Invalid causes @@ -96,6 +99,7 @@ enum class DeathCause { bool IsValidCause(DeathCause cause) { switch (cause) { case DeathCause::VerboseAbort: + case DeathCause::StdAbort: case DeathCause::StdTerminate: case DeathCause::Trap: return true; @@ -108,6 +112,8 @@ std::string ToString(DeathCause cause) { switch (cause) { case DeathCause::VerboseAbort: return "verbose abort"; + case DeathCause::StdAbort: + return "`std::abort`"; case DeathCause::StdTerminate: return "`std::terminate`"; case DeathCause::Trap: @@ -123,6 +129,19 @@ std::string ToString(DeathCause cause) { assert(false && "Unreachable"); } +template +std::string ToString(std::array const& causes) { + std::stringstream ss; + ss << "{"; + for (std::size_t i = 0; i != N; ++i) { + ss << ToString(causes[i]); + if (i+1 != N) + ss << ", "; + } + ss << "}"; + return ss.str(); +} + TEST_NORETURN void StopChildProcess(DeathCause cause) { std::exit(static_cast(cause)); } DeathCause ConvertToDeathCause(int val) { @@ -177,8 +196,9 @@ public: DeathTest(DeathTest const&) = delete; DeathTest& operator=(DeathTest const&) = delete; - template - DeathTestResult Run(DeathCause expected_cause, Func&& func, const Matcher& matcher) { + template + DeathTestResult Run(const std::array& expected_causes, Func&& func, const Matcher& matcher) { + std::signal(SIGABRT, [](int) { StopChildProcess(DeathCause::StdAbort); }); std::set_terminate([] { StopChildProcess(DeathCause::StdTerminate); }); DeathCause cause = Run(func); @@ -187,12 +207,12 @@ public: return DeathTestResult(Outcome::InvalidCause, cause, ToString(cause)); } - if (expected_cause != cause) { + if (std::find(expected_causes.begin(), expected_causes.end(), cause) == expected_causes.end()) { std::stringstream failure_description; failure_description // << "Child died, but with a different death cause\n" // - << "Expected cause: " << ToString(expected_cause) << "\n" // - << "Actual cause: " << ToString(cause) << "\n"; + << "Expected cause(s): " << ToString(expected_causes) << "\n" // + << "Actual cause: " << ToString(cause) << "\n"; return DeathTestResult(Outcome::UnexpectedCause, cause, failure_description.str()); } @@ -328,12 +348,13 @@ void std::__libcpp_verbose_abort(char const* format, ...) { } #endif // _LIBCPP_VERSION -template -bool ExpectDeath(DeathCause expected_cause, const char* stmt, Func&& func, const Matcher& matcher) { - assert(IsValidCause(expected_cause)); +template +bool ExpectDeath(const std::array& expected_causes, const char* stmt, Func&& func, const Matcher& matcher) { + for (auto cause : expected_causes) + assert(IsValidCause(cause)); DeathTest test_case; - DeathTestResult test_result = test_case.Run(expected_cause, func, matcher); + DeathTestResult test_result = test_case.Run(expected_causes, func, matcher); if (!test_result.success()) { test_case.PrintFailureDetails(test_result.failure_description(), stmt, test_result.cause()); } @@ -341,18 +362,32 @@ bool ExpectDeath(DeathCause expected_cause, const char* stmt, Func&& func, const return test_result.success(); } +template +bool ExpectDeath(DeathCause expected_cause, const char* stmt, Func&& func, const Matcher& matcher) { + return ExpectDeath(std::array{expected_cause}, stmt, func, matcher); +} + +template +bool ExpectDeath(const std::array& expected_causes, const char* stmt, Func&& func) { + return ExpectDeath(expected_causes, stmt, func, MakeAnyMatcher()); +} + template bool ExpectDeath(DeathCause expected_cause, const char* stmt, Func&& func) { - return ExpectDeath(expected_cause, stmt, func, MakeAnyMatcher()); + return ExpectDeath(std::array{expected_cause}, stmt, func, MakeAnyMatcher()); } // clang-format off /// Assert that the specified expression aborts with the expected cause and, optionally, error message. +#define EXPECT_ANY_DEATH(...) \ + assert(( ExpectDeath(std::array{DeathCause::VerboseAbort, DeathCause::StdAbort, DeathCause::StdTerminate, DeathCause::Trap}, #__VA_ARGS__, [&]() { __VA_ARGS__; } ) )) #define EXPECT_DEATH(...) \ assert(( ExpectDeath(DeathCause::VerboseAbort, #__VA_ARGS__, [&]() { __VA_ARGS__; } ) )) #define EXPECT_DEATH_MATCHES(matcher, ...) \ assert(( ExpectDeath(DeathCause::VerboseAbort, #__VA_ARGS__, [&]() { __VA_ARGS__; }, matcher) )) +#define EXPECT_STD_ABORT(...) \ + assert( ExpectDeath(DeathCause::StdAbort, #__VA_ARGS__, [&]() { __VA_ARGS__; }) ) #define EXPECT_STD_TERMINATE(...) \ assert( ExpectDeath(DeathCause::StdTerminate, #__VA_ARGS__, __VA_ARGS__) ) diff --git a/libcxx/test/support/count_new.h b/libcxx/test/support/count_new.h index ef4306e520b1..0d17e394d031 100644 --- a/libcxx/test/support/count_new.h +++ b/libcxx/test/support/count_new.h @@ -379,78 +379,161 @@ TEST_DIAGNOSTIC_POP MemCounter &globalMemCounter = *getGlobalMemCounter(); #ifndef DISABLE_NEW_COUNT -void* operator new(std::size_t s) TEST_THROW_SPEC(std::bad_alloc) -{ - getGlobalMemCounter()->newCalled(s); - void* ret = std::malloc(s); - if (ret == nullptr) - detail::throw_bad_alloc_helper(); - return ret; -} - -void operator delete(void* p) TEST_NOEXCEPT -{ - getGlobalMemCounter()->deleteCalled(p); - std::free(p); -} - -void* operator new[](std::size_t s) TEST_THROW_SPEC(std::bad_alloc) -{ - getGlobalMemCounter()->newArrayCalled(s); - return operator new(s); -} - -void operator delete[](void* p) TEST_NOEXCEPT -{ - getGlobalMemCounter()->deleteArrayCalled(p); - operator delete(p); -} - -#ifndef TEST_HAS_NO_ALIGNED_ALLOCATION -#if defined(_LIBCPP_MSVCRT_LIKE) || \ - (!defined(_LIBCPP_VERSION) && defined(_WIN32)) -#define USE_ALIGNED_ALLOC -#endif - -void* operator new(std::size_t s, std::align_val_t av) TEST_THROW_SPEC(std::bad_alloc) { - const std::size_t a = static_cast(av); - getGlobalMemCounter()->alignedNewCalled(s, a); - void *ret = nullptr; -#ifdef USE_ALIGNED_ALLOC - ret = _aligned_malloc(s, a); -#else - assert(posix_memalign(&ret, std::max(a, sizeof(void*)), s) != EINVAL); -#endif - if (ret == nullptr) +// operator new(size_t[, nothrow_t]) and operator delete(size_t[, nothrow_t]) +void* operator new(std::size_t s) TEST_THROW_SPEC(std::bad_alloc) { + getGlobalMemCounter()->newCalled(s); + void* p = std::malloc(s); + if (p == nullptr) detail::throw_bad_alloc_helper(); + return p; +} + +void* operator new(std::size_t s, std::nothrow_t const&) TEST_NOEXCEPT { +# ifdef TEST_HAS_NO_EXCEPTIONS + getGlobalMemCounter()->newCalled(s); +# else + try { + getGlobalMemCounter()->newCalled(s); + } catch (std::bad_alloc const&) { + return nullptr; + } +# endif + return std::malloc(s); +} + +void operator delete(void* p) TEST_NOEXCEPT { + getGlobalMemCounter()->deleteCalled(p); + std::free(p); +} + +void operator delete(void* p, std::nothrow_t const&) TEST_NOEXCEPT { + getGlobalMemCounter()->deleteCalled(p); + std::free(p); +} + +// operator new[](size_t[, nothrow_t]) and operator delete[](size_t[, nothrow_t]) +void* operator new[](std::size_t s) TEST_THROW_SPEC(std::bad_alloc) { + getGlobalMemCounter()->newArrayCalled(s); + void* p = std::malloc(s); + if (p == nullptr) + detail::throw_bad_alloc_helper(); + return p; +} + +void* operator new[](std::size_t s, std::nothrow_t const&) TEST_NOEXCEPT { +# ifdef TEST_HAS_NO_EXCEPTIONS + getGlobalMemCounter()->newArrayCalled(s); +# else + try { + getGlobalMemCounter()->newArrayCalled(s); + } catch (std::bad_alloc const&) { + return nullptr; + } +# endif + return std::malloc(s); +} + +void operator delete[](void* p) TEST_NOEXCEPT { + getGlobalMemCounter()->deleteArrayCalled(p); + std::free(p); +} + +void operator delete[](void* p, std::nothrow_t const&) TEST_NOEXCEPT { + getGlobalMemCounter()->deleteArrayCalled(p); + std::free(p); +} + +# ifndef TEST_HAS_NO_ALIGNED_ALLOCATION +# if defined(_LIBCPP_MSVCRT_LIKE) || (!defined(_LIBCPP_VERSION) && defined(_WIN32)) +# define USE_ALIGNED_ALLOC +# endif + +inline void* alocate_aligned_impl(std::size_t size, std::align_val_t align) { + const std::size_t alignment = static_cast(align); + void* ret = nullptr; +# ifdef USE_ALIGNED_ALLOC + ret = _aligned_malloc(size, alignment); +# else + assert(posix_memalign(&ret, std::max(alignment, sizeof(void*)), size) != EINVAL); +# endif return ret; } -void operator delete(void *p, std::align_val_t av) TEST_NOEXCEPT { - const std::size_t a = static_cast(av); - getGlobalMemCounter()->alignedDeleteCalled(p, a); - if (p) { -#ifdef USE_ALIGNED_ALLOC - ::_aligned_free(p); -#else - ::free(p); -#endif +inline void free_aligned_impl(void* ptr, std::align_val_t) { + if (ptr) { +# ifdef USE_ALIGNED_ALLOC + ::_aligned_free(ptr); +# else + ::free(ptr); +# endif } } +// operator new(size_t, align_val_t[, nothrow_t]) and operator delete(size_t, align_val_t[, nothrow_t]) +void* operator new(std::size_t s, std::align_val_t av) TEST_THROW_SPEC(std::bad_alloc) { + getGlobalMemCounter()->alignedNewCalled(s, static_cast(av)); + void* p = alocate_aligned_impl(s, av); + if (p == nullptr) + detail::throw_bad_alloc_helper(); + return p; +} + +void* operator new(std::size_t s, std::align_val_t av, std::nothrow_t const&) TEST_NOEXCEPT { +# ifdef TEST_HAS_NO_EXCEPTIONS + getGlobalMemCounter()->alignedNewCalled(s, static_cast(av)); +# else + try { + getGlobalMemCounter()->alignedNewCalled(s, static_cast(av)); + } catch (std::bad_alloc const&) { + return nullptr; + } +# endif + return alocate_aligned_impl(s, av); +} + +void operator delete(void* p, std::align_val_t av) TEST_NOEXCEPT { + getGlobalMemCounter()->alignedDeleteCalled(p, static_cast(av)); + free_aligned_impl(p, av); +} + +void operator delete(void* p, std::align_val_t av, std::nothrow_t const&) TEST_NOEXCEPT { + getGlobalMemCounter()->alignedDeleteCalled(p, static_cast(av)); + free_aligned_impl(p, av); +} + +// operator new[](size_t, align_val_t[, nothrow_t]) and operator delete[](size_t, align_val_t[, nothrow_t]) void* operator new[](std::size_t s, std::align_val_t av) TEST_THROW_SPEC(std::bad_alloc) { - const std::size_t a = static_cast(av); - getGlobalMemCounter()->alignedNewArrayCalled(s, a); - return operator new(s, av); + getGlobalMemCounter()->alignedNewArrayCalled(s, static_cast(av)); + void* p = alocate_aligned_impl(s, av); + if (p == nullptr) + detail::throw_bad_alloc_helper(); + return p; } -void operator delete[](void *p, std::align_val_t av) TEST_NOEXCEPT { - const std::size_t a = static_cast(av); - getGlobalMemCounter()->alignedDeleteArrayCalled(p, a); - return operator delete(p, av); +void* operator new[](std::size_t s, std::align_val_t av, std::nothrow_t const&) TEST_NOEXCEPT { +# ifdef TEST_HAS_NO_EXCEPTIONS + getGlobalMemCounter()->alignedNewArrayCalled(s, static_cast(av)); +# else + try { + getGlobalMemCounter()->alignedNewArrayCalled(s, static_cast(av)); + } catch (std::bad_alloc const&) { + return nullptr; + } +# endif + return alocate_aligned_impl(s, av); } -#endif // TEST_HAS_NO_ALIGNED_ALLOCATION +void operator delete[](void* p, std::align_val_t av) TEST_NOEXCEPT { + getGlobalMemCounter()->alignedDeleteArrayCalled(p, static_cast(av)); + free_aligned_impl(p, av); +} + +void operator delete[](void* p, std::align_val_t av, std::nothrow_t const&) TEST_NOEXCEPT { + getGlobalMemCounter()->alignedDeleteArrayCalled(p, static_cast(av)); + free_aligned_impl(p, av); +} + +# endif // TEST_HAS_NO_ALIGNED_ALLOCATION #endif // DISABLE_NEW_COUNT diff --git a/libcxx/test/support/test.support/test_check_assertion.pass.cpp b/libcxx/test/support/test.support/test_check_assertion.pass.cpp index d1ac6717267f..4dfc5319aaf9 100644 --- a/libcxx/test/support/test.support/test_check_assertion.pass.cpp +++ b/libcxx/test/support/test.support/test_check_assertion.pass.cpp @@ -30,7 +30,7 @@ bool TestDeathTest( }; DeathTest test_case; - DeathTestResult test_result = test_case.Run(expected_cause, func, get_matcher()); + DeathTestResult test_result = test_case.Run(std::array{expected_cause}, func, get_matcher()); std::string maybe_failure_description; Outcome outcome = test_result.outcome(); @@ -109,16 +109,21 @@ int main(int, char**) { // Test the `EXPECT_DEATH` macros themselves. Since they assert success, we can only test successful cases. { - auto invoke_abort = [] { _LIBCPP_VERBOSE_ABORT("contains some message"); }; + auto invoke_verbose_abort = [] { _LIBCPP_VERBOSE_ABORT("contains some message"); }; + auto invoke_abort = [] { std::abort(); }; auto simple_matcher = [](const std::string& text) { bool success = text.find("some") != std::string::npos; return MatchResult(success, ""); }; - EXPECT_DEATH(invoke_abort()); - EXPECT_DEATH_MATCHES(MakeAnyMatcher(), invoke_abort()); - EXPECT_DEATH_MATCHES(simple_matcher, invoke_abort()); + EXPECT_ANY_DEATH(_LIBCPP_VERBOSE_ABORT("")); + EXPECT_ANY_DEATH(std::abort()); + EXPECT_ANY_DEATH(std::terminate()); + EXPECT_DEATH(invoke_verbose_abort()); + EXPECT_DEATH_MATCHES(MakeAnyMatcher(), invoke_verbose_abort()); + EXPECT_DEATH_MATCHES(simple_matcher, invoke_verbose_abort()); + EXPECT_STD_ABORT(invoke_abort()); EXPECT_STD_TERMINATE([] { std::terminate(); }); TEST_LIBCPP_ASSERT_FAILURE(fail_assert(), "Some message"); } diff --git a/libcxxabi/src/stdlib_new_delete.cpp b/libcxxabi/src/stdlib_new_delete.cpp index f8a00ec58425..b802559d479e 100644 --- a/libcxxabi/src/stdlib_new_delete.cpp +++ b/libcxxabi/src/stdlib_new_delete.cpp @@ -7,7 +7,10 @@ //===----------------------------------------------------------------------===// #include "__cxxabi_config.h" +#include "abort_message.h" +#include "include/overridable_function.h" // from libc++ #include <__memory/aligned_alloc.h> +#include #include #include @@ -25,6 +28,20 @@ # error libc++ and libc++abi seem to disagree on whether exceptions are enabled #endif +inline void __throw_bad_alloc_shim() { +#ifndef _LIBCPP_HAS_NO_EXCEPTIONS + throw std::bad_alloc(); +#else + abort_message("bad_alloc was thrown in -fno-exceptions mode"); +#endif +} + +#define _LIBCPP_ASSERT_SHIM(expr, str) \ + do { \ + if (!expr) \ + abort_message(str); \ + } while (false) + // ------------------ BEGIN COPY ------------------ // Implement all new and delete operators as weak definitions // in this shared library, so that they can be overridden by programs @@ -46,64 +63,76 @@ static void* operator_new_impl(std::size_t size) { return p; } -_LIBCPP_WEAK -void* operator new(std::size_t size) _THROW_BAD_ALLOC { +_LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE _LIBCPP_WEAK void* operator new(std::size_t size) _THROW_BAD_ALLOC { void* p = operator_new_impl(size); -#ifndef _LIBCPP_HAS_NO_EXCEPTIONS if (p == nullptr) - throw std::bad_alloc(); -#endif + __throw_bad_alloc_shim(); return p; } -_LIBCPP_WEAK -void* operator new(size_t size, const std::nothrow_t&) noexcept { +_LIBCPP_WEAK void* operator new(size_t size, const std::nothrow_t&) noexcept { +#ifdef _LIBCPP_HAS_NO_EXCEPTIONS +# if _LIBCPP_CAN_DETECT_OVERRIDDEN_FUNCTION + _LIBCPP_ASSERT_SHIM( + !std::__is_function_overridden(static_cast(&operator new)), + "libc++ was configured with exceptions disabled and `operator new(size_t)` has been overridden, " + "but `operator new(size_t, nothrow_t)` has not been overridden. This is problematic because " + "`operator new(size_t, nothrow_t)` must call `operator new(size_t)`, which will terminate in case " + "it fails to allocate, making it impossible for `operator new(size_t, nothrow_t)` to fulfill its " + "contract (since it should return nullptr upon failure). Please make sure you override " + "`operator new(size_t, nothrow_t)` as well."); +# endif + + return operator_new_impl(size); +#else void* p = nullptr; -#ifndef _LIBCPP_HAS_NO_EXCEPTIONS try { -#endif // _LIBCPP_HAS_NO_EXCEPTIONS p = ::operator new(size); -#ifndef _LIBCPP_HAS_NO_EXCEPTIONS } catch (...) { } -#endif // _LIBCPP_HAS_NO_EXCEPTIONS return p; +#endif } -_LIBCPP_WEAK -void* operator new[](size_t size) _THROW_BAD_ALLOC { return ::operator new(size); } +_LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE _LIBCPP_WEAK void* operator new[](size_t size) _THROW_BAD_ALLOC { + return ::operator new(size); +} -_LIBCPP_WEAK -void* operator new[](size_t size, const std::nothrow_t&) noexcept { +_LIBCPP_WEAK void* operator new[](size_t size, const std::nothrow_t&) noexcept { +#ifdef _LIBCPP_HAS_NO_EXCEPTIONS +# if _LIBCPP_CAN_DETECT_OVERRIDDEN_FUNCTION + _LIBCPP_ASSERT_SHIM( + !std::__is_function_overridden(static_cast(&operator new[])), + "libc++ was configured with exceptions disabled and `operator new[](size_t)` has been overridden, " + "but `operator new[](size_t, nothrow_t)` has not been overridden. This is problematic because " + "`operator new[](size_t, nothrow_t)` must call `operator new[](size_t)`, which will terminate in case " + "it fails to allocate, making it impossible for `operator new[](size_t, nothrow_t)` to fulfill its " + "contract (since it should return nullptr upon failure). Please make sure you override " + "`operator new[](size_t, nothrow_t)` as well."); +# endif + + return operator_new_impl(size); +#else void* p = nullptr; -#ifndef _LIBCPP_HAS_NO_EXCEPTIONS try { -#endif // _LIBCPP_HAS_NO_EXCEPTIONS p = ::operator new[](size); -#ifndef _LIBCPP_HAS_NO_EXCEPTIONS } catch (...) { } -#endif // _LIBCPP_HAS_NO_EXCEPTIONS return p; +#endif } -_LIBCPP_WEAK -void operator delete(void* ptr) noexcept { std::free(ptr); } +_LIBCPP_WEAK void operator delete(void* ptr) noexcept { std::free(ptr); } -_LIBCPP_WEAK -void operator delete(void* ptr, const std::nothrow_t&) noexcept { ::operator delete(ptr); } +_LIBCPP_WEAK void operator delete(void* ptr, const std::nothrow_t&) noexcept { ::operator delete(ptr); } -_LIBCPP_WEAK -void operator delete(void* ptr, size_t) noexcept { ::operator delete(ptr); } +_LIBCPP_WEAK void operator delete(void* ptr, size_t) noexcept { ::operator delete(ptr); } -_LIBCPP_WEAK -void operator delete[](void* ptr) noexcept { ::operator delete(ptr); } +_LIBCPP_WEAK void operator delete[](void* ptr) noexcept { ::operator delete(ptr); } -_LIBCPP_WEAK -void operator delete[](void* ptr, const std::nothrow_t&) noexcept { ::operator delete[](ptr); } +_LIBCPP_WEAK void operator delete[](void* ptr, const std::nothrow_t&) noexcept { ::operator delete[](ptr); } -_LIBCPP_WEAK -void operator delete[](void* ptr, size_t) noexcept { ::operator delete[](ptr); } +_LIBCPP_WEAK void operator delete[](void* ptr, size_t) noexcept { ::operator delete[](ptr); } #if !defined(_LIBCPP_HAS_NO_LIBRARY_ALIGNED_ALLOCATION) @@ -127,70 +156,89 @@ static void* operator_new_aligned_impl(std::size_t size, std::align_val_t alignm return p; } -_LIBCPP_WEAK -void* operator new(std::size_t size, std::align_val_t alignment) _THROW_BAD_ALLOC { +_LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE _LIBCPP_WEAK void* +operator new(std::size_t size, std::align_val_t alignment) _THROW_BAD_ALLOC { void* p = operator_new_aligned_impl(size, alignment); -# ifndef _LIBCPP_HAS_NO_EXCEPTIONS if (p == nullptr) - throw std::bad_alloc(); -# endif + __throw_bad_alloc_shim(); return p; } -_LIBCPP_WEAK -void* operator new(size_t size, std::align_val_t alignment, const std::nothrow_t&) noexcept { +_LIBCPP_WEAK void* operator new(size_t size, std::align_val_t alignment, const std::nothrow_t&) noexcept { +# ifdef _LIBCPP_HAS_NO_EXCEPTIONS +# if _LIBCPP_CAN_DETECT_OVERRIDDEN_FUNCTION + _LIBCPP_ASSERT_SHIM( + !std::__is_function_overridden(static_cast(&operator new)), + "libc++ was configured with exceptions disabled and `operator new(size_t, align_val_t)` has been overridden, " + "but `operator new(size_t, align_val_t, nothrow_t)` has not been overridden. This is problematic because " + "`operator new(size_t, align_val_t, nothrow_t)` must call `operator new(size_t, align_val_t)`, which will " + "terminate in case it fails to allocate, making it impossible for `operator new(size_t, align_val_t, nothrow_t)` " + "to fulfill its contract (since it should return nullptr upon failure). Please make sure you override " + "`operator new(size_t, align_val_t, nothrow_t)` as well."); +# endif + + return operator_new_aligned_impl(size, alignment); +# else void* p = nullptr; -# ifndef _LIBCPP_HAS_NO_EXCEPTIONS try { -# endif // _LIBCPP_HAS_NO_EXCEPTIONS p = ::operator new(size, alignment); -# ifndef _LIBCPP_HAS_NO_EXCEPTIONS } catch (...) { } -# endif // _LIBCPP_HAS_NO_EXCEPTIONS return p; +# endif } -_LIBCPP_WEAK -void* operator new[](size_t size, std::align_val_t alignment) _THROW_BAD_ALLOC { +_LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE _LIBCPP_WEAK void* +operator new[](size_t size, std::align_val_t alignment) _THROW_BAD_ALLOC { return ::operator new(size, alignment); } -_LIBCPP_WEAK -void* operator new[](size_t size, std::align_val_t alignment, const std::nothrow_t&) noexcept { +_LIBCPP_WEAK void* operator new[](size_t size, std::align_val_t alignment, const std::nothrow_t&) noexcept { +# ifdef _LIBCPP_HAS_NO_EXCEPTIONS +# if _LIBCPP_CAN_DETECT_OVERRIDDEN_FUNCTION + _LIBCPP_ASSERT_SHIM( + !std::__is_function_overridden(static_cast(&operator new[])), + "libc++ was configured with exceptions disabled and `operator new[](size_t, align_val_t)` has been overridden, " + "but `operator new[](size_t, align_val_t, nothrow_t)` has not been overridden. This is problematic because " + "`operator new[](size_t, align_val_t, nothrow_t)` must call `operator new[](size_t, align_val_t)`, which will " + "terminate in case it fails to allocate, making it impossible for `operator new[](size_t, align_val_t, " + "nothrow_t)` to fulfill its contract (since it should return nullptr upon failure). Please make sure you " + "override " + "`operator new[](size_t, align_val_t, nothrow_t)` as well."); +# endif + + return operator_new_aligned_impl(size, alignment); +# else void* p = nullptr; -# ifndef _LIBCPP_HAS_NO_EXCEPTIONS try { -# endif // _LIBCPP_HAS_NO_EXCEPTIONS p = ::operator new[](size, alignment); -# ifndef _LIBCPP_HAS_NO_EXCEPTIONS } catch (...) { } -# endif // _LIBCPP_HAS_NO_EXCEPTIONS return p; +# endif } -_LIBCPP_WEAK -void operator delete(void* ptr, std::align_val_t) noexcept { std::__libcpp_aligned_free(ptr); } +_LIBCPP_WEAK void operator delete(void* ptr, std::align_val_t) noexcept { std::__libcpp_aligned_free(ptr); } -_LIBCPP_WEAK -void operator delete(void* ptr, std::align_val_t alignment, const std::nothrow_t&) noexcept { +_LIBCPP_WEAK void operator delete(void* ptr, std::align_val_t alignment, const std::nothrow_t&) noexcept { ::operator delete(ptr, alignment); } -_LIBCPP_WEAK -void operator delete(void* ptr, size_t, std::align_val_t alignment) noexcept { ::operator delete(ptr, alignment); } +_LIBCPP_WEAK void operator delete(void* ptr, size_t, std::align_val_t alignment) noexcept { + ::operator delete(ptr, alignment); +} -_LIBCPP_WEAK -void operator delete[](void* ptr, std::align_val_t alignment) noexcept { ::operator delete(ptr, alignment); } +_LIBCPP_WEAK void operator delete[](void* ptr, std::align_val_t alignment) noexcept { + ::operator delete(ptr, alignment); +} -_LIBCPP_WEAK -void operator delete[](void* ptr, std::align_val_t alignment, const std::nothrow_t&) noexcept { +_LIBCPP_WEAK void operator delete[](void* ptr, std::align_val_t alignment, const std::nothrow_t&) noexcept { ::operator delete[](ptr, alignment); } -_LIBCPP_WEAK -void operator delete[](void* ptr, size_t, std::align_val_t alignment) noexcept { ::operator delete[](ptr, alignment); } +_LIBCPP_WEAK void operator delete[](void* ptr, size_t, std::align_val_t alignment) noexcept { + ::operator delete[](ptr, alignment); +} #endif // !_LIBCPP_HAS_NO_LIBRARY_ALIGNED_ALLOCATION // ------------------ END COPY ------------------