[compiler-rt] [sanitizer_common] Add unit tests for operator new failure handlings (PR #226832)

via llvm-commits llvm-commits at lists.llvm.org
Sun Sep 27 13:17:32 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-compiler-rt-sanitizer

Author: Justin T. Gibbs (scsiguy)

<details>
<summary>Changes</summary>

Depends on #<!-- -->225847.

Test `RunNewHandlerChain`, `NewImplThrowing`, `NewImplNothrow` and
`InvokeOnExhausted` with fake allocation and exhaustion callbacks. The
tests cover handler retries, chain exhaustion, exceptions thrown by a
`std::new_handler`, and the `allocator_may_return_null` exhaustion
policy for throwing and nothrow `operator new`. Sanitizers that adopt
the framework then only need to test their own wiring.

The tests build only where the header includes `<new>` and supports
exceptions, which excludes Windows.

Assisted-by: Claude Opus 5.5


---

Patch is 33.12 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/226832.diff


17 Files Affected:

- (modified) compiler-rt/lib/asan/asan_allocator.cpp (+1-5) 
- (modified) compiler-rt/lib/dfsan/dfsan_new_delete.cpp (+11-6) 
- (modified) compiler-rt/lib/hwasan/hwasan_new_delete.cpp (+15-5) 
- (modified) compiler-rt/lib/lsan/lsan_interceptors.cpp (+9-5) 
- (modified) compiler-rt/lib/memprof/memprof_new_delete.cpp (+3) 
- (modified) compiler-rt/lib/msan/msan_new_delete.cpp (+13-8) 
- (modified) compiler-rt/lib/nsan/nsan_new_delete.cpp (+6) 
- (modified) compiler-rt/lib/sanitizer_common/sanitizer_allocator_checks.h (+6) 
- (modified) compiler-rt/lib/sanitizer_common/sanitizer_new_handler.h (+8-6) 
- (modified) compiler-rt/lib/sanitizer_common/sanitizer_new_operators.inc (+7-6) 
- (modified) compiler-rt/lib/sanitizer_common/tests/CMakeLists.txt (+1) 
- (added) compiler-rt/lib/sanitizer_common/tests/sanitizer_new_handler_test.cpp (+202) 
- (modified) compiler-rt/lib/tsan/rtl/tsan_new_delete.cpp (+18-13) 
- (added) compiler-rt/test/dfsan/invalid_aligned_new.cpp (+43) 
- (added) compiler-rt/test/memprof/TestCases/invalid_aligned_new.cpp (+34) 
- (added) compiler-rt/test/nsan/invalid_aligned_new.cpp (+34) 
- (added) compiler-rt/test/sanitizer_common/TestCases/invalid_aligned_new.cpp (+55) 


``````````diff
diff --git a/compiler-rt/lib/asan/asan_allocator.cpp b/compiler-rt/lib/asan/asan_allocator.cpp
index 6af197ab4cb1c..b75a8ea09e34a 100644
--- a/compiler-rt/lib/asan/asan_allocator.cpp
+++ b/compiler-rt/lib/asan/asan_allocator.cpp
@@ -1214,12 +1214,8 @@ void* asan_new(uptr size, BufferedStackTrace* stack, bool array) {
 
 void* asan_new_aligned(uptr size, uptr alignment, BufferedStackTrace* stack,
                        bool array) {
-  if (UNLIKELY(alignment == 0 || !IsPowerOfTwo(alignment))) {
-    errno = errno_EINVAL;
-    if (AllocatorMayReturnNull())
-      return nullptr;
+  if (UNLIKELY(!CheckAlignedNewAlignment(alignment)))
     ReportInvalidAllocationAlignment(alignment, stack);
-  }
   return SetErrnoOnNull(instance.Allocate(size, alignment, stack,
                                           array ? FROM_NEW_BR : FROM_NEW,
                                           /*can_fill=*/true));
diff --git a/compiler-rt/lib/dfsan/dfsan_new_delete.cpp b/compiler-rt/lib/dfsan/dfsan_new_delete.cpp
index 4482e22951040..0031ae9737b0f 100644
--- a/compiler-rt/lib/dfsan/dfsan_new_delete.cpp
+++ b/compiler-rt/lib/dfsan/dfsan_new_delete.cpp
@@ -16,6 +16,7 @@
 #include "dfsan.h"
 #include "interception/interception.h"
 #include "sanitizer_common/sanitizer_allocator.h"
+#include "sanitizer_common/sanitizer_allocator_checks.h"
 #include "sanitizer_common/sanitizer_allocator_report.h"
 
 using namespace __dfsan;
@@ -34,12 +35,16 @@ enum class align_val_t : size_t {};
     ReportOutOfMemory(size, &stack); \
   }                                  \
   return res
-#define OPERATOR_NEW_BODY_ALIGN(nothrow)         \
-  void *res = dfsan_memalign((uptr)align, size); \
-  if (!nothrow && UNLIKELY(!res)) {              \
-    UNINITIALIZED BufferedStackTrace stack;                    \
-    ReportOutOfMemory(size, &stack);             \
-  }                                              \
+#define OPERATOR_NEW_BODY_ALIGN(nothrow)                   \
+  if (UNLIKELY(!CheckAlignedNewAlignment((uptr)align))) {  \
+    UNINITIALIZED BufferedStackTrace stack;                \
+    ReportInvalidAllocationAlignment((uptr)align, &stack); \
+  }                                                        \
+  void* res = dfsan_memalign((uptr)align, size);           \
+  if (!nothrow && UNLIKELY(!res)) {                        \
+    UNINITIALIZED BufferedStackTrace stack;                \
+    ReportOutOfMemory(size, &stack);                       \
+  }                                                        \
   return res;
 
 INTERCEPTOR_ATTRIBUTE
diff --git a/compiler-rt/lib/hwasan/hwasan_new_delete.cpp b/compiler-rt/lib/hwasan/hwasan_new_delete.cpp
index 232eb0eb6da67..f4a32476e3775 100644
--- a/compiler-rt/lib/hwasan/hwasan_new_delete.cpp
+++ b/compiler-rt/lib/hwasan/hwasan_new_delete.cpp
@@ -11,14 +11,15 @@
 // Interceptors for operators new and delete.
 //===----------------------------------------------------------------------===//
 
+#include <stddef.h>
+#include <stdlib.h>
+
 #include "hwasan.h"
 #include "interception/interception.h"
 #include "sanitizer_common/sanitizer_allocator.h"
+#include "sanitizer_common/sanitizer_allocator_checks.h"
 #include "sanitizer_common/sanitizer_allocator_report.h"
 
-#include <stddef.h>
-#include <stdlib.h>
-
 #if HWASAN_REPLACE_OPERATORS_NEW_AND_DELETE
 
 // TODO(alekseys): throw std::bad_alloc instead of dying on OOM.
@@ -40,23 +41,32 @@
 #  define OPERATOR_NEW_BODY_ARRAY_NOTHROW \
     GET_MALLOC_STACK_TRACE;               \
     return hwasan_malloc(size, &stack)
+#  define CHECK_ALIGNED_NEW_ALIGNMENT                                       \
+    do {                                                                    \
+      if (UNLIKELY(!CheckAlignedNewAlignment(static_cast<uptr>(align))))    \
+        ReportInvalidAllocationAlignment(static_cast<uptr>(align), &stack); \
+    } while (0)
 #  define OPERATOR_NEW_BODY_ALIGN                                        \
     GET_MALLOC_STACK_TRACE;                                              \
-    void *res = hwasan_memalign(static_cast<uptr>(align), size, &stack); \
+    CHECK_ALIGNED_NEW_ALIGNMENT;                                         \
+    void* res = hwasan_memalign(static_cast<uptr>(align), size, &stack); \
     if (UNLIKELY(!res))                                                  \
       ReportOutOfMemory(size, &stack);                                   \
     return res
 #  define OPERATOR_NEW_BODY_ALIGN_NOTHROW \
     GET_MALLOC_STACK_TRACE;               \
+    CHECK_ALIGNED_NEW_ALIGNMENT;          \
     return hwasan_memalign(static_cast<uptr>(align), size, &stack)
 #  define OPERATOR_NEW_BODY_ALIGN_ARRAY                                  \
     GET_MALLOC_STACK_TRACE;                                              \
-    void *res = hwasan_memalign(static_cast<uptr>(align), size, &stack); \
+    CHECK_ALIGNED_NEW_ALIGNMENT;                                         \
+    void* res = hwasan_memalign(static_cast<uptr>(align), size, &stack); \
     if (UNLIKELY(!res))                                                  \
       ReportOutOfMemory(size, &stack);                                   \
     return res
 #  define OPERATOR_NEW_BODY_ALIGN_ARRAY_NOTHROW \
     GET_MALLOC_STACK_TRACE;                     \
+    CHECK_ALIGNED_NEW_ALIGNMENT;                \
     return hwasan_memalign(static_cast<uptr>(align), size, &stack)
 
 #  define OPERATOR_DELETE_BODY \
diff --git a/compiler-rt/lib/lsan/lsan_interceptors.cpp b/compiler-rt/lib/lsan/lsan_interceptors.cpp
index 5340c6ffba607..13bc6f22b2060 100644
--- a/compiler-rt/lib/lsan/lsan_interceptors.cpp
+++ b/compiler-rt/lib/lsan/lsan_interceptors.cpp
@@ -13,6 +13,7 @@
 
 #include "interception/interception.h"
 #include "sanitizer_common/sanitizer_allocator.h"
+#include "sanitizer_common/sanitizer_allocator_checks.h"
 #include "sanitizer_common/sanitizer_allocator_dlsym.h"
 #include "sanitizer_common/sanitizer_allocator_report.h"
 #include "sanitizer_common/sanitizer_atomic.h"
@@ -256,11 +257,14 @@ INTERCEPTOR(int, mprobe, void *ptr) {
   void *res = lsan_malloc(size, stack);\
   if (!nothrow && UNLIKELY(!res)) ReportOutOfMemory(size, &stack);\
   return res;
-#define OPERATOR_NEW_BODY_ALIGN(nothrow)\
-  ENSURE_LSAN_INITED;\
-  GET_STACK_TRACE_MALLOC;\
-  void *res = lsan_memalign((uptr)align, size, stack);\
-  if (!nothrow && UNLIKELY(!res)) ReportOutOfMemory(size, &stack);\
+#define OPERATOR_NEW_BODY_ALIGN(nothrow)                   \
+  ENSURE_LSAN_INITED;                                      \
+  GET_STACK_TRACE_MALLOC;                                  \
+  if (UNLIKELY(!CheckAlignedNewAlignment((uptr)align)))    \
+    ReportInvalidAllocationAlignment((uptr)align, &stack); \
+  void* res = lsan_memalign((uptr)align, size, stack);     \
+  if (!nothrow && UNLIKELY(!res))                          \
+    ReportOutOfMemory(size, &stack);                       \
   return res;
 
 #define OPERATOR_DELETE_BODY\
diff --git a/compiler-rt/lib/memprof/memprof_new_delete.cpp b/compiler-rt/lib/memprof/memprof_new_delete.cpp
index cae5de301367a..6def879c959c1 100644
--- a/compiler-rt/lib/memprof/memprof_new_delete.cpp
+++ b/compiler-rt/lib/memprof/memprof_new_delete.cpp
@@ -14,6 +14,7 @@
 #include "memprof_allocator.h"
 #include "memprof_internal.h"
 #include "memprof_stack.h"
+#include "sanitizer_common/sanitizer_allocator_checks.h"
 #include "sanitizer_common/sanitizer_allocator_report.h"
 
 #include "interception/interception.h"
@@ -38,6 +39,8 @@ enum class align_val_t : size_t {};
   return res;
 #define OPERATOR_NEW_BODY_ALIGN(type, nothrow)                                 \
   GET_STACK_TRACE_MALLOC;                                                      \
+  if (UNLIKELY(!CheckAlignedNewAlignment((uptr)align)))                        \
+    ReportInvalidAllocationAlignment((uptr)align, &stack);                     \
   void *res = memprof_memalign((uptr)align, size, &stack, type);               \
   if (!nothrow && UNLIKELY(!res))                                              \
     ReportOutOfMemory(size, &stack);                                           \
diff --git a/compiler-rt/lib/msan/msan_new_delete.cpp b/compiler-rt/lib/msan/msan_new_delete.cpp
index 7daa55474b7da..177e02e8044cc 100644
--- a/compiler-rt/lib/msan/msan_new_delete.cpp
+++ b/compiler-rt/lib/msan/msan_new_delete.cpp
@@ -11,9 +11,10 @@
 // Interceptors for operators new and delete.
 //===----------------------------------------------------------------------===//
 
-#include "msan.h"
 #include "interception/interception.h"
+#include "msan.h"
 #include "sanitizer_common/sanitizer_allocator.h"
+#include "sanitizer_common/sanitizer_allocator_checks.h"
 #include "sanitizer_common/sanitizer_allocator_report.h"
 
 #if MSAN_REPLACE_OPERATORS_NEW_AND_DELETE
@@ -38,13 +39,17 @@ namespace std {
       ReportOutOfMemory(size, &stack);        \
     }                                         \
     return res
-#  define OPERATOR_NEW_BODY_ALIGN(nothrow)                \
-    GET_MALLOC_STACK_TRACE;                               \
-    void *res = msan_memalign((uptr)align, size, &stack); \
-    if (!nothrow && UNLIKELY(!res)) {                     \
-      GET_FATAL_STACK_TRACE_IF_EMPTY(&stack);             \
-      ReportOutOfMemory(size, &stack);                    \
-    }                                                     \
+#  define OPERATOR_NEW_BODY_ALIGN(nothrow)                   \
+    GET_MALLOC_STACK_TRACE;                                  \
+    if (UNLIKELY(!CheckAlignedNewAlignment((uptr)align))) {  \
+      GET_FATAL_STACK_TRACE_IF_EMPTY(&stack);                \
+      ReportInvalidAllocationAlignment((uptr)align, &stack); \
+    }                                                        \
+    void* res = msan_memalign((uptr)align, size, &stack);    \
+    if (!nothrow && UNLIKELY(!res)) {                        \
+      GET_FATAL_STACK_TRACE_IF_EMPTY(&stack);                \
+      ReportOutOfMemory(size, &stack);                       \
+    }                                                        \
     return res;
 
 INTERCEPTOR_ATTRIBUTE
diff --git a/compiler-rt/lib/nsan/nsan_new_delete.cpp b/compiler-rt/lib/nsan/nsan_new_delete.cpp
index f203a583f2c44..0101e19d202c0 100644
--- a/compiler-rt/lib/nsan/nsan_new_delete.cpp
+++ b/compiler-rt/lib/nsan/nsan_new_delete.cpp
@@ -15,6 +15,7 @@
 #include "nsan.h"
 #include "nsan_allocator.h"
 #include "sanitizer_common/sanitizer_allocator.h"
+#include "sanitizer_common/sanitizer_allocator_checks.h"
 #include "sanitizer_common/sanitizer_allocator_report.h"
 
 #include <stddef.h>
@@ -36,6 +37,11 @@ enum class align_val_t : size_t {};
   }                                                                            \
   return res
 #define OPERATOR_NEW_BODY_ALIGN(nothrow)                                       \
+  if (UNLIKELY(!CheckAlignedNewAlignment((uptr)align))) {                      \
+    BufferedStackTrace stack;                                                  \
+    GET_FATAL_STACK_TRACE_IF_EMPTY(&stack);                                    \
+    ReportInvalidAllocationAlignment((uptr)align, &stack);                     \
+  }                                                                            \
   void *res = nsan_memalign((uptr)align, size);                                \
   if (!nothrow && UNLIKELY(!res)) {                                            \
     BufferedStackTrace stack;                                                  \
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_allocator_checks.h b/compiler-rt/lib/sanitizer_common/sanitizer_allocator_checks.h
index 1cc3992c4c9fa..af3f3a278c2a5 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_allocator_checks.h
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_allocator_checks.h
@@ -57,6 +57,12 @@ inline bool CheckPosixMemalignAlignment(uptr alignment) {
          (alignment % sizeof(void *)) == 0;
 }
 
+// Checks the std::align_val_t argument of aligned operator new, verifies that
+// the alignment is a power of two.
+inline bool CheckAlignedNewAlignment(uptr alignment) {
+  return alignment != 0 && IsPowerOfTwo(alignment);
+}
+
 // Returns true if calloc(size, n) call overflows on size*n calculation.
 inline bool CheckForCallocOverflow(uptr size, uptr n) {
   if (!size)
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_new_handler.h b/compiler-rt/lib/sanitizer_common/sanitizer_new_handler.h
index 0b52796836c96..706b38fc7cdd4 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_new_handler.h
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_new_handler.h
@@ -13,10 +13,12 @@
 // throwing / nothrow exhaustion policies that compose with it. Each
 // sanitizer's operator new wrapper supplies two small lambdas:
 //
-//   * Alloc        — invokes the sanitizer's internal allocator, returning
-//                    nullptr on OOM (never aborting on OOM). Other detected
-//                    failure modes (e.g. invalid alignment) should abort with
-//                    a diagnostic.
+//   * Alloc        — invokes the sanitizer's internal allocator. Returns
+//                    nullptr, rather than aborting, only when storage cannot
+//                    be obtained: every nullptr return is treated as a
+//                    storage failure by the std::get_new_handler() loop.
+//                    Other detected failures (e.g. invalid alignment) must
+//                    abort with a diagnostic.
 //
 //   * OnExhausted  — invokes the sanitizer's "abort with diagnostic"
 //                    handler (e.g. asan's ReportOutOfMemory + Die()).
@@ -51,8 +53,8 @@ new_handler get_new_handler() noexcept;
 namespace __sanitizer {
 
 // Runs std::get_new_handler() per [new.delete.single]/3+/4 until the
-// allocation succeeds or the chain is exhausted. Returns the allocated
-// pointer on success, nullptr if the handler chain is exhausted.
+// allocation succeeds (returns the allocated pointer) or the chain is
+// exhausted (returns nullptr).
 //
 // NOTE: Exceptions thrown by Alloc or std::new_handler callbacks escape this
 //       function. Callers that need to convert exceptions to a nullptr return
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_new_operators.inc b/compiler-rt/lib/sanitizer_common/sanitizer_new_operators.inc
index 66dd83f2fddc5..a81426bc17c74 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_new_operators.inc
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_new_operators.inc
@@ -11,9 +11,9 @@
 // All eight variants compose the same per-call setup (a sanitizer-specific
 // stack-trace acquisition) with one of the two chain-handling templates from
 // sanitizer_new_handler.h and two sanitizer-supplied lambdas: an Alloc that
-// invokes the sanitizer's internal allocator (returning nullptr on failure)
-// and an OnExhausted that calls the sanitizer's "abort with diagnostic"
-// handler.
+// invokes the sanitizer's internal allocator (returning nullptr only when
+// storage cannot be obtained) and an OnExhausted that calls the sanitizer's
+// "abort with diagnostic" handler.
 //
 // This file is included after the consuming TU has defined the prerequisite
 // macros listed below. Names reference symbols (e.g. `stack`, `size`) that
@@ -40,9 +40,10 @@
 //   SANITIZER_NEW_ALIGNED(size, align)
 //   SANITIZER_NEW_ARRAY_ALIGNED(size, align)
 //       The sanitizer's four internal alloc helpers. Each must return nullptr
-//       on OOM (so the chain-handling templates can drive the
-//       std::get_new_handler() loop).  Other failure modes (e.g. invalid
-//       alignment) should cause the helper to abort with a diagnostic.
+//       only when storage cannot be obtained: every nullptr return is treated
+//       as a storage failure by the std::get_new_handler() loop. Other failure
+//       modes (e.g. invalid alignment) must cause the helper to abort with a
+//       diagnostic.
 //
 //===----------------------------------------------------------------------===//
 
diff --git a/compiler-rt/lib/sanitizer_common/tests/CMakeLists.txt b/compiler-rt/lib/sanitizer_common/tests/CMakeLists.txt
index bf3e32c3f7786..ce5a1019144aa 100644
--- a/compiler-rt/lib/sanitizer_common/tests/CMakeLists.txt
+++ b/compiler-rt/lib/sanitizer_common/tests/CMakeLists.txt
@@ -34,6 +34,7 @@ set(SANITIZER_UNITTESTS
   sanitizer_mac_test.cpp
   sanitizer_module_uuid_size.cpp
   sanitizer_mutex_test.cpp
+  sanitizer_new_handler_test.cpp
   sanitizer_nolibc_test.cpp
   sanitizer_posix_test.cpp
   sanitizer_printf_test.cpp
diff --git a/compiler-rt/lib/sanitizer_common/tests/sanitizer_new_handler_test.cpp b/compiler-rt/lib/sanitizer_common/tests/sanitizer_new_handler_test.cpp
new file mode 100644
index 0000000000000..39a4f557856a8
--- /dev/null
+++ b/compiler-rt/lib/sanitizer_common/tests/sanitizer_new_handler_test.cpp
@@ -0,0 +1,202 @@
+//===-- sanitizer_new_handler_test.cpp ------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Tests for the operator new failure handling in sanitizer_new_handler.h,
+// driven by fake allocation and exhaustion callbacks.
+//
+//===----------------------------------------------------------------------===//
+#include "sanitizer_common/sanitizer_platform.h"
+
+#if !SANITIZER_WINDOWS && defined(__cpp_exceptions)
+
+#  include <stdio.h>
+#  include <stdlib.h>
+
+#  include <new>
+#  include <stdexcept>
+
+#  include "gtest/gtest.h"
+#  include "sanitizer_common/sanitizer_new_handler.h"
+
+namespace __sanitizer {
+namespace {
+
+int handler_calls;
+// Number of calls after which CountingHandler uninstalls itself.
+int handler_budget;
+
+void CountingHandler() {
+  if (++handler_calls >= handler_budget)
+    std::set_new_handler(nullptr);
+}
+
+void ThrowingHandler() { throw std::runtime_error("handler"); }
+
+// Ends the retry loop by throwing std::bad_alloc while staying installed.
+void BadAllocHandler() {
+  ++handler_calls;
+  throw std::bad_alloc();
+}
+
+void ReportExhausted() {
+  fprintf(stderr, "exhausted after %d handler call(s)\n", handler_calls);
+  abort();
+}
+
+// Exhaustion callbacks must not return; throwing records the failure without
+// killing the test binary.
+struct UnexpectedExhaustion {};
+
+void UnexpectedExhausted() {
+  ADD_FAILURE() << "unexpected exhaustion";
+  throw UnexpectedExhaustion();
+}
+
+class NewHandlerTest : public ::testing::Test {
+ protected:
+  // The new_handler and allocator_may_return_null are process-wide; restore
+  // them so other tests in this binary are unaffected.
+  void SetUp() override {
+    saved_handler_ = std::get_new_handler();
+    saved_may_return_null_ = AllocatorMayReturnNull();
+    handler_calls = 0;
+    handler_budget = 1;
+    attempts_ = 0;
+  }
+
+  void TearDown() override {
+    std::set_new_handler(saved_handler_);
+    SetAllocatorMayReturnNull(saved_may_return_null_);
+  }
+
+  // Returns an allocation callback that fails `failures` times, then succeeds.
+  auto Alloc(int failures) {
+    return [this, failures]() -> void* {
+      return ++attempts_ > failures ? &storage_ : nullptr;
+    };
+  }
+
+  // "Always" means the first ~million attempts. Eventually succeeding makes an
+  // infinite retry loop fail with a clear diagnostic instead of timing out.
+  static constexpr int kAlwaysFail = 1 << 20;
+  int attempts_;
+  char storage_;
+
+ private:
+  std::new_handler saved_handler_;
+  bool saved_may_return_null_;
+};
+
+TEST_F(NewHandlerTest, SuccessSkipsHandler) {
+  std::set_new_handler(CountingHandler);
+  EXPECT_EQ(&storage_, RunNewHandlerChain(Alloc(0)));
+  EXPECT_EQ(1, attempts_);
+  EXPECT_EQ(0, handler_calls);
+}
+
+TEST_F(NewHandlerTest, RetriesAfterEachHandlerCall) {
+  handler_budget = 10;
+  std::set_new_handler(CountingHandler);
+  EXPECT_EQ(&storage_, RunNewHandlerChain(Alloc(3)));
+  EXPECT_EQ(4, attempts_);
+  EXPECT_EQ(3, handler_calls);
+}
+
+TEST_F(NewHandlerTest, NoHandlerReturnsNull) {
+  std::set_new_handler(nullptr);
+  EXPECT_EQ(nullptr, RunNewHandlerChain(Alloc(kAlwaysFail)));
+  EXPECT_EQ(1, attempts_);
+}
+
+TEST_F(NewHandlerTest, ExhaustedChainReturnsNull) {
+  handler_budget = 2;
+  std::set_new_handler(CountingHandler);
+  EXPECT_EQ(nullptr, RunNewHandlerChain(A...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/226832


More information about the llvm-commits mailing list