[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