| 1 | //===-- sanitizer_new_handler.h ---------------------------------*- C++ -*-===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // This file is shared between run-time libraries of sanitizers. |
| 10 | // |
| 11 | // It provides the operator new chain-handling framework required by |
| 12 | // [new.delete.single]/3+/4: a std::get_new_handler() loop plus the |
| 13 | // throwing / nothrow exhaustion policies that compose with it. Each |
| 14 | // sanitizer's operator new wrapper supplies two small lambdas: |
| 15 | // |
| 16 | // * Alloc — invokes the sanitizer's internal allocator. Returns |
| 17 | // nullptr, rather than aborting, only when storage cannot |
| 18 | // be obtained: every nullptr return is treated as a |
| 19 | // storage failure by the std::get_new_handler() loop. |
| 20 | // Other detected failures (e.g. invalid alignment) must |
| 21 | // abort with a diagnostic. |
| 22 | // |
| 23 | // * OnExhausted — invokes the sanitizer's "abort with diagnostic" |
| 24 | // handler (e.g. asan's ReportOutOfMemory + Die()). |
| 25 | // Required to never return (enforced by UNREACHABLE()). |
| 26 | //===----------------------------------------------------------------------===// |
| 27 | #ifndef SANITIZER_NEW_HANDLER_H |
| 28 | #define SANITIZER_NEW_HANDLER_H |
| 29 | |
| 30 | #include "sanitizer_allocator.h" |
| 31 | #include "sanitizer_internal_defs.h" |
| 32 | #include "sanitizer_platform.h" |
| 33 | |
| 34 | #if !SANITIZER_WINDOWS && defined(__cpp_exceptions) |
| 35 | # include <new> |
| 36 | #else |
| 37 | // Builds without exception support can't include <new>: on Windows the |
| 38 | // sanitizer runtimes are built without exceptions; elsewhere this TU may |
| 39 | // have been compiled with -fno-exceptions (e.g. with |
| 40 | // -DCOMPILER_RT_ASAN_ENABLE_EXCEPTIONS=OFF). Forward-declare just enough |
| 41 | // of std for the loop; the real std::get_new_handler is supplied by the |
| 42 | // C++ runtime the sanitizer links against (vcruntime / msvcprt / |
| 43 | // MinGW libstdc++ / libstdc++ / libc++) — it doesn't need to come from |
| 44 | // <new>. |
| 45 | namespace std { |
| 46 | struct nothrow_t {}; |
| 47 | enum class align_val_t : __sanitizer::usize {}; |
| 48 | using new_handler = void (*)(); |
| 49 | new_handler get_new_handler() noexcept; |
| 50 | } // namespace std |
| 51 | #endif // !SANITIZER_WINDOWS && defined(__cpp_exceptions) |
| 52 | |
| 53 | namespace __sanitizer { |
| 54 | |
| 55 | // Runs std::get_new_handler() per [new.delete.single]/3+/4 until the |
| 56 | // allocation succeeds (returns the allocated pointer) or the chain is |
| 57 | // exhausted (returns nullptr). |
| 58 | // |
| 59 | // NOTE: Exceptions thrown by Alloc or std::new_handler callbacks escape this |
| 60 | // function. Callers that need to convert exceptions to a nullptr return |
| 61 | // (e.g. NewImplNothrow below) must wrap the call in try/catch themselves. |
| 62 | template <typename Alloc> |
| 63 | void* RunNewHandlerChain(Alloc alloc) { |
| 64 | for (;;) { |
| 65 | void* res = alloc(); |
| 66 | if (LIKELY(res != nullptr)) |
| 67 | return res; |
| 68 | std::new_handler handler = std::get_new_handler(); |
| 69 | if (!handler) |
| 70 | return nullptr; |
| 71 | handler(); |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | // NORETURN wrapper providing pre-C++23 compatibility. |
| 76 | template <typename OnExhausted> |
| 77 | NORETURN void InvokeOnExhausted(OnExhausted on_exhausted) { |
| 78 | on_exhausted(); |
| 79 | UNREACHABLE("operator new OnExhausted callable returned" ); |
| 80 | } |
| 81 | |
| 82 | // Throwing operator new: chain, then on exhaustion throw std::bad_alloc |
| 83 | // when this TU was compiled with exception support and AllocatorMayReturnNull() |
| 84 | // is true. Otherwise (Windows runtimes, -fno-exceptions builds, or the |
| 85 | // default flag value) fall through to the abort path. |
| 86 | template <typename Alloc, typename OnExhausted> |
| 87 | void* NewImplThrowing(Alloc alloc, OnExhausted on_exhausted) { |
| 88 | void* res = RunNewHandlerChain(alloc); |
| 89 | if (LIKELY(res != nullptr)) |
| 90 | return res; |
| 91 | #if !SANITIZER_WINDOWS && defined(__cpp_exceptions) |
| 92 | if (AllocatorMayReturnNull()) |
| 93 | throw std::bad_alloc(); |
| 94 | #endif // !SANITIZER_WINDOWS && defined(__cpp_exceptions) |
| 95 | InvokeOnExhausted(on_exhausted); |
| 96 | } |
| 97 | |
| 98 | // Nothrow operator new: per [new.delete.single]/4 behaves as-if the |
| 99 | // throwing form is called within a try/catch. When exception support is |
| 100 | // absent (Windows runtimes or -fno-exceptions TUs) we can't write the |
| 101 | // try/catch literally — instead we avoid the throw path entirely, so the |
| 102 | // main body matches NewImplThrowing with "throw std::bad_alloc()" replaced |
| 103 | // by "return nullptr". |
| 104 | template <typename Alloc, typename OnExhausted> |
| 105 | void* NewImplNothrow(Alloc alloc, OnExhausted on_exhausted) noexcept { |
| 106 | #if !SANITIZER_WINDOWS && defined(__cpp_exceptions) |
| 107 | try { |
| 108 | #endif // !SANITIZER_WINDOWS && defined(__cpp_exceptions) |
| 109 | void* res = RunNewHandlerChain(alloc); |
| 110 | if (LIKELY(res != nullptr)) |
| 111 | return res; |
| 112 | if (AllocatorMayReturnNull()) |
| 113 | return nullptr; |
| 114 | InvokeOnExhausted(on_exhausted); |
| 115 | #if !SANITIZER_WINDOWS && defined(__cpp_exceptions) |
| 116 | } catch (...) { |
| 117 | return nullptr; |
| 118 | } |
| 119 | #endif // !SANITIZER_WINDOWS && defined(__cpp_exceptions) |
| 120 | } |
| 121 | |
| 122 | } // namespace __sanitizer |
| 123 | |
| 124 | #endif // SANITIZER_NEW_HANDLER_H |
| 125 | |