[compiler-rt] [asan] Adopt sanitizer_common operator-new framework (PR #196388)
Justin T. Gibbs via llvm-commits
llvm-commits at lists.llvm.org
Wed Jul 29 09:51:15 PDT 2026
https://github.com/scsiguy updated https://github.com/llvm/llvm-project/pull/196388
>From 74ae735f8fc4561b488da75862016d0e9d0897c3 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 1/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: The framework auto-detects exception support via the standard
__cpp_exceptions feature-test macro: when a consuming TU is
compiled with -fno-exceptions, the throw/try-catch logic is
compiled out and the framework collapses to the abort path
(same shape as Windows). This means TUs do not have to be
built with -fexceptions, and a -fno-exceptions adopter incurs
no std::bad_alloc symbol dependency. When -fexceptions IS used,
instantiating the throwing-form template introduces a runtime
dependency on std::bad_alloc — 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 | 122 ++++++++++++++++++
.../sanitizer_new_operators.inc | 114 ++++++++++++++++
3 files changed, 238 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..6a5a361ec6324
--- /dev/null
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_new_handler.h
@@ -0,0 +1,122 @@
+//===-- 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). Other detected
+// failure modes (e.g. invalid alignment) should abort with
+// a diagnostic.
+//
+// * OnExhausted — invokes the sanitizer's "abort with diagnostic"
+// handler (e.g. asan's ReportOutOfMemory + Die()).
+// Required to never return (enforced by UNREACHABLE()).
+//===----------------------------------------------------------------------===//
+#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 && defined(__cpp_exceptions)
+# include <new>
+#else
+// Builds without exception support can't include <new>: on Windows the
+// sanitizer runtimes are built without exceptions; elsewhere this TU may
+// have been compiled with -fno-exceptions (e.g. with
+// -DCOMPILER_RT_ASAN_ENABLE_EXCEPTIONS=OFF). Forward-declare just enough
+// of std for the loop; the real std::get_new_handler is supplied by the
+// C++ runtime the sanitizer links against (vcruntime / msvcprt /
+// MinGW libstdc++ / libstdc++ / libc++) — 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 && defined(__cpp_exceptions)
+
+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: Exceptions thrown by Alloc or std::new_handler callbacks escape 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 providing pre-C++23 compatibility.
+template <typename OnExhausted>
+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
+// when this TU was compiled with exception support and AllocatorMayReturnNull()
+// is true. Otherwise (Windows runtimes, -fno-exceptions builds, or the
+// default flag value) fall through to the abort path.
+template <typename Alloc, typename OnExhausted>
+void* NewImplThrowing(Alloc alloc, OnExhausted on_exhausted) {
+ void* res = RunNewHandlerChain(alloc);
+ if (LIKELY(res != nullptr))
+ return res;
+#if !SANITIZER_WINDOWS && defined(__cpp_exceptions)
+ 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. When exception support is
+// absent (Windows runtimes or -fno-exceptions TUs) we can't write the
+// try/catch literally — instead we avoid the throw path entirely, so the
+// main body matches 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 && defined(__cpp_exceptions)
+ try {
+#endif
+ void* res = RunNewHandlerChain(alloc);
+ if (LIKELY(res != nullptr))
+ return res;
+ if (AllocatorMayReturnNull())
+ return nullptr;
+ InvokeOnExhausted(on_exhausted);
+#if !SANITIZER_WINDOWS && defined(__cpp_exceptions)
+ } 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..66dd83f2fddc5
--- /dev/null
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_new_operators.inc
@@ -0,0 +1,114 @@
+//===-- 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 prerequisite
+// 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). Other failure modes (e.g. invalid
+// alignment) should cause the helper to abort with a diagnostic.
+//
+//===----------------------------------------------------------------------===//
+
+#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 d4fa816ef9237d95c13c91607c96e3442f0b0f1b 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 2/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: The throwing form aborts (rather than throwing std::bad_alloc)
when the asan_new_delete.cpp TU itself lacks exception support
— i.e. on Windows, or on any build configured with
-DCOMPILER_RT_ASAN_ENABLE_EXCEPTIONS=OFF. The framework auto-
detects this via __cpp_exceptions; no explicit guard is needed
in the asan source.
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
>From 97fd650c20f5a30bf1595ac454553c8086447641 Mon Sep 17 00:00:00 2001
From: "Justin T. Gibbs" <gibbs at scsiguy.com>
Date: Tue, 28 Jul 2026 10:54:38 -0700
Subject: [PATCH 3/3] DIAG (DO NOT MERGE): print std::bad_alloc typeinfo
pointers on both sides
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
max_allocation_size.cpp fails on macOS asan with an uncaught std::bad_alloc,
though on Linux the same test passes end-to-end. The leading hypothesis is a
typeinfo-identity mismatch across the runtime/test dylib boundary — but
theory alone can't decide it, since std::bad_alloc's typeinfo is nominally
sourced from a single libc++abi.dylib on Darwin.
Add temporary logging so a Darwin CI run tells us directly:
* Runtime side (sanitizer_new_handler.h, right before `throw
std::bad_alloc()`): print &_ZTISt9bad_alloc via a forward-declared
Itanium ABI symbol reference (the -fno-rtti TU can't use typeid).
* Test side (max_allocation_size.cpp, in the "new" branch): print
&typeid(std::bad_alloc) before the operator new call, and in the typed
catch clause alongside &typeid(e) for the caught object. Fall back to
catch(...) with abi::__cxa_current_exception_type() so we also see the
type_info* of whatever actually arrives when the typed catch doesn't
match.
Log interpretation matrix:
* All three DIAG lines appear and pointers agree → catch fired, typeinfo
identity holds, our failure hypothesis is wrong.
* DIAG-TEST-BADALLOC-TINFO + DIAG-RUNTIME-BADALLOC-TINFO appear but
neither DIAG-TEST-CAUGHT-BADALLOC nor DIAG-TEST-CAUGHT-OTHER does →
exception died before reaching user code (unwinder/interposition path).
* DIAG-TEST-CAUGHT-OTHER appears with a bad_alloc typeinfo pointer that
differs from the expected one → cross-dylib typeinfo identity failure;
fix is at the link/loader layer.
* DIAG-TEST-CAUGHT-OTHER appears with an unrelated type_info name →
something other than bad_alloc is being thrown; fix is at the throw
site.
Verified locally on Linux — all pointers match at 0x554cf0, typed catch
fires, catch(...) fallback is silent, CHECK-NULL still matches. To be
reverted once the Darwin data is in.
---
.../sanitizer_common/sanitizer_new_handler.h | 17 +++++++++-
.../TestCases/max_allocation_size.cpp | 31 ++++++++++++++++++-
2 files changed, 46 insertions(+), 2 deletions(-)
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_new_handler.h b/compiler-rt/lib/sanitizer_common/sanitizer_new_handler.h
index 6a5a361ec6324..0e8a2aa52262b 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_new_handler.h
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_new_handler.h
@@ -77,6 +77,15 @@ NORETURN void InvokeOnExhausted(OnExhausted on_exhausted) {
UNREACHABLE("operator new OnExhausted callable returned");
}
+// DIAGNOSTIC (DO NOT MERGE): forward-declare the Itanium ABI typeinfo symbol
+// for std::bad_alloc so we can print its address from a -fno-rtti TU without
+// invoking typeid. Guarded to the same condition as the throw itself.
+#if !SANITIZER_WINDOWS && defined(__cpp_exceptions)
+extern "C" {
+extern const char _ZTISt9bad_alloc[];
+}
+#endif
+
// Throwing operator new: chain, then on exhaustion throw std::bad_alloc
// when this TU was compiled with exception support and AllocatorMayReturnNull()
// is true. Otherwise (Windows runtimes, -fno-exceptions builds, or the
@@ -87,8 +96,14 @@ void* NewImplThrowing(Alloc alloc, OnExhausted on_exhausted) {
if (LIKELY(res != nullptr))
return res;
#if !SANITIZER_WINDOWS && defined(__cpp_exceptions)
- if (AllocatorMayReturnNull())
+ if (AllocatorMayReturnNull()) {
+ // DIAGNOSTIC (DO NOT MERGE): print the runtime-side typeinfo pointer for
+ // std::bad_alloc right before the throw, so CI logs let us compare it to
+ // the pointer observed by the test binary at the catch site.
+ Printf("DIAG-RUNTIME-BADALLOC-TINFO: %p\n",
+ (const void*)&_ZTISt9bad_alloc);
throw std::bad_alloc();
+ }
#endif
InvokeOnExhausted(on_exhausted);
}
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 94e71b9e8835c..3d8b872cbce01 100644
--- a/compiler-rt/test/sanitizer_common/TestCases/max_allocation_size.cpp
+++ b/compiler-rt/test/sanitizer_common/TestCases/max_allocation_size.cpp
@@ -53,12 +53,14 @@
// UNSUPPORTED: internal_symbolizer
#include <assert.h>
+#include <cxxabi.h>
#include <errno.h>
#include <limits>
#include <new>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <typeinfo>
constexpr size_t MaxAllocationSize = size_t{2} << 20;
@@ -77,13 +79,40 @@ static void *allocate(const char *Action, size_t Size) {
return nullptr;
}
if (!strcmp(Action, "new")) {
+ // DIAGNOSTIC (DO NOT MERGE): print the test-side typeinfo pointer for
+ // std::bad_alloc so CI logs let us compare it to the runtime-side pointer
+ // printed just before the throw. If the pointers agree, the catch failure
+ // is not a typeinfo-identity issue; if they differ, it is.
+ fprintf(stderr, "DIAG-TEST-BADALLOC-TINFO: %p\n",
+ (const void *)&typeid(std::bad_alloc));
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 &) {
+ } catch (const std::bad_alloc &e) {
+ fprintf(stderr, "DIAG-TEST-CAUGHT-BADALLOC: caught=%p thrown=%p\n",
+ (const void *)&typeid(std::bad_alloc),
+ (const void *)&typeid(e));
+ errno = ENOMEM;
+ return nullptr;
+ } catch (...) {
+ // DIAGNOSTIC (DO NOT MERGE): fallback so we can distinguish
+ // (a) bad_alloc arrived but the typed catch above didn't match
+ // (typeinfo identity issue), from
+ // (b) some other type arrived (wrong throw), from
+ // (c) no exception reached user code at all (terminated before
+ // unwinding to this frame — neither this line nor the typed
+ // catch's line will appear in the log).
+ // __cxa_current_exception_type() returns the type_info* of whatever
+ // is currently being handled, without needing to name the type.
+ const std::type_info *ti = abi::__cxa_current_exception_type();
+ fprintf(stderr,
+ "DIAG-TEST-CAUGHT-OTHER: expected_badalloc=%p got_ti=%p "
+ "got_name=%s\n",
+ (const void *)&typeid(std::bad_alloc), (const void *)ti,
+ ti ? ti->name() : "<null>");
errno = ENOMEM;
return nullptr;
}
More information about the llvm-commits
mailing list