diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -533,6 +533,7 @@ __numeric/transform_exclusive_scan.h __numeric/transform_inclusive_scan.h __numeric/transform_reduce.h + __overridable_function __pstl/internal/algorithm_fwd.h __pstl/internal/algorithm_impl.h __pstl/internal/execution_defs.h diff --git a/libcxx/include/__overridable_function b/libcxx/include/__overridable_function new file mode 100644 --- /dev/null +++ b/libcxx/include/__overridable_function @@ -0,0 +1,38 @@ +// -*- 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___OVERRIDABLE_FUNCTION +#define _LIBCPP___OVERRIDABLE_FUNCTION + +#include <__config> +#include + +_LIBCPP_BEGIN_NAMESPACE_STD + +#define _LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE \ + __attribute__((__section__("__TEXT,__lcxx_override,regular,pure_instructions"))) + +template +_LIBCPP_HIDE_FROM_ABI bool __is_function_overridden(_Ret (*__ptr)(_Args...)) noexcept { + // The linker places these at the start/end of the __lcxx_override section, + // which is where we put the definitions for any overridable function, via + // _LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE. + // + // Placing the function in a separate section allows for this logic to be applied + // even in the scenario where clients are statically linking against libc++/libc++abi. + extern const uintptr_t __lcxx_override_start __asm("section$start$__TEXT$__lcxx_override"); + extern const uintptr_t __lcxx_override_end __asm("section$end$__TEXT$__lcxx_override"); + uintptr_t* __uptr = reinterpret_cast(__ptr); + + return __uptr < &__lcxx_override_start || __uptr > &__lcxx_override_end; +} + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___OVERRIDABLE_FUNCTION diff --git a/libcxx/include/new b/libcxx/include/new --- a/libcxx/include/new +++ b/libcxx/include/new @@ -90,6 +90,7 @@ #include <__availability> #include <__config> #include <__exception/exception.h> +#include <__overridable_function> #include <__type_traits/alignment_of.h> #include <__type_traits/is_function.h> #include <__type_traits/is_same.h> @@ -210,7 +211,7 @@ #if !defined(_LIBCPP_ABI_VCRUNTIME) -_LIBCPP_NODISCARD_AFTER_CXX17 _LIBCPP_OVERRIDABLE_FUNC_VIS void* operator new(std::size_t __sz) _THROW_BAD_ALLOC; +_LIBCPP_NODISCARD_AFTER_CXX17 _LIBCPP_OVERRIDABLE_FUNC_VIS _LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE void* operator new(std::size_t __sz) _THROW_BAD_ALLOC; _LIBCPP_NODISCARD_AFTER_CXX17 _LIBCPP_OVERRIDABLE_FUNC_VIS void* operator new(std::size_t __sz, const std::nothrow_t&) _NOEXCEPT _LIBCPP_NOALIAS; _LIBCPP_OVERRIDABLE_FUNC_VIS void operator delete(void* __p) _NOEXCEPT; _LIBCPP_OVERRIDABLE_FUNC_VIS void operator delete(void* __p, const std::nothrow_t&) _NOEXCEPT; @@ -218,7 +219,7 @@ _LIBCPP_OVERRIDABLE_FUNC_VIS _LIBCPP_AVAILABILITY_SIZED_NEW_DELETE void operator delete(void* __p, std::size_t __sz) _NOEXCEPT; #endif -_LIBCPP_NODISCARD_AFTER_CXX17 _LIBCPP_OVERRIDABLE_FUNC_VIS void* operator new[](std::size_t __sz) _THROW_BAD_ALLOC; +_LIBCPP_NODISCARD_AFTER_CXX17 _LIBCPP_OVERRIDABLE_FUNC_VIS _LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE void* operator new[](std::size_t __sz) _THROW_BAD_ALLOC; _LIBCPP_NODISCARD_AFTER_CXX17 _LIBCPP_OVERRIDABLE_FUNC_VIS void* operator new[](std::size_t __sz, const std::nothrow_t&) _NOEXCEPT _LIBCPP_NOALIAS; _LIBCPP_OVERRIDABLE_FUNC_VIS void operator delete[](void* __p) _NOEXCEPT; _LIBCPP_OVERRIDABLE_FUNC_VIS void operator delete[](void* __p, const std::nothrow_t&) _NOEXCEPT; @@ -227,7 +228,7 @@ #endif #ifndef _LIBCPP_HAS_NO_LIBRARY_ALIGNED_ALLOCATION -_LIBCPP_NODISCARD_AFTER_CXX17 _LIBCPP_OVERRIDABLE_FUNC_VIS void* operator new(std::size_t __sz, std::align_val_t) _THROW_BAD_ALLOC; +_LIBCPP_NODISCARD_AFTER_CXX17 _LIBCPP_OVERRIDABLE_FUNC_VIS _LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE void* operator new(std::size_t __sz, std::align_val_t) _THROW_BAD_ALLOC; _LIBCPP_NODISCARD_AFTER_CXX17 _LIBCPP_OVERRIDABLE_FUNC_VIS void* operator new(std::size_t __sz, std::align_val_t, const std::nothrow_t&) _NOEXCEPT _LIBCPP_NOALIAS; _LIBCPP_OVERRIDABLE_FUNC_VIS void operator delete(void* __p, std::align_val_t) _NOEXCEPT; _LIBCPP_OVERRIDABLE_FUNC_VIS void operator delete(void* __p, std::align_val_t, const std::nothrow_t&) _NOEXCEPT; @@ -235,7 +236,7 @@ _LIBCPP_OVERRIDABLE_FUNC_VIS _LIBCPP_AVAILABILITY_SIZED_NEW_DELETE void operator delete(void* __p, std::size_t __sz, std::align_val_t) _NOEXCEPT; #endif -_LIBCPP_NODISCARD_AFTER_CXX17 _LIBCPP_OVERRIDABLE_FUNC_VIS void* operator new[](std::size_t __sz, std::align_val_t) _THROW_BAD_ALLOC; +_LIBCPP_NODISCARD_AFTER_CXX17 _LIBCPP_OVERRIDABLE_FUNC_VIS _LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE void* operator new[](std::size_t __sz, std::align_val_t) _THROW_BAD_ALLOC; _LIBCPP_NODISCARD_AFTER_CXX17 _LIBCPP_OVERRIDABLE_FUNC_VIS void* operator new[](std::size_t __sz, std::align_val_t, const std::nothrow_t&) _NOEXCEPT _LIBCPP_NOALIAS; _LIBCPP_OVERRIDABLE_FUNC_VIS void operator delete[](void* __p, std::align_val_t) _NOEXCEPT; _LIBCPP_OVERRIDABLE_FUNC_VIS void operator delete[](void* __p, std::align_val_t, const std::nothrow_t&) _NOEXCEPT; diff --git a/libcxx/src/new.cpp b/libcxx/src/new.cpp --- a/libcxx/src/new.cpp +++ b/libcxx/src/new.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include <__memory/aligned_alloc.h> +#include <__overridable_function> #include #include @@ -40,11 +41,9 @@ void * operator new(std::size_t size) _THROW_BAD_ALLOC { - void* p = allocate_impl(size); -#ifndef _LIBCPP_HAS_NO_EXCEPTIONS + void* p = operator_new_impl(size); if (p == nullptr) - throw std::bad_alloc(); -#endif + std::__throw_bad_alloc(); return p; } @@ -52,19 +51,23 @@ void* operator new(size_t size, const std::nothrow_t&) noexcept { +#ifdef _LIBCPP_HAS_NO_EXCEPTIONS + _LIBCPP_ASSERT(!std::__is_function_overridden(&operator new(size_t)), + "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)."); + + return operator_new_impl(size); +#else void* p = nullptr; -#ifndef _LIBCPP_HAS_NO_EXCEPTIONS - try - { -#endif // _LIBCPP_HAS_NO_EXCEPTIONS + try { p = ::operator new(size); -#ifndef _LIBCPP_HAS_NO_EXCEPTIONS + } catch (...) { } - catch (...) - { - } -#endif // _LIBCPP_HAS_NO_EXCEPTIONS return p; +#endif } _LIBCPP_WEAK @@ -78,19 +81,23 @@ void* operator new[](size_t size, const std::nothrow_t&) noexcept { +#ifdef _LIBCPP_HAS_NO_EXCEPTIONS + _LIBCPP_ASSERT(!std::__is_function_overridden(&operator new[](size_t)), + "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)."); + + return operator_new_impl(size); +#else void* p = nullptr; -#ifndef _LIBCPP_HAS_NO_EXCEPTIONS - try - { -#endif // _LIBCPP_HAS_NO_EXCEPTIONS + try { p = ::operator new[](size); -#ifndef _LIBCPP_HAS_NO_EXCEPTIONS - } - catch (...) - { + } catch (...) { } -#endif // _LIBCPP_HAS_NO_EXCEPTIONS return p; +#endif } _LIBCPP_WEAK @@ -162,10 +169,8 @@ 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 + std::__throw_bad_alloc(); return p; } @@ -173,19 +178,23 @@ void* operator new(size_t size, std::align_val_t alignment, const std::nothrow_t&) noexcept { +#ifdef _LIBCPP_HAS_NO_EXCEPTIONS + _LIBCPP_ASSERT(!std::__is_function_overridden(&operator new(std::size_t, std::align_val_t)), + "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)."); + + return operator_new_aligned_impl(size, alignment); +#else void* p = nullptr; -#ifndef _LIBCPP_HAS_NO_EXCEPTIONS - try - { -#endif // _LIBCPP_HAS_NO_EXCEPTIONS + try { p = ::operator new(size, alignment); -#ifndef _LIBCPP_HAS_NO_EXCEPTIONS + } catch (...) { } - catch (...) - { - } -#endif // _LIBCPP_HAS_NO_EXCEPTIONS return p; +#endif } _LIBCPP_WEAK @@ -199,19 +208,23 @@ void* operator new[](size_t size, std::align_val_t alignment, const std::nothrow_t&) noexcept { +#ifdef _LIBCPP_HAS_NO_EXCEPTIONS + _LIBCPP_ASSERT(!std::__is_function_overridden(&operator new[](std::size_t, std::align_val_t)), + "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)."); + + return operator_new_aligned_impl(size, alignment); +#else void* p = nullptr; -#ifndef _LIBCPP_HAS_NO_EXCEPTIONS - try - { -#endif // _LIBCPP_HAS_NO_EXCEPTIONS + try { p = ::operator new[](size, alignment); -#ifndef _LIBCPP_HAS_NO_EXCEPTIONS - } - catch (...) - { + } catch (...) { } -#endif // _LIBCPP_HAS_NO_EXCEPTIONS return p; +#endif } _LIBCPP_WEAK 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 --- /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_DEATH((void)operator new(std::numeric_limits::max())); + EXPECT_DEATH((void)operator new(std::numeric_limits::max(), static_cast(32))); + EXPECT_DEATH((void)operator new[](std::numeric_limits::max())); + EXPECT_DEATH((void)operator new[](std::numeric_limits::max(), static_cast(32))); + return 0; +} diff --git a/libcxxabi/src/stdlib_new_delete.cpp b/libcxxabi/src/stdlib_new_delete.cpp --- a/libcxxabi/src/stdlib_new_delete.cpp +++ b/libcxxabi/src/stdlib_new_delete.cpp @@ -8,6 +8,7 @@ #include "__cxxabi_config.h" #include <__memory/aligned_alloc.h> +#include <__overridable_function> #include #include @@ -30,7 +31,7 @@ // in this shared library, so that they can be overridden by programs // that define non-weak copies of the functions. -static void* operator_new_impl(std::size_t size) { +static void* operator_new_impl(std::size_t size) noexcept { if (size == 0) size = 1; void* p; @@ -50,11 +51,9 @@ void * operator new(std::size_t size) _THROW_BAD_ALLOC { - void* p = allocate_impl(size); -#ifndef _LIBCPP_HAS_NO_EXCEPTIONS + void* p = operator_new_impl(size); if (p == nullptr) - throw std::bad_alloc(); -#endif + std::__throw_bad_alloc(); return p; } @@ -62,19 +61,23 @@ void* operator new(size_t size, const std::nothrow_t&) noexcept { +#ifdef _LIBCPP_HAS_NO_EXCEPTIONS + _LIBCPP_ASSERT(!std::__is_function_overridden(&operator new(size_t)), + "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)."); + + return operator_new_impl(size); +#else void* p = nullptr; -#ifndef _LIBCPP_HAS_NO_EXCEPTIONS - try - { -#endif // _LIBCPP_HAS_NO_EXCEPTIONS + try { p = ::operator new(size); -#ifndef _LIBCPP_HAS_NO_EXCEPTIONS + } catch (...) { } - catch (...) - { - } -#endif // _LIBCPP_HAS_NO_EXCEPTIONS return p; +#endif } _LIBCPP_WEAK @@ -88,19 +91,23 @@ void* operator new[](size_t size, const std::nothrow_t&) noexcept { +#ifdef _LIBCPP_HAS_NO_EXCEPTIONS + _LIBCPP_ASSERT(!std::__is_function_overridden(&operator new[](size_t)), + "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)."); + + return operator_new_impl(size); +#else void* p = nullptr; -#ifndef _LIBCPP_HAS_NO_EXCEPTIONS - try - { -#endif // _LIBCPP_HAS_NO_EXCEPTIONS + try { p = ::operator new[](size); -#ifndef _LIBCPP_HAS_NO_EXCEPTIONS - } - catch (...) - { + } catch (...) { } -#endif // _LIBCPP_HAS_NO_EXCEPTIONS return p; +#endif } _LIBCPP_WEAK @@ -147,7 +154,7 @@ #if !defined(_LIBCPP_HAS_NO_LIBRARY_ALIGNED_ALLOCATION) -static void* operator_new_aligned_impl(std::size_t size, std::align_val_t alignment) { +static void* operator_new_aligned_impl(std::size_t size, std::align_val_t alignment) noexcept { if (size == 0) size = 1; if (static_cast(alignment) < sizeof(void*)) @@ -172,10 +179,8 @@ 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 + std::__throw_bad_alloc(); return p; } @@ -183,19 +188,23 @@ void* operator new(size_t size, std::align_val_t alignment, const std::nothrow_t&) noexcept { +#ifdef _LIBCPP_HAS_NO_EXCEPTIONS + _LIBCPP_ASSERT(!std::__is_function_overridden(&operator new(std::size_t, std::align_val_t)), + "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)."); + + return operator_new_aligned_impl(size, alignment); +#else void* p = nullptr; -#ifndef _LIBCPP_HAS_NO_EXCEPTIONS - try - { -#endif // _LIBCPP_HAS_NO_EXCEPTIONS + try { p = ::operator new(size, alignment); -#ifndef _LIBCPP_HAS_NO_EXCEPTIONS + } catch (...) { } - catch (...) - { - } -#endif // _LIBCPP_HAS_NO_EXCEPTIONS return p; +#endif } _LIBCPP_WEAK @@ -209,19 +218,23 @@ void* operator new[](size_t size, std::align_val_t alignment, const std::nothrow_t&) noexcept { +#ifdef _LIBCPP_HAS_NO_EXCEPTIONS + _LIBCPP_ASSERT(!std::__is_function_overridden(&operator new[](std::size_t, std::align_val_t)), + "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)."); + + return operator_new_aligned_impl(size, alignment); +#else void* p = nullptr; -#ifndef _LIBCPP_HAS_NO_EXCEPTIONS - try - { -#endif // _LIBCPP_HAS_NO_EXCEPTIONS + try { p = ::operator new[](size, alignment); -#ifndef _LIBCPP_HAS_NO_EXCEPTIONS - } - catch (...) - { + } catch (...) { } -#endif // _LIBCPP_HAS_NO_EXCEPTIONS return p; +#endif } _LIBCPP_WEAK