Replace experimental_infallible_new with compile-time flag

The runtime option aborted on every OOM, breaking new(std::nothrow)
semantics. Replace with configure-time --enable-cxx-infallible-new
(default off): when on, throwing new aborts (size logged) and
nothrow returns null; when off, standard new_handler + bad_alloc /
null behavior is preserved. Under LTO the on-path lets the compiler
prove operator new is no-throw.
This commit is contained in:
Slobodan Predolac
2026-06-04 10:58:43 -07:00
parent a6048680a8
commit 160ab9d7f6
19 changed files with 103 additions and 44 deletions

View File

@@ -217,6 +217,15 @@ any of the following arguments (not a definitive list) to 'configure':
Disable C++ integration. This will cause new and delete operator Disable C++ integration. This will cause new and delete operator
implementations to be omitted. implementations to be omitted.
* `--enable-cxx-infallible-new`
Make the throwing `operator new` abort on allocation failure (logging the
requested size) instead of throwing `std::bad_alloc`; the `std::nothrow`
overloads still return null. Disabled by default, and has no effect when
C++ integration is disabled (`--disable-cxx`). When enabled, under LTO
this lets the compiler treat `operator new` as non-throwing and elide
exception-handling cleanup in callers.
* `--with-xslroot=<path>` * `--with-xslroot=<path>`
Specify where to find DocBook XSL stylesheets when building the Specify where to find DocBook XSL stylesheets when building the

View File

@@ -349,8 +349,11 @@ ifeq (@enable_cxx@, 1)
CPP_SRCS := $(srcroot)src/jemalloc_cpp.cpp CPP_SRCS := $(srcroot)src/jemalloc_cpp.cpp
TESTS_INTEGRATION_CPP := $(srcroot)test/integration/cpp/basic.cpp TESTS_INTEGRATION_CPP := $(srcroot)test/integration/cpp/basic.cpp
ifeq (@enable_cxx_exceptions@, 1) ifeq (@enable_cxx_exceptions@, 1)
TESTS_INTEGRATION_CPP += $(srcroot)test/integration/cpp/infallible_new_true.cpp \ ifeq (@enable_cxx_infallible_new@, 1)
$(srcroot)test/integration/cpp/infallible_new_false.cpp TESTS_INTEGRATION_CPP += $(srcroot)test/integration/cpp/infallible_new.cpp
else
TESTS_INTEGRATION_CPP += $(srcroot)test/integration/cpp/failing_new.cpp
endif
endif endif
else else
CPP_SRCS := CPP_SRCS :=

View File

@@ -396,8 +396,31 @@ if test "x$enable_cxx" = "x1"; then
enable_cxx_exceptions="0" enable_cxx_exceptions="0"
fi fi
fi fi
dnl Do not enable infallible-new by default. When enabled, the throwing
dnl operator new aborts on OOM (with size logged) instead of throwing
dnl std::bad_alloc; nothrow new still returns null. With LTO this lets the
dnl compiler prove operator new doesn't throw and elide exception cleanup.
AC_ARG_ENABLE([cxx-infallible-new],
[AS_HELP_STRING([--enable-cxx-infallible-new],
[Abort on OOM in throwing operator new instead of throwing std::bad_alloc])],
[if test "x$enable_cxx_infallible_new" = "xno" ; then
enable_cxx_infallible_new="0"
else
enable_cxx_infallible_new="1"
fi
],
[enable_cxx_infallible_new="0"]
)
if test "x$enable_cxx" != "x1" ; then
enable_cxx_infallible_new="0"
fi
if test "x$enable_cxx_infallible_new" = "x1" ; then
AC_DEFINE([JEMALLOC_INFALLIBLE_NEW], [ ], [ ])
fi
AC_SUBST([enable_cxx]) AC_SUBST([enable_cxx])
AC_SUBST([enable_cxx_exceptions]) AC_SUBST([enable_cxx_exceptions])
AC_SUBST([enable_cxx_infallible_new])
AC_SUBST([CONFIGURE_CXXFLAGS]) AC_SUBST([CONFIGURE_CXXFLAGS])
AC_SUBST([SPECIFIED_CXXFLAGS]) AC_SUBST([SPECIFIED_CXXFLAGS])
AC_SUBST([EXTRA_CXXFLAGS]) AC_SUBST([EXTRA_CXXFLAGS])

