[compiler-rt] [asan] Adopt sanitizer_common operator-new framework (PR #196388)

David Justo via llvm-commits llvm-commits at lists.llvm.org
Mon Jun 8 11:55:14 PDT 2026


https://github.com/davidmrdavid updated https://github.com/llvm/llvm-project/pull/196388

>From e28517d467c031f3721de7c2d697b8422320bcc0 Mon Sep 17 00:00:00 2001
From: "Justin T. Gibbs" <gibbs at scsiguy.com>
Date: Sat, 30 May 2026 15:51:01 -0700
Subject: [PATCH 1/3] [asan] Enable exceptions for asan_new_delete.cpp
 translation unit
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Preparation for a follow-up change that lets the throwing operator
new wrappers in asan_new_delete.cpp throw std::bad_alloc on OOM. To
support that, asan_new_delete.cpp must be compiled with -fexceptions
and must be able to include <new> (i.e. without -nostdinc++). The
rest of the ASan runtime stays compiled with -fno-exceptions and
-nostdinc++.

NOTE: Comments in CMakeLists.txt are written assuming the follow-on
      changes to use exceptions land, thus avoiding the need to update
      this file again.

Build-system changes:

  * Define ASAN_CXX_CFLAGS as ASAN_CFLAGS minus -fno-exceptions and
    -nostdinc++ plus -fexceptions. Define ASAN_DYNAMIC_CXX_CFLAGS
    likewise relative to ASAN_DYNAMIC_CFLAGS. RTTI stays disabled;
    libstdc++ / libc++abi supply the bad_alloc typeinfo.

  * Static build: switch RTAsan_cxx (which already contains only
    ${ASAN_CXX_SOURCES} = asan_new_delete.cpp) from ASAN_CFLAGS to
    ASAN_CXX_CFLAGS. RTAsan (the C slice) is unchanged.

  * Dynamic build: split the previous single RTAsan_dynamic object
    library into two — RTAsan_dynamic keeps ${ASAN_SOURCES} (no
    change in flags) and a new RTAsan_dynamic_cxx carries
    ${ASAN_CXX_SOURCES} with ASAN_DYNAMIC_CXX_CFLAGS. Both shared
    clang_rt.asan link rules pull in RTAsan_dynamic_cxx alongside
    RTAsan_dynamic so the linked .so retains the same set of object
    files; only the compile flags for asan_new_delete.cpp change.

NFC at runtime — the asan_new_delete.cpp source as of this commit
does not yet use exceptions or include <new>, so the new flags are
inert. The follow-up commit makes use of them.

Assisted by: Claude Opus 4.7
---
 compiler-rt/cmake/config-ix.cmake   |  1 +
 compiler-rt/lib/asan/CMakeLists.txt | 30 ++++++++++++++++++++++++++---
 2 files changed, 28 insertions(+), 3 deletions(-)

diff --git a/compiler-rt/cmake/config-ix.cmake b/compiler-rt/cmake/config-ix.cmake
index d1d89c0a29f8a..2610abcd984fc 100644
--- a/compiler-rt/cmake/config-ix.cmake
+++ b/compiler-rt/cmake/config-ix.cmake
@@ -95,6 +95,7 @@ check_cxx_compiler_flag(-fPIC                COMPILER_RT_HAS_FPIC_FLAG)
 check_cxx_compiler_flag(-fPIE                COMPILER_RT_HAS_FPIE_FLAG)
 check_cxx_compiler_flag(-fno-builtin         COMPILER_RT_HAS_FNO_BUILTIN_FLAG)
 check_cxx_compiler_flag(-fno-exceptions      COMPILER_RT_HAS_FNO_EXCEPTIONS_FLAG)
+check_cxx_compiler_flag(-fexceptions         COMPILER_RT_HAS_FEXCEPTIONS_FLAG)
 check_cxx_compiler_flag(-fomit-frame-pointer COMPILER_RT_HAS_FOMIT_FRAME_POINTER_FLAG)
 check_cxx_compiler_flag(-funwind-tables      COMPILER_RT_HAS_FUNWIND_TABLES_FLAG)
 check_cxx_compiler_flag(-fno-stack-protector COMPILER_RT_HAS_FNO_STACK_PROTECTOR_FLAG)
diff --git a/compiler-rt/lib/asan/CMakeLists.txt b/compiler-rt/lib/asan/CMakeLists.txt
index 6085f18426dff..e2c3996f356f7 100644
--- a/compiler-rt/lib/asan/CMakeLists.txt
+++ b/compiler-rt/lib/asan/CMakeLists.txt
@@ -143,6 +143,19 @@ set(ASAN_DYNAMIC_CFLAGS ${ASAN_CFLAGS})
 append_list_if(COMPILER_RT_HAS_FTLS_MODEL_INITIAL_EXEC
   -ftls-model=initial-exec ASAN_DYNAMIC_CFLAGS)
 
+# asan_new_delete.cpp throws std::bad_alloc, so the translation units that
+# build it need -fexceptions and access to <new> (drop -nostdinc++). Only
+# the C++ slices (RTAsan_cxx in the static build and RTAsan_dynamic_cxx in
+# the dynamic build) opt into these flags — the rest of the runtime keeps
+# -fno-exceptions / -nostdinc++.
+set(ASAN_CXX_CFLAGS ${ASAN_CFLAGS})
+list(REMOVE_ITEM ASAN_CXX_CFLAGS -fno-exceptions -nostdinc++)
+append_list_if(COMPILER_RT_HAS_FEXCEPTIONS_FLAG -fexceptions ASAN_CXX_CFLAGS)
+set(ASAN_DYNAMIC_CXX_CFLAGS ${ASAN_DYNAMIC_CFLAGS})
+list(REMOVE_ITEM ASAN_DYNAMIC_CXX_CFLAGS -fno-exceptions -nostdinc++)
+append_list_if(COMPILER_RT_HAS_FEXCEPTIONS_FLAG -fexceptions
+  ASAN_DYNAMIC_CXX_CFLAGS)
+
 # LLVM turns /OPT:ICF back on when LLVM_ENABLE_PDBs is set
 # we _REALLY_ need to turn it back off for ASAN, because the way
 # asan emulates weak functions from DLLs requires NOICF
@@ -165,11 +178,20 @@ append_list_if(MINGW "${MINGW_LIBRARIES}" ASAN_DYNAMIC_LIBS)
 add_compiler_rt_object_libraries(RTAsan_dynamic
   OS ${SANITIZER_COMMON_SUPPORTED_OS}
   ARCHS ${ASAN_SUPPORTED_ARCH}
-  SOURCES ${ASAN_SOURCES} ${ASAN_CXX_SOURCES}
+  SOURCES ${ASAN_SOURCES}
   ADDITIONAL_HEADERS ${ASAN_HEADERS}
   CFLAGS ${ASAN_DYNAMIC_CFLAGS}
   DEFS ${ASAN_DYNAMIC_DEFINITIONS})
 
+# Separate object library for the dynamic build's C++ slice.
+add_compiler_rt_object_libraries(RTAsan_dynamic_cxx
+  OS ${SANITIZER_COMMON_SUPPORTED_OS}
+  ARCHS ${ASAN_SUPPORTED_ARCH}
+  SOURCES ${ASAN_CXX_SOURCES}
+  ADDITIONAL_HEADERS ${ASAN_HEADERS}
+  CFLAGS ${ASAN_DYNAMIC_CXX_CFLAGS}
+  DEFS ${ASAN_DYNAMIC_DEFINITIONS})
+
 if(NOT APPLE)
   add_compiler_rt_object_libraries(RTAsan
     ARCHS ${ASAN_SUPPORTED_ARCH}
@@ -181,7 +203,7 @@ if(NOT APPLE)
     ARCHS ${ASAN_SUPPORTED_ARCH}
     SOURCES ${ASAN_CXX_SOURCES}
     ADDITIONAL_HEADERS ${ASAN_HEADERS}
-    CFLAGS ${ASAN_CFLAGS}
+    CFLAGS ${ASAN_CXX_CFLAGS}
     DEFS ${ASAN_COMMON_DEFINITIONS})
   add_compiler_rt_object_libraries(RTAsan_static
     ARCHS ${ASAN_SUPPORTED_ARCH}
@@ -219,6 +241,7 @@ if(APPLE)
     OS ${SANITIZER_COMMON_SUPPORTED_OS}
     ARCHS ${ASAN_SUPPORTED_ARCH}
     OBJECT_LIBS RTAsan_dynamic
+                RTAsan_dynamic_cxx
                 RTInterception
                 RTSanitizerCommon
                 RTSanitizerCommonLibc
@@ -319,6 +342,7 @@ else()
       ARCHS ${arch}
       OBJECT_LIBS ${ASAN_COMMON_RUNTIME_OBJECT_LIBS}
               RTAsan_dynamic
+              RTAsan_dynamic_cxx
               # The only purpose of RTAsan_dynamic_version_script_dummy is to
               # carry a dependency of the shared runtime on the version script.
               # Replacing it with a straightforward
@@ -394,7 +418,7 @@ endif()
 
 add_compiler_rt_resource_file(asan_ignorelist asan_ignorelist.txt asan)
 
-# On AIX, static sanitizer libraries are not added to the DSO, so we need to put 
+# On AIX, static sanitizer libraries are not added to the DSO, so we need to put
 # asan.link_with_main_exec.txt and asan_cxx.link_with_main_exec.txt to the build
 # and install dir for use in resolving undefined sanitizer symbols at runtime.
 if ("${CMAKE_SYSTEM_NAME}" MATCHES "AIX")

>From 80355ecc9a1d15fd431a58deb1c03e229cf6def9 Mon Sep 17 00:00:00 2001
From: "Justin T. Gibbs" <gibbs at scsiguy.com>
Date: Sat, 30 May 2026 17:31:24 -0700
Subject: [PATCH 2/3] [sanitizer_common] Add operator new chain-handling
 framework
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Implement the operator new wrapper machinery required by
[new.delete.single]/3+/4 in shared sanitizer_common files so every
sanitizer can reuse it (avoids ~130 lines of duplication).

sanitizer_new_handler.h provides three main templates in namespace
__sanitizer (plus a NORETURN InvokeOnExhausted wrapper used internally):

  * RunNewHandlerChain<Alloc>(alloc)
      Runs std::get_new_handler() in a loop until either the
      allocation succeeds or the chain is exhausted (returns nullptr).

  * NewImplThrowing<Alloc, OnExhausted>(alloc, on_exhausted)
      Throwing operator new: runs the chain; on exhaustion either
      throws std::bad_alloc (AllocatorMayReturnNull()=true opts into
      standards-conformant throw) or invokes on_exhausted (default;
      historical abort-on-OOM).

  * NewImplNothrow<Alloc, OnExhausted>(alloc, on_exhausted) noexcept
      Nothrow operator new: per [new.delete.single]/4 behaves as-if
      the throwing form is called within a try/catch converting any
      exceptions to a nullptr return.

NOTE: Windows builds do not support exceptions, so the throwing
      std::bad_alloc behavior described above is converted to an
      invocation of on_exhausted(); a user new_handler that throws
      on Windows trips the noexcept on NewImplNothrow and aborts via
      std::terminate.

NOTE: TUs using this framework must be built with -fexceptions on
      non-Windows.

NOTE: Instantiating the throwing-form template introduces a
      dependency on `std::bad_alloc` in the consuming TU.
      Adopters must link a C++ ABI library (libstdc++ / libc++abi)
      into the resulting runtime.

sanitizer_new_operators.inc builds the eight standard
OPERATOR_NEW_BODY* macros on top of these templates. A consuming
sanitizer defines six ingredient macros (a stack-trace setup,
a report-OOM invocation, and four alloc helpers) and then includes the
file. The resulting OPERATOR_NEW_BODY / OPERATOR_NEW_BODY_NOTHROW /
OPERATOR_NEW_BODY_ARRAY / ... / OPERATOR_NEW_BODY_ALIGN_ARRAY_NOTHROW
macros can then be used directly as the bodies of the eight operator new
overrides.

NFC. No consumer yet — this is a prerequisite for a follow-up that
restructures compiler-rt/lib/asan/asan_new_delete.cpp to use the
shared framework.

Assisted by: Claude Opus 4.7
---
 .../lib/sanitizer_common/CMakeLists.txt       |   2 +
 .../sanitizer_common/sanitizer_new_handler.h  | 138 ++++++++++++++++++
 .../sanitizer_new_operators.inc               | 118 +++++++++++++++
 3 files changed, 258 insertions(+)
 create mode 100644 compiler-rt/lib/sanitizer_common/sanitizer_new_handler.h
 create mode 100644 compiler-rt/lib/sanitizer_common/sanitizer_new_operators.inc

diff --git a/compiler-rt/lib/sanitizer_common/CMakeLists.txt b/compiler-rt/lib/sanitizer_common/CMakeLists.txt
index 96c23c6d8ab82..07e9cad572b3d 100644
--- a/compiler-rt/lib/sanitizer_common/CMakeLists.txt
+++ b/compiler-rt/lib/sanitizer_common/CMakeLists.txt
@@ -170,6 +170,8 @@ set(SANITIZER_IMPL_HEADERS
   sanitizer_mac.h
   sanitizer_malloc_mac.inc
   sanitizer_mutex.h
+  sanitizer_new_handler.h
+  sanitizer_new_operators.inc
   sanitizer_placement_new.h
   sanitizer_platform.h
   sanitizer_platform_interceptors.h
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_new_handler.h b/compiler-rt/lib/sanitizer_common/sanitizer_new_handler.h
new file mode 100644
index 0000000000000..91845843dff05
--- /dev/null
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_new_handler.h
@@ -0,0 +1,138 @@
+//===-- sanitizer_new_handler.h ---------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is shared between run-time libraries of sanitizers.
+//
+// It provides the operator new chain-handling framework required by
+// [new.delete.single]/3+/4: a std::get_new_handler() loop plus the
+// 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). Non-OOM
+//                    failure modes (e.g. invalid alignment) are the
+//                    caller's responsibility to detect and report before
+//                    invoking the framework — those conditions are UB/API
+//                    misuse, not allocation failures, and should die with
+//                    the caller's specific diagnostic rather than be
+//                    routed through the std::get_new_handler() chain.
+//
+//   * OnExhausted  — invokes the sanitizer's "abort with diagnostic"
+//                    handler (e.g. asan's ReportOutOfMemory + Die()).
+//                    Required to never return (enforced by UNREACHABLE()
+//                    annotations in this common infrastructure).
+//===----------------------------------------------------------------------===//
+#ifndef SANITIZER_NEW_HANDLER_H
+#define SANITIZER_NEW_HANDLER_H
+
+#include "sanitizer_allocator.h"
+#include "sanitizer_internal_defs.h"
+#include "sanitizer_platform.h"
+
+#if !SANITIZER_WINDOWS
+#  include <new>
+#else
+// On Windows the sanitizer runtimes are built without exceptions and can't
+// include <new>. Forward-declare just enough of std for the loop; the real
+// std::get_new_handler is supplied by the C++ runtime the sanitizer DLL
+// already links against (vcruntime / msvcprt / mingw libstdc++) — it
+// doesn't need to come from <new>.
+namespace std {
+struct nothrow_t {};
+enum class align_val_t : __sanitizer::usize {};
+using new_handler = void (*)();
+new_handler get_new_handler() noexcept;
+}  // namespace std
+#endif  // !SANITIZER_WINDOWS
+
+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.
+//
+// NOTE: A handler that throws, propagates out of this function.
+//       Callers that need to convert exceptions to a nullptr return
+//       (e.g. NewImplNothrow below) must wrap the call in try/catch
+//       themselves.
+template <typename Alloc>
+void* RunNewHandlerChain(Alloc alloc) {
+  for (;;) {
+    void* res = alloc();
+    if (LIKELY(res != nullptr))
+      return res;
+    std::new_handler handler = std::get_new_handler();
+    if (!handler)
+      return nullptr;
+    handler();
+  }
+}
+
+// NORETURN wrapper that promotes the OnExhausted callable's
+// morally-noreturn contract into a real noreturn function signature
+// the compiler can see at every call site.
+//
+// NOTE: in C++23 (CWG2511 / P2173R1), [[noreturn]] may be attached directly
+//       to a lambda's call operator, e.g.
+//
+//       [&]() [[noreturn]] { sanitizer_report(); }
+//
+//       which removes the need for this wrapper entirely. Once compiler-rt
+//       requires C++23 (and the supported clang/MSVC versions ship the fix),
+//       this helper can be deleted in favor of marking the OnExhausted
+//       lambdas in sanitizer_new_operators.inc as [[noreturn]] directly.
+template <typename OnExhausted>
+static NORETURN void InvokeOnExhausted(OnExhausted on_exhausted) {
+  on_exhausted();
+  UNREACHABLE("operator new OnExhausted callable returned");
+}
+
+// Throwing operator new: chain, then on exhaustion throw std::bad_alloc
+// (non-Windows + AllocatorMayReturnNull()=true) or invoke on_exhausted
+// (default flag, or Windows for any flag value).
+template <typename Alloc, typename OnExhausted>
+void* NewImplThrowing(Alloc alloc, OnExhausted on_exhausted) {
+  void* res = RunNewHandlerChain(alloc);
+  if (LIKELY(res != nullptr))
+    return res;
+  // Windows builds can't throw exceptions, so always fallthrough
+  // to the abort path.
+#if !SANITIZER_WINDOWS
+  if (AllocatorMayReturnNull())
+    throw std::bad_alloc();
+#endif
+  InvokeOnExhausted(on_exhausted);
+}
+
+// Nothrow operator new: per [new.delete.single]/4 behaves as-if the
+// throwing form is called within a try/catch. The lack of exception
+// support in Windows builds prevents us from doing this literally —
+// we have to avoid throwing std::bad_alloc at all. So the main body here
+// is identical to NewImplThrowing with "throw std::bad_alloc()" replaced
+// by "return nullptr".
+template <typename Alloc, typename OnExhausted>
+void* NewImplNothrow(Alloc alloc, OnExhausted on_exhausted) noexcept {
+#if !SANITIZER_WINDOWS
+  try {
+#endif
+    void* res = RunNewHandlerChain(alloc);
+    if (LIKELY(res != nullptr))
+      return res;
+    if (AllocatorMayReturnNull())
+      return nullptr;
+    InvokeOnExhausted(on_exhausted);
+#if !SANITIZER_WINDOWS
+  } catch (...) {
+    return nullptr;
+  }
+#endif
+}
+
+}  // namespace __sanitizer
+
+#endif  // SANITIZER_NEW_HANDLER_H
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_new_operators.inc b/compiler-rt/lib/sanitizer_common/sanitizer_new_operators.inc
new file mode 100644
index 0000000000000..50ecdac45be5d
--- /dev/null
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_new_operators.inc
@@ -0,0 +1,118 @@
+//===-- sanitizer_new_operators.inc -----------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Shared OPERATOR_NEW_BODY* macros for sanitizer operator new overrides.
+//
+// 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.
+//
+// This file is included after the consuming TU has defined the ingredient
+// macros listed below. Names reference symbols (e.g. `stack`, `size`) that
+// exist only in the macro's expansion context, so this is an .inc, not a
+// freestanding header.
+//
+// The consuming TU must define before including:
+//
+//   SANITIZER_NEW_STACK_TRACE
+//       A statement that, when expanded inside an operator new body, puts a
+//       BufferedStackTrace named `stack` in scope. e.g. for asan:
+//         #define SANITIZER_NEW_STACK_TRACE GET_STACK_TRACE_MALLOC
+//
+//   SANITIZER_NEW_REPORT_OOM(size)
+//       The sanitizer's noreturn diagnostic path for chain-exhausted OOM.
+//       Must call a function that does not return (the framework will run
+//       UNREACHABLE() and abort if the macro's expansion ever returns).
+//       e.g. for asan:
+//         #define SANITIZER_NEW_REPORT_OOM(size) ReportOutOfMemory(size,
+//         &stack)
+//
+//   SANITIZER_NEW(size)
+//   SANITIZER_NEW_ARRAY(size)
+//   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). Non-OOM failure modes (e.g. invalid
+//       alignment) are UB/API misuse and should die with the helper's own
+//       diagnostic rather than be routed through the framework.
+//
+// After the include, the eight OPERATOR_NEW_BODY* macros below are defined
+// and may be used as the bodies of the eight operator new overrides.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef SANITIZER_NEW_STACK_TRACE
+#  error "SANITIZER_NEW_STACK_TRACE must be defined before including"
+#endif
+#ifndef SANITIZER_NEW_REPORT_OOM
+#  error "SANITIZER_NEW_REPORT_OOM must be defined before including"
+#endif
+#ifndef SANITIZER_NEW
+#  error "SANITIZER_NEW must be defined before including"
+#endif
+#ifndef SANITIZER_NEW_ARRAY
+#  error "SANITIZER_NEW_ARRAY must be defined before including"
+#endif
+#ifndef SANITIZER_NEW_ALIGNED
+#  error "SANITIZER_NEW_ALIGNED must be defined before including"
+#endif
+#ifndef SANITIZER_NEW_ARRAY_ALIGNED
+#  error "SANITIZER_NEW_ARRAY_ALIGNED must be defined before including"
+#endif
+
+#define OPERATOR_NEW_BODY                    \
+  SANITIZER_NEW_STACK_TRACE;                 \
+  return __sanitizer::NewImplThrowing(       \
+      [&]() { return SANITIZER_NEW(size); }, \
+      [&]() { SANITIZER_NEW_REPORT_OOM(size); })
+
+#define OPERATOR_NEW_BODY_NOTHROW            \
+  SANITIZER_NEW_STACK_TRACE;                 \
+  return __sanitizer::NewImplNothrow(        \
+      [&]() { return SANITIZER_NEW(size); }, \
+      [&]() { SANITIZER_NEW_REPORT_OOM(size); })
+
+#define OPERATOR_NEW_BODY_ARRAY                    \
+  SANITIZER_NEW_STACK_TRACE;                       \
+  return __sanitizer::NewImplThrowing(             \
+      [&]() { return SANITIZER_NEW_ARRAY(size); }, \
+      [&]() { SANITIZER_NEW_REPORT_OOM(size); })
+
+#define OPERATOR_NEW_BODY_ARRAY_NOTHROW            \
+  SANITIZER_NEW_STACK_TRACE;                       \
+  return __sanitizer::NewImplNothrow(              \
+      [&]() { return SANITIZER_NEW_ARRAY(size); }, \
+      [&]() { SANITIZER_NEW_REPORT_OOM(size); })
+
+#define OPERATOR_NEW_BODY_ALIGN                             \
+  SANITIZER_NEW_STACK_TRACE;                                \
+  return __sanitizer::NewImplThrowing(                      \
+      [&]() { return SANITIZER_NEW_ALIGNED(size, align); }, \
+      [&]() { SANITIZER_NEW_REPORT_OOM(size); })
+
+#define OPERATOR_NEW_BODY_ALIGN_NOTHROW                     \
+  SANITIZER_NEW_STACK_TRACE;                                \
+  return __sanitizer::NewImplNothrow(                       \
+      [&]() { return SANITIZER_NEW_ALIGNED(size, align); }, \
+      [&]() { SANITIZER_NEW_REPORT_OOM(size); })
+
+#define OPERATOR_NEW_BODY_ALIGN_ARRAY                             \
+  SANITIZER_NEW_STACK_TRACE;                                      \
+  return __sanitizer::NewImplThrowing(                            \
+      [&]() { return SANITIZER_NEW_ARRAY_ALIGNED(size, align); }, \
+      [&]() { SANITIZER_NEW_REPORT_OOM(size); })
+
+#define OPERATOR_NEW_BODY_ALIGN_ARRAY_NOTHROW                     \
+  SANITIZER_NEW_STACK_TRACE;                                      \
+  return __sanitizer::NewImplNothrow(                             \
+      [&]() { return SANITIZER_NEW_ARRAY_ALIGNED(size, align); }, \
+      [&]() { SANITIZER_NEW_REPORT_OOM(size); })

>From d771d36f98f847f2dc1314efb9327b908784bf51 Mon Sep 17 00:00:00 2001
From: "Justin T. Gibbs" <gibbs at scsiguy.com>
Date: Sat, 30 May 2026 16:21:52 -0700
Subject: [PATCH 3/3] [asan] Adopt sanitizer_common operator-new framework
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Adopt the operator new chain-handling framework to give asan's eight operator
new overrides standards-conformant behavior on allocation failure.

The new behavior for handling OOM is opt-in via allocator_may_return_null=1.
With the flag set, throwing operator new throws std::bad_alloc on OOM and
nothrow operator new returns nullptr. Under the default flag value
(=0) both forms continue to ReportOutOfMemory + Die() — preserving
historical behavior. In all cases, the std::get_new_handler() chain
is executed as required by ([new.delete.single]/3+/4).

NOTE: On Windows the throwing form always aborts; std::bad_alloc
      requires exception support, which the framework / asan runtime
      lack there.

NOTE: The framework's typeinfo for std::bad_alloc dependency is
      satisfied for the asan dynamic .so via RTAsan_dynamic_cxx.
      Users who statically link clang_rt.asan_cxx-<arch>.a
      into their own binaries must arrange to link a C++ ABI library
      themselves.

NOTE: Throwing behavior requires user code built with -fexceptions.
      -fno-exceptions user code that triggers operator new failure
      under allocator_may_return_null=1 will reach std::terminate
      when the thrown std::bad_alloc propagates to a frame without
      a catch handler (asan's signal handler then reports the abort).

This closes the long-standing TODO in asan_new_delete.cpp ("throw
std::bad_alloc instead of dying on OOM") and aligns ASan with
libstdc++ / libc++ / tcmalloc. See https://github.com/google/sanitizers/issues/295.

Implementation (asan-specific; framework mechanics live in the prereq):

  * asan_new / asan_new_array / asan_new_aligned / asan_new_array_aligned
    in asan_allocator.cpp pass may_return_null=true to AllocateImpl()
    so the Alloc lambdas seen by the framework's NewImpl* templates
    always observe nullptr on failure rather than aborting. This
    includes asan_new_aligned's alignment-validation path — bad
    alignment now returns nullptr and lets the new_handler chain
    decide rather than aborting unconditionally.

  * Local definition of the OPERATOR_NEW_BODY* macros is replaced
    by definition of the six SANITIZER_NEW_* ingredient macros required
    by sanitizer_common/sanitizer_new_operators.inc. The nothrow operator
    new / delete overloads gain NOEXCEPT to match the standard
    library's exception specifications.

  * sanitizer_common/sanitizer_flags.inc: update the
    allocator_may_return_null docstring to document the new throw
    behavior and the Windows limitation.

  * Diagnostic change for the operator-new path: because asan_new
    now forces may_return_null=true on AllocateImpl(), the in-place
    "allocation-size-too-big" diagnostic that previously fired for
    oversize requests inside Allocate is replaced by a "WARNING:
    AddressSanitizer failed to allocate" line (from Allocate's
    may_return_null=true branch) followed by the chain-exhausted
    "out-of-memory" SUMMARY (or std::bad_alloc throw on flag=1).
    Other failure paths (malloc / calloc / realloc / aligned_alloc /
    posix_memalign) continue to emit "allocation-size-too-big" as
    before.

Test Plan:
New tests under compiler-rt/test/asan/TestCases/ covering the eight
OPERATOR_NEW_BODY* macro paths plus both flag values:

  Opt-in (allocator_may_return_null=1):
    * throw_bad_alloc_oversize.cpp                  — array throws bad_alloc
    * throw_bad_alloc_aligned.cpp                   — aligned array throws bad_alloc
    * throw_bad_alloc_single.cpp                    — single-object throws bad_alloc
    * throw_bad_alloc_aligned_single.cpp            — aligned single-object throws bad_alloc
    * nothrow_new_returns_null.cpp                  — array nothrow returns null
    * nothrow_new_single_returns_null.cpp           — single-object nothrow returns null
    * nothrow_new_aligned_single_returns_null.cpp   — aligned single-object nothrow returns null
    * nothrow_new_aligned_array_returns_null.cpp    — aligned array nothrow returns null
    * new_handler_invocation.cpp                    — handler runs, can break loop
    * new_handler_throws_other.cpp                  — non-bad_alloc handler exception propagates from throwing new
    * nothrow_new_handler_throws_other.cpp          — non-bad_alloc handler exception is swallowed by nothrow new (per /4 as-if try/catch)

  Default flag (allocator_may_return_null=0) — abort path:
    * throw_bad_alloc_default_aborts.cpp            — throwing new aborts with vanilla diagnostic
    * nothrow_new_default_aborts.cpp                — nothrow new aborts with vanilla diagnostic

The four nothrow tests run on Windows too (the Windows nothrow path is
now standards-conformant); the throwing tests stay UNSUPPORTED on
Windows since they require std::bad_alloc to be catchable.

Existing malloc-size-too-big.cpp continues to assert that malloc OOM
under allocator_may_return_null=0 aborts.

Updated existing sanitizer_common tests for the new-operator behavior:
  * compiler-rt/test/sanitizer_common/TestCases/allocator_returns_null.cpp
  * compiler-rt/test/sanitizer_common/TestCases/max_allocation_size.cpp
  * compiler-rt/test/sanitizer_common/TestCases/Linux/allocator_returns_null_std.cpp

For the flag=0 cells (CHECK-nCRASH, CHECK-nnCRASH, the std test), the
expected SUMMARY pattern is loosened to (allocation-size-too-big|out-of-memory)
— other sanitizers still emit "allocation-size-too-big".

For the flag=1 + throwing-new case, the test now wraps the operator
new call in a try/catch and converts std::bad_alloc to nullptr to
preserve the existing test invariants (errno=ENOMEM, x=nullptr ->
CHECK-NULL). This validates that the bad_alloc throw is catchable
from user code (the whole point of the new contract).

Assisted-by: Claude Opus 4.7
---
 compiler-rt/lib/asan/asan_allocator.cpp       | 15 ++--
 compiler-rt/lib/asan/asan_new_delete.cpp      | 77 ++++++-------------
 .../lib/sanitizer_common/sanitizer_flags.inc  | 10 ++-
 .../asan/TestCases/new_handler_invocation.cpp | 45 +++++++++++
 .../TestCases/new_handler_throws_other.cpp    | 36 +++++++++
 ...nothrow_new_aligned_array_returns_null.cpp | 27 +++++++
 ...othrow_new_aligned_single_returns_null.cpp | 26 +++++++
 .../TestCases/nothrow_new_default_aborts.cpp  | 33 ++++++++
 .../nothrow_new_handler_throws_other.cpp      | 32 ++++++++
 .../TestCases/nothrow_new_returns_null.cpp    | 26 +++++++
 .../nothrow_new_single_returns_null.cpp       | 25 ++++++
 .../TestCases/throw_bad_alloc_aligned.cpp     | 36 +++++++++
 .../throw_bad_alloc_aligned_single.cpp        | 34 ++++++++
 .../throw_bad_alloc_default_aborts.cpp        | 31 ++++++++
 .../TestCases/throw_bad_alloc_oversize.cpp    | 35 +++++++++
 .../asan/TestCases/throw_bad_alloc_single.cpp | 35 +++++++++
 .../Linux/allocator_returns_null_std.cpp      |  6 +-
 .../TestCases/allocator_returns_null.cpp      | 32 ++++++--
 .../TestCases/max_allocation_size.cpp         | 35 +++++++--
 19 files changed, 520 insertions(+), 76 deletions(-)
 create mode 100644 compiler-rt/test/asan/TestCases/new_handler_invocation.cpp
 create mode 100644 compiler-rt/test/asan/TestCases/new_handler_throws_other.cpp
 create mode 100644 compiler-rt/test/asan/TestCases/nothrow_new_aligned_array_returns_null.cpp
 create mode 100644 compiler-rt/test/asan/TestCases/nothrow_new_aligned_single_returns_null.cpp
 create mode 100644 compiler-rt/test/asan/TestCases/nothrow_new_default_aborts.cpp
 create mode 100644 compiler-rt/test/asan/TestCases/nothrow_new_handler_throws_other.cpp
 create mode 100644 compiler-rt/test/asan/TestCases/nothrow_new_returns_null.cpp
 create mode 100644 compiler-rt/test/asan/TestCases/nothrow_new_single_returns_null.cpp
 create mode 100644 compiler-rt/test/asan/TestCases/throw_bad_alloc_aligned.cpp
 create mode 100644 compiler-rt/test/asan/TestCases/throw_bad_alloc_aligned_single.cpp
 create mode 100644 compiler-rt/test/asan/TestCases/throw_bad_alloc_default_aborts.cpp
 create mode 100644 compiler-rt/test/asan/TestCases/throw_bad_alloc_oversize.cpp
 create mode 100644 compiler-rt/test/asan/TestCases/throw_bad_alloc_single.cpp

diff --git a/compiler-rt/lib/asan/asan_allocator.cpp b/compiler-rt/lib/asan/asan_allocator.cpp
index 6af197ab4cb1c..f4b2c969bcc97 100644
--- a/compiler-rt/lib/asan/asan_allocator.cpp
+++ b/compiler-rt/lib/asan/asan_allocator.cpp
@@ -1206,10 +1206,13 @@ uptr asan_malloc_usable_size(const void* ptr, uptr pc, uptr bp) {
 
 namespace {
 
+// Force may_return_null=true so the Alloc lambdas in asan_new_delete.cpp
+// satisfy the operator-new framework's contract (return nullptr on OOM
+// failure rather than abort).
 void* asan_new(uptr size, BufferedStackTrace* stack, bool array) {
-  return SetErrnoOnNull(instance.Allocate(size, /*alignment=*/0, stack,
-                                          array ? FROM_NEW_BR : FROM_NEW,
-                                          /*can_fill=*/true));
+  return SetErrnoOnNull(instance.AllocateImpl(
+      size, /*alignment=*/0, stack, array ? FROM_NEW_BR : FROM_NEW,
+      /*can_fill=*/true, /*may_return_null=*/true));
 }
 
 void* asan_new_aligned(uptr size, uptr alignment, BufferedStackTrace* stack,
@@ -1220,9 +1223,9 @@ void* asan_new_aligned(uptr size, uptr alignment, BufferedStackTrace* stack,
       return nullptr;
     ReportInvalidAllocationAlignment(alignment, stack);
   }
-  return SetErrnoOnNull(instance.Allocate(size, alignment, stack,
-                                          array ? FROM_NEW_BR : FROM_NEW,
-                                          /*can_fill=*/true));
+  return SetErrnoOnNull(instance.AllocateImpl(
+      size, alignment, stack, array ? FROM_NEW_BR : FROM_NEW,
+      /*can_fill=*/true, /*may_return_null=*/true));
 }
 
 void asan_delete(void* ptr, BufferedStackTrace* stack, bool array) {
diff --git a/compiler-rt/lib/asan/asan_new_delete.cpp b/compiler-rt/lib/asan/asan_new_delete.cpp
index 04f8082de3148..1f5bcc27a0c77 100644
--- a/compiler-rt/lib/asan/asan_new_delete.cpp
+++ b/compiler-rt/lib/asan/asan_new_delete.cpp
@@ -18,6 +18,7 @@
 #include "asan_report.h"
 #include "asan_stack.h"
 #include "interception/interception.h"
+#include "sanitizer_common/sanitizer_new_handler.h"
 
 // C++ operators can't have dllexport attributes on Windows. We export them
 // anyway by passing extra -export flags to the linker, which is exactly that
@@ -51,51 +52,17 @@ using namespace __asan;
 // This code has issues on OSX.
 // See https://github.com/google/sanitizers/issues/131.
 
-// Fake std::nothrow_t and std::align_val_t to avoid including <new>.
-namespace std {
-struct nothrow_t {};
-enum class align_val_t : size_t {};
-}  // namespace std
-
-// TODO(alekseyshl): throw std::bad_alloc instead of dying on OOM.
-// For local pool allocation, align to SHADOW_GRANULARITY to match asan
-// allocator behavior.
-#define OPERATOR_NEW_BODY             \
-  GET_STACK_TRACE_MALLOC;             \
-  void* res = asan_new(size, &stack); \
-  if (UNLIKELY(!res))                 \
-    ReportOutOfMemory(size, &stack);  \
-  return res
-#define OPERATOR_NEW_BODY_NOTHROW \
-  GET_STACK_TRACE_MALLOC;         \
-  return asan_new(size, &stack)
-#define OPERATOR_NEW_BODY_ARRAY             \
-  GET_STACK_TRACE_MALLOC;                   \
-  void* res = asan_new_array(size, &stack); \
-  if (UNLIKELY(!res))                       \
-    ReportOutOfMemory(size, &stack);        \
-  return res
-#define OPERATOR_NEW_BODY_ARRAY_NOTHROW \
-  GET_STACK_TRACE_MALLOC;               \
-  return asan_new_array(size, &stack)
-#define OPERATOR_NEW_BODY_ALIGN                                         \
-  GET_STACK_TRACE_MALLOC;                                               \
-  void* res = asan_new_aligned(size, static_cast<uptr>(align), &stack); \
-  if (UNLIKELY(!res))                                                   \
-    ReportOutOfMemory(size, &stack);                                    \
-  return res
-#define OPERATOR_NEW_BODY_ALIGN_NOTHROW \
-  GET_STACK_TRACE_MALLOC;               \
-  return asan_new_aligned(size, static_cast<uptr>(align), &stack)
-#define OPERATOR_NEW_BODY_ALIGN_ARRAY                                         \
-  GET_STACK_TRACE_MALLOC;                                                     \
-  void* res = asan_new_array_aligned(size, static_cast<uptr>(align), &stack); \
-  if (UNLIKELY(!res))                                                         \
-    ReportOutOfMemory(size, &stack);                                          \
-  return res
-#define OPERATOR_NEW_BODY_ALIGN_ARRAY_NOTHROW \
-  GET_STACK_TRACE_MALLOC;                     \
-  return asan_new_array_aligned(size, static_cast<uptr>(align), &stack)
+// SANITIZER_NEW_* macros below are inputs to the operator-new framework;
+// OPERATOR_NEW_BODY* macros are provided in return.
+#define SANITIZER_NEW_STACK_TRACE GET_STACK_TRACE_MALLOC
+#define SANITIZER_NEW_REPORT_OOM(size) ReportOutOfMemory(size, &stack)
+#define SANITIZER_NEW(size) asan_new(size, &stack)
+#define SANITIZER_NEW_ARRAY(size) asan_new_array(size, &stack)
+#define SANITIZER_NEW_ALIGNED(size, align) \
+  asan_new_aligned(size, static_cast<uptr>(align), &stack)
+#define SANITIZER_NEW_ARRAY_ALIGNED(size, align) \
+  asan_new_array_aligned(size, static_cast<uptr>(align), &stack)
+#include "sanitizer_common/sanitizer_new_operators.inc"
 
 // On OS X it's not enough to just provide our own 'operator new' and
 // 'operator delete' implementations, because they're going to be in the
@@ -110,11 +77,11 @@ void* operator new(size_t size) { OPERATOR_NEW_BODY; }
 CXX_OPERATOR_ATTRIBUTE
 void* operator new[](size_t size) { OPERATOR_NEW_BODY_ARRAY; }
 CXX_OPERATOR_ATTRIBUTE
-void* operator new(size_t size, std::nothrow_t const&) {
+void* operator new(size_t size, std::nothrow_t const&) NOEXCEPT {
   OPERATOR_NEW_BODY_NOTHROW;
 }
 CXX_OPERATOR_ATTRIBUTE
-void* operator new[](size_t size, std::nothrow_t const&) {
+void* operator new[](size_t size, std::nothrow_t const&) NOEXCEPT {
   OPERATOR_NEW_BODY_ARRAY_NOTHROW;
 }
 CXX_OPERATOR_ATTRIBUTE
@@ -126,12 +93,13 @@ void* operator new[](size_t size, std::align_val_t align) {
   OPERATOR_NEW_BODY_ALIGN_ARRAY;
 }
 CXX_OPERATOR_ATTRIBUTE
-void* operator new(size_t size, std::align_val_t align, std::nothrow_t const&) {
+void* operator new(size_t size, std::align_val_t align,
+                   std::nothrow_t const&) NOEXCEPT {
   OPERATOR_NEW_BODY_ALIGN_NOTHROW;
 }
 CXX_OPERATOR_ATTRIBUTE
 void* operator new[](size_t size, std::align_val_t align,
-                     std::nothrow_t const&) {
+                     std::nothrow_t const&) NOEXCEPT {
   OPERATOR_NEW_BODY_ALIGN_ARRAY_NOTHROW;
 }
 
@@ -177,9 +145,11 @@ void operator delete(void* ptr) NOEXCEPT { OPERATOR_DELETE_BODY; }
 CXX_OPERATOR_ATTRIBUTE
 void operator delete[](void* ptr) NOEXCEPT { OPERATOR_DELETE_BODY_ARRAY; }
 CXX_OPERATOR_ATTRIBUTE
-void operator delete(void* ptr, std::nothrow_t const&) { OPERATOR_DELETE_BODY; }
+void operator delete(void* ptr, std::nothrow_t const&) NOEXCEPT {
+  OPERATOR_DELETE_BODY;
+}
 CXX_OPERATOR_ATTRIBUTE
-void operator delete[](void* ptr, std::nothrow_t const&) {
+void operator delete[](void* ptr, std::nothrow_t const&) NOEXCEPT {
   OPERATOR_DELETE_BODY_ARRAY;
 }
 CXX_OPERATOR_ATTRIBUTE
@@ -199,12 +169,13 @@ void operator delete[](void* ptr, std::align_val_t align) NOEXCEPT {
   OPERATOR_DELETE_BODY_ALIGN_ARRAY;
 }
 CXX_OPERATOR_ATTRIBUTE
-void operator delete(void* ptr, std::align_val_t align, std::nothrow_t const&) {
+void operator delete(void* ptr, std::align_val_t align,
+                     std::nothrow_t const&) NOEXCEPT {
   OPERATOR_DELETE_BODY_ALIGN;
 }
 CXX_OPERATOR_ATTRIBUTE
 void operator delete[](void* ptr, std::align_val_t align,
-                       std::nothrow_t const&) {
+                       std::nothrow_t const&) NOEXCEPT {
   OPERATOR_DELETE_BODY_ALIGN_ARRAY;
 }
 CXX_OPERATOR_ATTRIBUTE
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_flags.inc b/compiler-rt/lib/sanitizer_common/sanitizer_flags.inc
index 5f449907f6011..e7350ef615c27 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_flags.inc
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_flags.inc
@@ -83,8 +83,14 @@ COMMON_FLAG(
     "detect_leaks=false, or if __lsan_do_leak_check() is called before the "
     "handler has a chance to run.")
 COMMON_FLAG(bool, allocator_may_return_null, false,
-            "If false, the allocator will crash instead of returning 0 on "
-            "out-of-memory.")
+            "Controls allocator behavior on out-of-memory. "
+            "If 'false' (default), out-of-memory events force an abort. "
+            "If 'true', allocators conform to the C/C++ standard and either "
+            "return 'null' or throw the C++ std::bad_alloc exception as "
+            "expected. "
+            "NOTE: On Windows sanitizer runtimes are built without exceptions "
+            "so throwing allocator forms always abort, regardless of the "
+            "value of this flag.")
 COMMON_FLAG(bool, print_summary, true,
             "If false, disable printing error summaries in addition to error "
             "reports.")
diff --git a/compiler-rt/test/asan/TestCases/new_handler_invocation.cpp b/compiler-rt/test/asan/TestCases/new_handler_invocation.cpp
new file mode 100644
index 0000000000000..cd526eb764ccd
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/new_handler_invocation.cpp
@@ -0,0 +1,45 @@
+// Throwing operator new must invoke std::new_handler before throwing
+// std::bad_alloc, per [new.delete.single]/3 (and /4 for the nothrow form).
+// Opt-in via allocator_may_return_null=1: the handler chain runs regardless
+// of the flag, but the chain-exhausted action only becomes "throw bad_alloc"
+// when the flag is set; otherwise it's ReportOutOfMemory + Die().
+
+// RUN: %clangxx_asan -O0 %s -o %t
+// RUN: %env_asan_opts=allocator_may_return_null=1 %run %t 2>&1 | FileCheck %s
+
+// UNSUPPORTED: target={{.*windows.*}}
+// REQUIRES: stable-runtime
+
+#include <cstdio>
+#include <new>
+
+static const size_t kHugeSize =
+#if __LP64__ || defined(_WIN64)
+    (1ULL << 40) + 1;
+#else
+    (3UL << 30) + 1;
+#endif
+
+static int handler_calls = 0;
+
+static void my_handler() {
+  ++handler_calls;
+  fprintf(stderr, "handler call %d\n", handler_calls);
+  // Break the loop. A real handler would free memory and return; this
+  // allocation is unrecoverable so we throw to terminate.
+  throw std::bad_alloc();
+}
+
+int main() {
+  std::set_new_handler(my_handler);
+  try {
+    char *p = new char[kHugeSize];
+    fprintf(stderr, "FAIL: allocation unexpectedly returned %p\n", p);
+  } catch (const std::bad_alloc &) {
+    fprintf(stderr, "caught bad_alloc after %d handler call(s)\n",
+            handler_calls);
+  }
+  // CHECK: handler call 1
+  // CHECK: caught bad_alloc after 1 handler call(s)
+  return 0;
+}
diff --git a/compiler-rt/test/asan/TestCases/new_handler_throws_other.cpp b/compiler-rt/test/asan/TestCases/new_handler_throws_other.cpp
new file mode 100644
index 0000000000000..8d64959228dd7
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/new_handler_throws_other.cpp
@@ -0,0 +1,36 @@
+// If the registered std::new_handler throws an exception other than
+// std::bad_alloc, that exception must propagate out of operator new
+// unmodified. Opt-in via allocator_may_return_null=1.
+
+// RUN: %clangxx_asan -O0 %s -o %t
+// RUN: %env_asan_opts=allocator_may_return_null=1 %run %t 2>&1 | FileCheck %s
+
+// UNSUPPORTED: target={{.*windows.*}}
+// REQUIRES: stable-runtime
+
+#include <cstdio>
+#include <new>
+#include <stdexcept>
+
+static const size_t kHugeSize =
+#if __LP64__ || defined(_WIN64)
+    (1ULL << 40) + 1;
+#else
+    (3UL << 30) + 1;
+#endif
+
+static void my_handler() { throw std::runtime_error("oom-policy"); }
+
+int main() {
+  std::set_new_handler(my_handler);
+  try {
+    char *p = new char[kHugeSize];
+    fprintf(stderr, "FAIL: allocation unexpectedly returned %p\n", p);
+  } catch (const std::bad_alloc &) {
+    fprintf(stderr, "FAIL: caught bad_alloc instead of runtime_error\n");
+  } catch (const std::runtime_error &e) {
+    fprintf(stderr, "caught runtime_error: %s\n", e.what());
+  }
+  // CHECK: caught runtime_error: oom-policy
+  return 0;
+}
diff --git a/compiler-rt/test/asan/TestCases/nothrow_new_aligned_array_returns_null.cpp b/compiler-rt/test/asan/TestCases/nothrow_new_aligned_array_returns_null.cpp
new file mode 100644
index 0000000000000..1430c24f5cf57
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/nothrow_new_aligned_array_returns_null.cpp
@@ -0,0 +1,27 @@
+// Aligned array nothrow operator new must return nullptr on allocation
+// failure (OPERATOR_NEW_BODY_ALIGN_ARRAY_NOTHROW). Opt-in via
+// allocator_may_return_null=1.
+
+// RUN: %clangxx_asan -O0 -std=c++17 %s -o %t
+// RUN: %env_asan_opts=allocator_may_return_null=1 %run %t 2>&1 | FileCheck %s
+
+// REQUIRES: stable-runtime
+
+#include <cstdio>
+#include <new>
+
+struct alignas(64) HugeAligned {
+#if __LP64__ || defined(_WIN64)
+  char data[(1ULL << 40) + 1];
+#else
+  char data[(3UL << 30) + 1];
+#endif
+};
+
+int main() {
+  HugeAligned *p = new (std::nothrow) HugeAligned[1];
+  fprintf(stderr, "nothrow aligned array returned %s\n",
+          p ? "non-null" : "null");
+  // CHECK: nothrow aligned array returned null
+  return 0;
+}
diff --git a/compiler-rt/test/asan/TestCases/nothrow_new_aligned_single_returns_null.cpp b/compiler-rt/test/asan/TestCases/nothrow_new_aligned_single_returns_null.cpp
new file mode 100644
index 0000000000000..c0096f54bc255
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/nothrow_new_aligned_single_returns_null.cpp
@@ -0,0 +1,26 @@
+// Aligned single-object nothrow operator new must return nullptr on
+// allocation failure (OPERATOR_NEW_BODY_ALIGN_NOTHROW). Opt-in via
+// allocator_may_return_null=1.
+
+// RUN: %clangxx_asan -O0 -std=c++17 %s -o %t
+// RUN: %env_asan_opts=allocator_may_return_null=1 %run %t 2>&1 | FileCheck %s
+
+// REQUIRES: stable-runtime
+
+#include <cstdio>
+#include <new>
+
+struct alignas(64) HugeAligned {
+#if __LP64__ || defined(_WIN64)
+  char data[(1ULL << 40) + 1];
+#else
+  char data[(3UL << 30) + 1];
+#endif
+};
+
+int main() {
+  HugeAligned *p = new (std::nothrow) HugeAligned;
+  fprintf(stderr, "nothrow aligned returned %s\n", p ? "non-null" : "null");
+  // CHECK: nothrow aligned returned null
+  return 0;
+}
diff --git a/compiler-rt/test/asan/TestCases/nothrow_new_default_aborts.cpp b/compiler-rt/test/asan/TestCases/nothrow_new_default_aborts.cpp
new file mode 100644
index 0000000000000..f35b8c7bea92e
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/nothrow_new_default_aborts.cpp
@@ -0,0 +1,33 @@
+// With allocator_may_return_null=false (default), nothrow operator new
+// aborts on OOM. The handler chain runs (per [new.delete.single]/4); on
+// chain exhaustion the runtime emits the asan diagnostic and Die()s rather
+// than returning nullptr.
+
+// RUN: %clangxx_asan -O0 %s -o %t
+// RUN: not %run %t 2>&1 | FileCheck %s
+
+// REQUIRES: stable-runtime
+
+#include <cstdio>
+#include <new>
+
+static const size_t kHugeSize =
+#if __LP64__ || defined(_WIN64)
+    (1ULL << 40) + 1;
+#else
+    (3UL << 30) + 1;
+#endif
+
+int main() {
+  // No new_handler installed -> chain exhausts immediately -> default flag
+  // selects the abort path.
+  char *p = new (std::nothrow) char[kHugeSize];
+  fprintf(stderr, "FAIL: allocation unexpectedly returned %p\n", p);
+  return 0;
+}
+
+// Linux's secondary mmap fails first (out of memory) and Windows's
+// kMaxAllowedMallocSize check trips first (requested allocation size); both
+// prove the default-flag abort path was taken.
+// CHECK: AddressSanitizer: {{out of memory|requested allocation size}}
+// CHECK: ABORTING
diff --git a/compiler-rt/test/asan/TestCases/nothrow_new_handler_throws_other.cpp b/compiler-rt/test/asan/TestCases/nothrow_new_handler_throws_other.cpp
new file mode 100644
index 0000000000000..e85eb7b6d8729
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/nothrow_new_handler_throws_other.cpp
@@ -0,0 +1,32 @@
+// If the registered std::new_handler throws an exception other than
+// std::bad_alloc, the nothrow operator new must catch it and return nullptr
+// (per [new.delete.single]/4 which specifies as-if try/catch behavior; the
+// framework's try/catch swallows all exception types, not just bad_alloc).
+// Opt-in via allocator_may_return_null=1.
+
+// RUN: %clangxx_asan -O0 %s -o %t
+// RUN: %env_asan_opts=allocator_may_return_null=1 %run %t 2>&1 | FileCheck %s
+
+// UNSUPPORTED: target={{.*windows.*}}
+// REQUIRES: stable-runtime
+
+#include <cstdio>
+#include <new>
+#include <stdexcept>
+
+static const size_t kHugeSize =
+#if __LP64__ || defined(_WIN64)
+    (1ULL << 40) + 1;
+#else
+    (3UL << 30) + 1;
+#endif
+
+static void my_handler() { throw std::runtime_error("oom-policy"); }
+
+int main() {
+  std::set_new_handler(my_handler);
+  char *p = new (std::nothrow) char[kHugeSize];
+  fprintf(stderr, "nothrow returned %s\n", p ? "non-null" : "null");
+  // CHECK: nothrow returned null
+  return 0;
+}
diff --git a/compiler-rt/test/asan/TestCases/nothrow_new_returns_null.cpp b/compiler-rt/test/asan/TestCases/nothrow_new_returns_null.cpp
new file mode 100644
index 0000000000000..19e73ca3c58d7
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/nothrow_new_returns_null.cpp
@@ -0,0 +1,26 @@
+// Per [new.delete.single]/4, nothrow operator new must return nullptr on
+// allocation failure after running the new_handler chain. Opt-in via
+// allocator_may_return_null=1; the default-flag abort case is covered by
+// nothrow_new_default_aborts.cpp.
+
+// RUN: %clangxx_asan -O0 %s -o %t
+// RUN: %env_asan_opts=allocator_may_return_null=1 %run %t 2>&1 | FileCheck %s
+
+// REQUIRES: stable-runtime
+
+#include <cstdio>
+#include <new>
+
+static const size_t kHugeSize =
+#if __LP64__ || defined(_WIN64)
+    (1ULL << 40) + 1;
+#else
+    (3UL << 30) + 1;
+#endif
+
+int main() {
+  char *p = new (std::nothrow) char[kHugeSize];
+  fprintf(stderr, "nothrow returned %s\n", p ? "non-null" : "null");
+  // CHECK: nothrow returned null
+  return 0;
+}
diff --git a/compiler-rt/test/asan/TestCases/nothrow_new_single_returns_null.cpp b/compiler-rt/test/asan/TestCases/nothrow_new_single_returns_null.cpp
new file mode 100644
index 0000000000000..23c264c618437
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/nothrow_new_single_returns_null.cpp
@@ -0,0 +1,25 @@
+// Single-object nothrow operator new must return nullptr on allocation
+// failure (OPERATOR_NEW_BODY_NOTHROW). Opt-in via allocator_may_return_null=1.
+
+// RUN: %clangxx_asan -O0 %s -o %t
+// RUN: %env_asan_opts=allocator_may_return_null=1 %run %t 2>&1 | FileCheck %s
+
+// REQUIRES: stable-runtime
+
+#include <cstdio>
+#include <new>
+
+struct alignas(1) Huge {
+#if __LP64__ || defined(_WIN64)
+  char data[(1ULL << 40) + 1];
+#else
+  char data[(3UL << 30) + 1];
+#endif
+};
+
+int main() {
+  Huge *p = new (std::nothrow) Huge;
+  fprintf(stderr, "nothrow returned %s\n", p ? "non-null" : "null");
+  // CHECK: nothrow returned null
+  return 0;
+}
diff --git a/compiler-rt/test/asan/TestCases/throw_bad_alloc_aligned.cpp b/compiler-rt/test/asan/TestCases/throw_bad_alloc_aligned.cpp
new file mode 100644
index 0000000000000..7384a2fa91f2b
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/throw_bad_alloc_aligned.cpp
@@ -0,0 +1,36 @@
+// Throwing aligned operator new must throw std::bad_alloc on allocation
+// failure, just like the unaligned form. Opt-in via allocator_may_return_null=1.
+
+// RUN: %clangxx_asan -O0 -std=c++17 %s -o %t
+// RUN: %env_asan_opts=allocator_may_return_null=1 %run %t 2>&1 | FileCheck %s
+
+// UNSUPPORTED: target={{.*windows.*}}
+// REQUIRES: stable-runtime
+
+#include <cstdio>
+#include <new>
+
+static const size_t kHugeSize =
+#if __LP64__ || defined(_WIN64)
+    (1ULL << 40) + 1;
+#else
+    (3UL << 30) + 1;
+#endif
+
+struct alignas(64) Aligned {
+  char data[1];
+};
+
+int main() {
+  bool caught = false;
+  try {
+    Aligned *p = new Aligned[kHugeSize];
+    fprintf(stderr, "FAIL: allocation unexpectedly returned %p\n", p);
+  } catch (const std::bad_alloc &) {
+    caught = true;
+  }
+  if (caught)
+    fprintf(stderr, "caught bad_alloc\n");
+  // CHECK: caught bad_alloc
+  return 0;
+}
diff --git a/compiler-rt/test/asan/TestCases/throw_bad_alloc_aligned_single.cpp b/compiler-rt/test/asan/TestCases/throw_bad_alloc_aligned_single.cpp
new file mode 100644
index 0000000000000..e9966cb459080
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/throw_bad_alloc_aligned_single.cpp
@@ -0,0 +1,34 @@
+// Aligned single-object throwing operator new must throw std::bad_alloc on
+// allocation failure (OPERATOR_NEW_BODY_ALIGN). Opt-in via
+// allocator_may_return_null=1.
+
+// RUN: %clangxx_asan -O0 -std=c++17 %s -o %t
+// RUN: %env_asan_opts=allocator_may_return_null=1 %run %t 2>&1 | FileCheck %s
+
+// UNSUPPORTED: target={{.*windows.*}}
+// REQUIRES: stable-runtime
+
+#include <cstdio>
+#include <new>
+
+struct alignas(64) HugeAligned {
+#if __LP64__ || defined(_WIN64)
+  char data[(1ULL << 40) + 1];
+#else
+  char data[(3UL << 30) + 1];
+#endif
+};
+
+int main() {
+  bool caught = false;
+  try {
+    HugeAligned *p = new HugeAligned;
+    fprintf(stderr, "FAIL: allocation unexpectedly returned %p\n", p);
+  } catch (const std::bad_alloc &) {
+    caught = true;
+  }
+  if (caught)
+    fprintf(stderr, "caught bad_alloc\n");
+  // CHECK: caught bad_alloc
+  return 0;
+}
diff --git a/compiler-rt/test/asan/TestCases/throw_bad_alloc_default_aborts.cpp b/compiler-rt/test/asan/TestCases/throw_bad_alloc_default_aborts.cpp
new file mode 100644
index 0000000000000..408747f083922
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/throw_bad_alloc_default_aborts.cpp
@@ -0,0 +1,31 @@
+// With allocator_may_return_null=false (default), throwing operator new
+// aborts on OOM. The handler chain runs (per [new.delete.single]/3); on
+// chain exhaustion the runtime emits the asan ERROR + SUMMARY block and
+// Die()s.
+
+// RUN: %clangxx_asan -O0 %s -o %t
+// RUN: not %run %t 2>&1 | FileCheck %s
+
+// UNSUPPORTED: target={{.*windows.*}}
+// REQUIRES: stable-runtime
+
+#include <cstdio>
+#include <new>
+
+static const size_t kHugeSize =
+#if __LP64__ || defined(_WIN64)
+    (1ULL << 40) + 1;
+#else
+    (3UL << 30) + 1;
+#endif
+
+int main() {
+  // No new_handler installed -> chain exhausts immediately -> default flag
+  // selects the abort path.
+  char *p = new char[kHugeSize];
+  fprintf(stderr, "FAIL: allocation unexpectedly returned %p\n", p);
+  return 0;
+}
+
+// CHECK: AddressSanitizer: out of memory
+// CHECK: ABORTING
diff --git a/compiler-rt/test/asan/TestCases/throw_bad_alloc_oversize.cpp b/compiler-rt/test/asan/TestCases/throw_bad_alloc_oversize.cpp
new file mode 100644
index 0000000000000..dadf85724294e
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/throw_bad_alloc_oversize.cpp
@@ -0,0 +1,35 @@
+// Throwing operator new must throw std::bad_alloc on allocation failure
+// (here triggered by an oversize request) rather than aborting. Opt-in via
+// allocator_may_return_null=1.
+
+// RUN: %clangxx_asan -O0 %s -o %t
+// RUN: %env_asan_opts=allocator_may_return_null=1 %run %t 2>&1 | FileCheck %s
+
+// Windows asan can't throw bad_alloc; see
+// sanitizer_common/sanitizer_new_handler.h.
+// UNSUPPORTED: target={{.*windows.*}}
+// REQUIRES: stable-runtime
+
+#include <cstdio>
+#include <new>
+
+static const size_t kHugeSize =
+#if __LP64__ || defined(_WIN64)
+    (1ULL << 40) + 1;
+#else
+    (3UL << 30) + 1;
+#endif
+
+int main() {
+  bool caught = false;
+  try {
+    char *p = new char[kHugeSize];
+    fprintf(stderr, "FAIL: allocation unexpectedly returned %p\n", p);
+  } catch (const std::bad_alloc &) {
+    caught = true;
+  }
+  if (caught)
+    fprintf(stderr, "caught bad_alloc\n");
+  // CHECK: caught bad_alloc
+  return 0;
+}
diff --git a/compiler-rt/test/asan/TestCases/throw_bad_alloc_single.cpp b/compiler-rt/test/asan/TestCases/throw_bad_alloc_single.cpp
new file mode 100644
index 0000000000000..da019cb0a9f15
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/throw_bad_alloc_single.cpp
@@ -0,0 +1,35 @@
+// Single-object throwing operator new must throw std::bad_alloc on
+// allocation failure (OPERATOR_NEW_BODY). Opt-in via
+// allocator_may_return_null=1.
+
+// RUN: %clangxx_asan -O0 %s -o %t
+// RUN: %env_asan_opts=allocator_may_return_null=1 %run %t 2>&1 | FileCheck %s
+
+// UNSUPPORTED: target={{.*windows.*}}
+// REQUIRES: stable-runtime
+
+#include <cstdio>
+#include <new>
+
+// Single object whose size alone exceeds the allocator's limit.
+struct alignas(1) Huge {
+#if __LP64__ || defined(_WIN64)
+  char data[(1ULL << 40) + 1];
+#else
+  char data[(3UL << 30) + 1];
+#endif
+};
+
+int main() {
+  bool caught = false;
+  try {
+    Huge *p = new Huge;
+    fprintf(stderr, "FAIL: allocation unexpectedly returned %p\n", p);
+  } catch (const std::bad_alloc &) {
+    caught = true;
+  }
+  if (caught)
+    fprintf(stderr, "caught bad_alloc\n");
+  // CHECK: caught bad_alloc
+  return 0;
+}
diff --git a/compiler-rt/test/sanitizer_common/TestCases/Linux/allocator_returns_null_std.cpp b/compiler-rt/test/sanitizer_common/TestCases/Linux/allocator_returns_null_std.cpp
index 812cf049f2a9b..3d315cff02fd8 100644
--- a/compiler-rt/test/sanitizer_common/TestCases/Linux/allocator_returns_null_std.cpp
+++ b/compiler-rt/test/sanitizer_common/TestCases/Linux/allocator_returns_null_std.cpp
@@ -27,4 +27,8 @@ int main(int argc, char **argv) {
 }
 
 // CHECK: #{{[0-9]+.*}}allocator_returns_null_std.cpp
-// CHECK: {{SUMMARY: .*Sanitizer: allocation-size-too-big.*allocator_returns_null_std.cpp.*}} in main
+// std::vector::resize uses throwing operator new[]. asan forces
+// may_return_null=true on Allocate so std::get_new_handler() runs first; the
+// chain-exhausted abort path emits "out-of-memory" rather than the in-place
+// "allocation-size-too-big" emitted by other sanitizers.
+// CHECK: {{SUMMARY: .*Sanitizer: (allocation-size-too-big|out-of-memory).*allocator_returns_null_std.cpp.*}} in main
diff --git a/compiler-rt/test/sanitizer_common/TestCases/allocator_returns_null.cpp b/compiler-rt/test/sanitizer_common/TestCases/allocator_returns_null.cpp
index ca6f637b9a3f5..de7dcf0712197 100644
--- a/compiler-rt/test/sanitizer_common/TestCases/allocator_returns_null.cpp
+++ b/compiler-rt/test/sanitizer_common/TestCases/allocator_returns_null.cpp
@@ -28,14 +28,20 @@
 // RUN:   | FileCheck %s --check-prefix=CHECK-NULL
 // RUN: %env_tool_opts=allocator_may_return_null=0 not %run %t new 2>&1 \
 // RUN:   | FileCheck %s --check-prefix=CHECK-nCRASH
-// RUN: %env_tool_opts=allocator_may_return_null=1 not %run %t new 2>&1 \
-// RUN:   | FileCheck %s --check-prefix=CHECK-nCRASH-OOM
+// flag=1 + throwing new: asan throws bad_alloc, the test catches it and
+// converges to CHECK-NULL; other sanitizers abort inside operator new.
+// RUN: %if asan %{ %env_tool_opts=allocator_may_return_null=1     %run %t new 2>&1 | FileCheck %s --check-prefix=CHECK-NULL %}
+// RUN: %if !asan %{ %env_tool_opts=allocator_may_return_null=1 not %run %t new 2>&1 | FileCheck %s --check-prefix=CHECK-nCRASH-OOM %}
 // RUN: %env_tool_opts=allocator_may_return_null=0 not %run %t new-nothrow 2>&1 \
 // RUN:   | FileCheck %s --check-prefix=CHECK-nnCRASH
 // RUN: %env_tool_opts=allocator_may_return_null=1     %run %t new-nothrow 2>&1 \
 // RUN:   | FileCheck %s --check-prefix=CHECK-NULL
 
 // TODO(alekseyshl): win32 is disabled due to failing errno tests, fix it there.
+// Windows asan would also fail the flag=1 + new cell above: its runtime is
+// built without exceptions and never throws bad_alloc, so the throwing form
+// always falls back to ReportOutOfMemory + Die(). Re-enabling this test on
+// Windows requires tightening the %if asan dispatch to exclude Windows.
 // UNSUPPORTED: ubsan, target={{.*windows-msvc.*}}
 
 #include <assert.h>
@@ -79,7 +85,14 @@ int main(int argc, char **argv) {
     assert(*t == 42);
     free(t);
   } else if (!strcmp(action, "new")) {
-    x = operator new(kMaxAllowedMallocSizePlusOne);
+    try {
+      x = operator new(kMaxAllowedMallocSizePlusOne);
+      assert(0 && "throwing operator new returned without throwing -- "
+                  "violates [basic.stc.dynamic.allocation]/3");
+    } catch (const std::bad_alloc &) {
+      x = nullptr;
+      errno = ENOMEM;
+    }
   } else if (!strcmp(action, "new-nothrow")) {
     x = operator new(kMaxAllowedMallocSizePlusOne, std::nothrow);
   } else {
@@ -110,13 +123,18 @@ int main(int argc, char **argv) {
 // CHECK-mrCRASH: {{SUMMARY: .*Sanitizer: allocation-size-too-big.*allocator_returns_null.cpp.*}} in main
 // CHECK-nCRASH: new:
 // CHECK-nCRASH: #{{[0-9]+.*}}allocator_returns_null.cpp
-// CHECK-nCRASH: {{SUMMARY: .*Sanitizer: allocation-size-too-big.*allocator_returns_null.cpp.*}} in main
+// asan's throwing/nothrow operator new forces may_return_null=true on
+// Allocate so std::get_new_handler() runs first; the chain-exhausted abort
+// path emits "out-of-memory" rather than the in-place
+// "allocation-size-too-big" emitted by other sanitizers. Same alternation
+// applies to CHECK-nnCRASH.
+// CHECK-nCRASH: {{SUMMARY: .*Sanitizer: (allocation-size-too-big|out-of-memory).*allocator_returns_null.cpp.*}} in main
 // CHECK-nCRASH-OOM: new:
-// CHECK-nCRASH-O#{{[0-9]+.*}}allocator_returns_null.cpp
+// CHECK-nCRASH-OOM: #{{[0-9]+.*}}allocator_returns_null.cpp
 // CHECK-nCRASH-OOM: {{SUMMARY: .*Sanitizer: out-of-memory.*allocator_returns_null.cpp.*}} in main
 // CHECK-nnCRASH: new-nothrow:
 // CHECK-nnCRASH: #{{[0-9]+.*}}allocator_returns_null.cpp
-// CHECK-nnCRASH: {{SUMMARY: .*Sanitizer: allocation-size-too-big.*allocator_returns_null.cpp.*}} in main
+// CHECK-nnCRASH: {{SUMMARY: .*Sanitizer: (allocation-size-too-big|out-of-memory).*allocator_returns_null.cpp.*}} in main
 
-// CHECK-NULL: {{malloc|calloc|calloc-overflow|realloc|realloc-after-malloc|new-nothrow}}
+// CHECK-NULL: {{malloc|calloc|calloc-overflow|realloc|realloc-after-malloc|new-nothrow|new}}
 // CHECK-NULL: errno: 12, x: 0
diff --git a/compiler-rt/test/sanitizer_common/TestCases/max_allocation_size.cpp b/compiler-rt/test/sanitizer_common/TestCases/max_allocation_size.cpp
index 2fde16fbed3d2..94e71b9e8835c 100644
--- a/compiler-rt/test/sanitizer_common/TestCases/max_allocation_size.cpp
+++ b/compiler-rt/test/sanitizer_common/TestCases/max_allocation_size.cpp
@@ -28,8 +28,10 @@
 // RUN:   | FileCheck %s --check-prefix=CHECK-NULL
 // RUN: %env_tool_opts=max_allocation_size_mb=2:allocator_may_return_null=0 \
 // RUN:   not %run %t new 2>&1 | FileCheck %s --check-prefix=CHECK-nCRASH
-// RUN: %env_tool_opts=max_allocation_size_mb=2:allocator_may_return_null=1 \
-// RUN:   not %run %t new 2>&1 | FileCheck %s --check-prefix=CHECK-nCRASH-OOM
+// flag=1 + throwing new: asan throws bad_alloc, allocate() catches it and
+// converges to CHECK-NULL; other sanitizers abort inside operator new.
+// RUN: %if asan %{ %env_tool_opts=max_allocation_size_mb=2:allocator_may_return_null=1     %run %t new 2>&1 | FileCheck %s --check-prefix=CHECK-NULL %}
+// RUN: %if !asan %{ %env_tool_opts=max_allocation_size_mb=2:allocator_may_return_null=1 not %run %t new 2>&1 | FileCheck %s --check-prefix=CHECK-nCRASH-OOM %}
 // RUN: %env_tool_opts=max_allocation_size_mb=2:allocator_may_return_null=0 \
 // RUN:   not %run %t new-nothrow 2>&1 \
 // RUN:   | FileCheck %s --check-prefix=CHECK-nnCRASH
@@ -41,6 +43,10 @@
 // RUN:   %run %t strndup 2>&1 | FileCheck %s --check-prefix=CHECK-NULL
 
 // win32 is disabled due to failing errno tests.
+// Windows asan would also fail the flag=1 + new cell above: its runtime is
+// built without exceptions and never throws bad_alloc, so the throwing form
+// always falls back to ReportOutOfMemory + Die(). Re-enabling this test on
+// Windows requires tightening the %if asan dispatch to exclude Windows.
 // UNSUPPORTED: ubsan, target={{.*windows-msvc.*}}
 
 // Symbolizer needs to allocated memory when reporting.
@@ -70,8 +76,18 @@ static void *allocate(const char *Action, size_t Size) {
     free(P);
     return nullptr;
   }
-  if (!strcmp(Action, "new"))
-    return ::operator new(Size);
+  if (!strcmp(Action, "new")) {
+    try {
+      void *p = ::operator new(Size);
+      assert(p != nullptr &&
+             "throwing operator new returned nullptr without throwing -- "
+             "violates [basic.stc.dynamic.allocation]/3");
+      return p;
+    } catch (const std::bad_alloc &) {
+      errno = ENOMEM;
+      return nullptr;
+    }
+  }
   if (!strcmp(Action, "new-nothrow"))
     return ::operator new(Size, std::nothrow);
   if (!strcmp(Action, "strndup")) {
@@ -136,18 +152,23 @@ int main(int Argc, char **Argv) {
 // CHECK-mrCRASH: {{SUMMARY: .*Sanitizer: allocation-size-too-big.* in allocate}}
 // CHECK-nCRASH: new:
 // CHECK-nCRASH: #{{[0-9]+.*}}max_allocation_size.cpp
-// CHECK-nCRASH: {{SUMMARY: .*Sanitizer: allocation-size-too-big.* in allocate}}
+// asan's throwing/nothrow operator new forces may_return_null=true on
+// Allocate so std::get_new_handler() runs first; the chain-exhausted abort
+// path emits "out-of-memory" rather than the in-place
+// "allocation-size-too-big" emitted by other sanitizers. Same alternation
+// applies to CHECK-nnCRASH.
+// CHECK-nCRASH: {{SUMMARY: .*Sanitizer: (allocation-size-too-big|out-of-memory).* in allocate}}
 // CHECK-nCRASH-OOM: new:
 // CHECK-nCRASH-OOM: #{{[0-9]+.*}}max_allocation_size.cpp
 // CHECK-nCRASH-OOM: {{SUMMARY: .*Sanitizer: out-of-memory.* in allocate}}
 // CHECK-nnCRASH: new-nothrow:
 // CHECK-nnCRASH: #{{[0-9]+.*}}max_allocation_size.cpp
-// CHECK-nnCRASH: {{SUMMARY: .*Sanitizer: allocation-size-too-big.* in allocate}}
+// CHECK-nnCRASH: {{SUMMARY: .*Sanitizer: (allocation-size-too-big|out-of-memory).* in allocate}}
 // CHECK-sCRASH: strndup:
 // CHECK-sCRASH: #{{[0-9]+.*}}max_allocation_size.cpp
 // CHECK-sCRASH: {{SUMMARY: .*Sanitizer: allocation-size-too-big.*}}
 
-// CHECK-NULL: {{malloc|calloc|calloc-overflow|realloc|realloc-after-malloc|new-nothrow|strndup}}
+// CHECK-NULL: {{malloc|calloc|calloc-overflow|realloc|realloc-after-malloc|new-nothrow|new|strndup}}
 // CHECK-NULL: errno: 12, P: 0
 //
 // CHECK-NOTNULL-NOT: P: 0



More information about the llvm-commits mailing list