View File

@@ -846,6 +846,16 @@ mallctl("arena." STRINGIFY(MALLCTL_ARENAS_ALL) ".decay",
build configuration.</para></listitem> build configuration.</para></listitem>
</varlistentry> </varlistentry>
<varlistentry id="config.infallible_new">
<term>
<mallctl>config.infallible_new</mallctl>
(<type>bool</type>)
<literal>r-</literal>
</term>
<listitem><para><option>--enable-cxx-infallible-new</option> was
specified during build configuration.</para></listitem>
</varlistentry>
<varlistentry id="config.lazy_lock"> <varlistentry id="config.lazy_lock">
<term> <term>
<mallctl>config.lazy_lock</mallctl> <mallctl>config.lazy_lock</mallctl>

View File

@@ -468,6 +468,13 @@
/* Are C++ exceptions enabled? */ /* Are C++ exceptions enabled? */
#undef JEMALLOC_HAVE_CXX_EXCEPTIONS #undef JEMALLOC_HAVE_CXX_EXCEPTIONS
/*
* If defined, throwing operator new aborts on OOM (with size logged) instead
* of throwing std::bad_alloc. Nothrow new still returns null. With LTO this
* lets the compiler prove operator new is no-throw and elide exception cleanup.
*/
#undef JEMALLOC_INFALLIBLE_NEW
/* Performs additional size checks when defined. */ /* Performs additional size checks when defined. */
#undef JEMALLOC_OPT_SIZE_CHECKS #undef JEMALLOC_OPT_SIZE_CHECKS

View File

@@ -30,7 +30,6 @@ extern void (*JET_MUTABLE junk_alloc_callback)(void *ptr, size_t size);
extern void (*JET_MUTABLE invalid_conf_abort)(void); extern void (*JET_MUTABLE invalid_conf_abort)(void);
extern bool opt_utrace; extern bool opt_utrace;
extern bool opt_xmalloc; extern bool opt_xmalloc;
extern bool opt_experimental_infallible_new;
extern bool opt_experimental_tcache_gc; extern bool opt_experimental_tcache_gc;
extern bool opt_zero; extern bool opt_zero;
extern unsigned opt_narenas; extern unsigned opt_narenas;

View File

@@ -238,6 +238,18 @@ static const bool config_enable_cxx =
#endif #endif
; ;
/*
* Whether the throwing operator new aborts on OOM instead of throwing
* std::bad_alloc (--enable-cxx-infallible-new).
*/
static const bool config_infallible_new =
#ifdef JEMALLOC_INFALLIBLE_NEW
true
#else
false
#endif
;
#if defined(_WIN32) || defined(__APPLE__) || defined(JEMALLOC_HAVE_SCHED_GETCPU) #if defined(_WIN32) || defined(__APPLE__) || defined(JEMALLOC_HAVE_SCHED_GETCPU)
/* Currently percpu_arena depends on sched_getcpu. */ /* Currently percpu_arena depends on sched_getcpu. */
#define JEMALLOC_PERCPU_ARENA #define JEMALLOC_PERCPU_ARENA

View File

@@ -731,11 +731,6 @@ malloc_conf_init_helper(sc_data_t *sc_data, unsigned bin_shard_sizes[SC_NBINS],
if (config_xmalloc) { if (config_xmalloc) {
CONF_HANDLE_BOOL(opt_xmalloc, "xmalloc") CONF_HANDLE_BOOL(opt_xmalloc, "xmalloc")
} }
if (config_enable_cxx) {
CONF_HANDLE_BOOL(
opt_experimental_infallible_new,
"experimental_infallible_new")
}
CONF_HANDLE_BOOL(opt_experimental_tcache_gc, CONF_HANDLE_BOOL(opt_experimental_tcache_gc,
"experimental_tcache_gc") "experimental_tcache_gc")

View File

@@ -91,6 +91,7 @@ CTL_PROTO(thread_idle)
CTL_PROTO(config_cache_oblivious) CTL_PROTO(config_cache_oblivious)
CTL_PROTO(config_debug) CTL_PROTO(config_debug)
CTL_PROTO(config_fill) CTL_PROTO(config_fill)
CTL_PROTO(config_infallible_new)
CTL_PROTO(config_lazy_lock) CTL_PROTO(config_lazy_lock)
CTL_PROTO(config_malloc_conf) CTL_PROTO(config_malloc_conf)
CTL_PROTO(config_opt_safety_checks) CTL_PROTO(config_opt_safety_checks)
@@ -143,7 +144,6 @@ CTL_PROTO(opt_junk)
CTL_PROTO(opt_zero) CTL_PROTO(opt_zero)
CTL_PROTO(opt_utrace) CTL_PROTO(opt_utrace)
CTL_PROTO(opt_xmalloc) CTL_PROTO(opt_xmalloc)
CTL_PROTO(opt_experimental_infallible_new)
CTL_PROTO(opt_experimental_tcache_gc) CTL_PROTO(opt_experimental_tcache_gc)
CTL_PROTO(opt_tcache) CTL_PROTO(opt_tcache)
CTL_PROTO(opt_tcache_max) CTL_PROTO(opt_tcache_max)
@@ -458,6 +458,7 @@ static const ctl_named_node_t thread_node[] = {
static const ctl_named_node_t config_node[] = { static const ctl_named_node_t config_node[] = {
{NAME("cache_oblivious"), CTL(config_cache_oblivious)}, {NAME("cache_oblivious"), CTL(config_cache_oblivious)},
{NAME("debug"), CTL(config_debug)}, {NAME("fill"), CTL(config_fill)}, {NAME("debug"), CTL(config_debug)}, {NAME("fill"), CTL(config_fill)},
{NAME("infallible_new"), CTL(config_infallible_new)},
{NAME("lazy_lock"), CTL(config_lazy_lock)}, {NAME("lazy_lock"), CTL(config_lazy_lock)},
{NAME("malloc_conf"), CTL(config_malloc_conf)}, {NAME("malloc_conf"), CTL(config_malloc_conf)},
{NAME("opt_safety_checks"), CTL(config_opt_safety_checks)}, {NAME("opt_safety_checks"), CTL(config_opt_safety_checks)},
@@ -513,7 +514,6 @@ static const ctl_named_node_t opt_node[] = {{NAME("abort"), CTL(opt_abort)},
{NAME("stats_interval_opts"), CTL(opt_stats_interval_opts)}, {NAME("stats_interval_opts"), CTL(opt_stats_interval_opts)},
{NAME("junk"), CTL(opt_junk)}, {NAME("zero"), CTL(opt_zero)}, {NAME("junk"), CTL(opt_junk)}, {NAME("zero"), CTL(opt_zero)},
{NAME("utrace"), CTL(opt_utrace)}, {NAME("xmalloc"), CTL(opt_xmalloc)}, {NAME("utrace"), CTL(opt_utrace)}, {NAME("xmalloc"), CTL(opt_xmalloc)},
{NAME("experimental_infallible_new"), CTL(opt_experimental_infallible_new)},
{NAME("experimental_tcache_gc"), CTL(opt_experimental_tcache_gc)}, {NAME("experimental_tcache_gc"), CTL(opt_experimental_tcache_gc)},
{NAME("tcache"), CTL(opt_tcache)}, {NAME("tcache"), CTL(opt_tcache)},
{NAME("tcache_max"), CTL(opt_tcache_max)}, {NAME("tcache_max"), CTL(opt_tcache_max)},
@@ -2184,6 +2184,7 @@ label_return:
CTL_RO_CONFIG_GEN(config_cache_oblivious, bool) CTL_RO_CONFIG_GEN(config_cache_oblivious, bool)
CTL_RO_CONFIG_GEN(config_debug, bool) CTL_RO_CONFIG_GEN(config_debug, bool)
CTL_RO_CONFIG_GEN(config_fill, bool) CTL_RO_CONFIG_GEN(config_fill, bool)
CTL_RO_CONFIG_GEN(config_infallible_new, bool)
CTL_RO_CONFIG_GEN(config_lazy_lock, bool) CTL_RO_CONFIG_GEN(config_lazy_lock, bool)
CTL_RO_CONFIG_GEN(config_malloc_conf, const char *) CTL_RO_CONFIG_GEN(config_malloc_conf, const char *)
CTL_RO_CONFIG_GEN(config_opt_safety_checks, bool) CTL_RO_CONFIG_GEN(config_opt_safety_checks, bool)
@@ -2257,8 +2258,6 @@ CTL_RO_NL_CGEN(config_fill, opt_junk, opt_junk, const char *)
CTL_RO_NL_CGEN(config_fill, opt_zero, opt_zero, bool) CTL_RO_NL_CGEN(config_fill, opt_zero, opt_zero, bool)
CTL_RO_NL_CGEN(config_utrace, opt_utrace, opt_utrace, bool) CTL_RO_NL_CGEN(config_utrace, opt_utrace, opt_utrace, bool)
CTL_RO_NL_CGEN(config_xmalloc, opt_xmalloc, opt_xmalloc, bool) CTL_RO_NL_CGEN(config_xmalloc, opt_xmalloc, opt_xmalloc, bool)
CTL_RO_NL_CGEN(config_enable_cxx, opt_experimental_infallible_new,
opt_experimental_infallible_new, bool)
CTL_RO_NL_GEN(opt_experimental_tcache_gc, opt_experimental_tcache_gc, bool) CTL_RO_NL_GEN(opt_experimental_tcache_gc, opt_experimental_tcache_gc, bool)
CTL_RO_NL_GEN(opt_tcache, opt_tcache, bool) CTL_RO_NL_GEN(opt_tcache, opt_tcache, bool)
CTL_RO_NL_GEN(opt_tcache_max, opt_tcache_max, size_t) CTL_RO_NL_GEN(opt_tcache_max, opt_tcache_max, size_t)

View File

@@ -182,7 +182,6 @@ void (*JET_MUTABLE invalid_conf_abort)(void) = &abort;
bool opt_utrace = false; bool opt_utrace = false;
bool opt_xmalloc = false; bool opt_xmalloc = false;
bool opt_experimental_infallible_new = false;
bool opt_experimental_tcache_gc = true; bool opt_experimental_tcache_gc = true;
bool opt_zero = false; bool opt_zero = false;
unsigned opt_narenas = 0; unsigned opt_narenas = 0;

View File

@@ -66,20 +66,20 @@ void operator delete[](
JEMALLOC_NOINLINE JEMALLOC_NOINLINE
static void * static void *
handleOOM(std::size_t size, bool nothrow) { handleOOM(std::size_t size, bool nothrow) {
if (opt_experimental_infallible_new) { #ifdef JEMALLOC_INFALLIBLE_NEW
if (nothrow) {
return nullptr;
}
const char *huge_warning = (size >= ((std::size_t)1 << 30)) const char *huge_warning = (size >= ((std::size_t)1 << 30))
? "This may be caused by heap corruption, if the large size " ? "This may be caused by heap corruption, if the large size "
"is unexpected (suggest building with sanitizers for " "is unexpected (suggest building with sanitizers for "
"debugging)." "debugging). "
: ""; : "";
safety_check_fail( safety_check_fail(
"<jemalloc>: Allocation of size %zu failed. " "<jemalloc>: Allocation of size %zu failed. %sAborting.\n",
"%s opt.experimental_infallible_new is true. Aborting.\n",
size, huge_warning); size, huge_warning);
return nullptr; return nullptr;
} #else
void *ptr = nullptr; void *ptr = nullptr;
while (ptr == nullptr) { while (ptr == nullptr) {
@@ -108,6 +108,7 @@ handleOOM(std::size_t size, bool nothrow) {
#endif #endif
} }
return ptr; return ptr;
#endif
} }
template <bool IsNoExcept> template <bool IsNoExcept>

View File

@@ -1623,6 +1623,7 @@ stats_general_print(emitter_t *emitter) {
CONFIG_WRITE_BOOL(cache_oblivious); CONFIG_WRITE_BOOL(cache_oblivious);
CONFIG_WRITE_BOOL(debug); CONFIG_WRITE_BOOL(debug);
CONFIG_WRITE_BOOL(fill); CONFIG_WRITE_BOOL(fill);
CONFIG_WRITE_BOOL(infallible_new);
CONFIG_WRITE_BOOL(lazy_lock); CONFIG_WRITE_BOOL(lazy_lock);
emitter_kv(emitter, "malloc_conf", "config.malloc_conf", emitter_kv(emitter, "malloc_conf", "config.malloc_conf",
emitter_type_string, &config_malloc_conf); emitter_type_string, &config_malloc_conf);
@@ -1774,7 +1775,6 @@ stats_general_print(emitter_t *emitter) {
OPT_WRITE_BOOL("zero") OPT_WRITE_BOOL("zero")
OPT_WRITE_BOOL("utrace") OPT_WRITE_BOOL("utrace")
OPT_WRITE_BOOL("xmalloc") OPT_WRITE_BOOL("xmalloc")
OPT_WRITE_BOOL("experimental_infallible_new")
OPT_WRITE_BOOL("experimental_tcache_gc") OPT_WRITE_BOOL("experimental_tcache_gc")
OPT_WRITE_BOOL("tcache") OPT_WRITE_BOOL("tcache")
OPT_WRITE_SIZE_T("tcache_max") OPT_WRITE_SIZE_T("tcache_max")

View File

@@ -0,0 +1,8 @@
#!/bin/sh
XMALLOC_STR=""
if [ "x${enable_xmalloc}" = "x1" ] ; then
XMALLOC_STR="xmalloc:false"
fi
export MALLOC_CONF="${XMALLOC_STR}"

View File

@@ -3,8 +3,9 @@
#include "test/jemalloc_test.h" #include "test/jemalloc_test.h"
/* /*
* We can't test C++ in unit tests. In order to intercept abort, use the * Verifies that, when jemalloc is built with --enable-cxx-infallible-new,
* internal test hook in integration tests. * throwing operator new on OOM aborts via safety_check_fail. The test hook
* intercepts the abort and asserts the size-bearing message prefix.
*/ */
bool fake_abort_called; bool fake_abort_called;
void void

View File

@@ -0,0 +1,8 @@
#!/bin/sh
XMALLOC_STR=""
if [ "x${enable_xmalloc}" = "x1" ] ; then
XMALLOC_STR="xmalloc:false"
fi
export MALLOC_CONF="${XMALLOC_STR}"

View File

@@ -1,8 +0,0 @@
#!/bin/sh
XMALLOC_STR=""
if [ "x${enable_xmalloc}" = "x1" ] ; then
XMALLOC_STR="xmalloc:false,"
fi
export MALLOC_CONF="${XMALLOC_STR}experimental_infallible_new:false"

View File

@@ -1,8 +0,0 @@
#!/bin/sh
XMALLOC_STR=""
if [ "x${enable_xmalloc}" = "x1" ] ; then
XMALLOC_STR="xmalloc:false,"
fi
export MALLOC_CONF="${XMALLOC_STR}experimental_infallible_new:true"

View File

@@ -266,6 +266,7 @@ TEST_BEGIN(test_mallctl_config) {
TEST_MALLCTL_CONFIG(cache_oblivious, bool); TEST_MALLCTL_CONFIG(cache_oblivious, bool);
TEST_MALLCTL_CONFIG(debug, bool); TEST_MALLCTL_CONFIG(debug, bool);
TEST_MALLCTL_CONFIG(fill, bool); TEST_MALLCTL_CONFIG(fill, bool);
TEST_MALLCTL_CONFIG(infallible_new, bool);
TEST_MALLCTL_CONFIG(lazy_lock, bool); TEST_MALLCTL_CONFIG(lazy_lock, bool);
TEST_MALLCTL_CONFIG(malloc_conf, const char *); TEST_MALLCTL_CONFIG(malloc_conf, const char *);
TEST_MALLCTL_CONFIG(prof, bool); TEST_MALLCTL_CONFIG(prof, bool);