From 160ab9d7f62aca5a930929899a9b3be0e459fd83 Mon Sep 17 00:00:00 2001 From: Slobodan Predolac Date: Thu, 4 Jun 2026 10:58:43 -0700 Subject: [PATCH] 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. --- INSTALL.md | 9 +++++++ Makefile.in | 7 ++++-- configure.ac | 23 +++++++++++++++++ doc/jemalloc.xml.in | 10 ++++++++ .../internal/jemalloc_internal_defs.h.in | 7 ++++++ .../internal/jemalloc_internal_externs.h | 1 - .../jemalloc/internal/jemalloc_preamble.h.in | 12 +++++++++ src/conf.c | 5 ---- src/ctl.c | 7 +++--- src/jemalloc.c | 1 - src/jemalloc_cpp.cpp | 25 ++++++++++--------- src/stats.c | 2 +- ...fallible_new_false.cpp => failing_new.cpp} | 0 test/integration/cpp/failing_new.sh | 8 ++++++ ...llible_new_true.cpp => infallible_new.cpp} | 5 ++-- test/integration/cpp/infallible_new.sh | 8 ++++++ test/integration/cpp/infallible_new_false.sh | 8 ------ test/integration/cpp/infallible_new_true.sh | 8 ------ test/unit/mallctl.c | 1 + 19 files changed, 103 insertions(+), 44 deletions(-) rename test/integration/cpp/{infallible_new_false.cpp => failing_new.cpp} (100%) create mode 100644 test/integration/cpp/failing_new.sh rename test/integration/cpp/{infallible_new_true.cpp => infallible_new.cpp} (87%) create mode 100644 test/integration/cpp/infallible_new.sh delete mode 100644 test/integration/cpp/infallible_new_false.sh delete mode 100644 test/integration/cpp/infallible_new_true.sh diff --git a/INSTALL.md b/INSTALL.md index 7ed27233..abbfefda 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -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 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=` Specify where to find DocBook XSL stylesheets when building the diff --git a/Makefile.in b/Makefile.in index 99c9bf92..16d6a820 100644 --- a/Makefile.in +++ b/Makefile.in @@ -349,8 +349,11 @@ ifeq (@enable_cxx@, 1) CPP_SRCS := $(srcroot)src/jemalloc_cpp.cpp TESTS_INTEGRATION_CPP := $(srcroot)test/integration/cpp/basic.cpp ifeq (@enable_cxx_exceptions@, 1) -TESTS_INTEGRATION_CPP += $(srcroot)test/integration/cpp/infallible_new_true.cpp \ - $(srcroot)test/integration/cpp/infallible_new_false.cpp +ifeq (@enable_cxx_infallible_new@, 1) +TESTS_INTEGRATION_CPP += $(srcroot)test/integration/cpp/infallible_new.cpp +else +TESTS_INTEGRATION_CPP += $(srcroot)test/integration/cpp/failing_new.cpp +endif endif else CPP_SRCS := diff --git a/configure.ac b/configure.ac index 75eea008..6532af1c 100644 --- a/configure.ac +++ b/configure.ac @@ -396,8 +396,31 @@ if test "x$enable_cxx" = "x1"; then enable_cxx_exceptions="0" 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_exceptions]) +AC_SUBST([enable_cxx_infallible_new]) AC_SUBST([CONFIGURE_CXXFLAGS]) AC_SUBST([SPECIFIED_CXXFLAGS]) AC_SUBST([EXTRA_CXXFLAGS]) diff --git a/doc/jemalloc.xml.in b/doc/jemalloc.xml.in index d3e475b8..3f3883bf 100644 --- a/doc/jemalloc.xml.in +++ b/doc/jemalloc.xml.in @@ -846,6 +846,16 @@ mallctl("arena." STRINGIFY(MALLCTL_ARENAS_ALL) ".decay", build configuration. + + + config.infallible_new + (bool) + r- + + was + specified during build configuration. + + config.lazy_lock diff --git a/include/jemalloc/internal/jemalloc_internal_defs.h.in b/include/jemalloc/internal/jemalloc_internal_defs.h.in index 54d4da20..0b750a99 100644 --- a/include/jemalloc/internal/jemalloc_internal_defs.h.in +++ b/include/jemalloc/internal/jemalloc_internal_defs.h.in @@ -468,6 +468,13 @@ /* Are C++ exceptions enabled? */ #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. */ #undef JEMALLOC_OPT_SIZE_CHECKS diff --git a/include/jemalloc/internal/jemalloc_internal_externs.h b/include/jemalloc/internal/jemalloc_internal_externs.h index 5c7b2c87..4d2d18fe 100644 --- a/include/jemalloc/internal/jemalloc_internal_externs.h +++ b/include/jemalloc/internal/jemalloc_internal_externs.h @@ -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 bool opt_utrace; extern bool opt_xmalloc; -extern bool opt_experimental_infallible_new; extern bool opt_experimental_tcache_gc; extern bool opt_zero; extern unsigned opt_narenas; diff --git a/include/jemalloc/internal/jemalloc_preamble.h.in b/include/jemalloc/internal/jemalloc_preamble.h.in index bbfe2513..b00e0b7a 100644 --- a/include/jemalloc/internal/jemalloc_preamble.h.in +++ b/include/jemalloc/internal/jemalloc_preamble.h.in @@ -238,6 +238,18 @@ static const bool config_enable_cxx = #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) /* Currently percpu_arena depends on sched_getcpu. */ #define JEMALLOC_PERCPU_ARENA diff --git a/src/conf.c b/src/conf.c index 85c4ff9f..0ca2112d 100644 --- a/src/conf.c +++ b/src/conf.c @@ -731,11 +731,6 @@ malloc_conf_init_helper(sc_data_t *sc_data, unsigned bin_shard_sizes[SC_NBINS], if (config_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, "experimental_tcache_gc") diff --git a/src/ctl.c b/src/ctl.c index bb4a1876..771ef716 100644 --- a/src/ctl.c +++ b/src/ctl.c @@ -91,6 +91,7 @@ CTL_PROTO(thread_idle) CTL_PROTO(config_cache_oblivious) CTL_PROTO(config_debug) CTL_PROTO(config_fill) +CTL_PROTO(config_infallible_new) CTL_PROTO(config_lazy_lock) CTL_PROTO(config_malloc_conf) CTL_PROTO(config_opt_safety_checks) @@ -143,7 +144,6 @@ CTL_PROTO(opt_junk) CTL_PROTO(opt_zero) CTL_PROTO(opt_utrace) CTL_PROTO(opt_xmalloc) -CTL_PROTO(opt_experimental_infallible_new) CTL_PROTO(opt_experimental_tcache_gc) CTL_PROTO(opt_tcache) 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[] = { {NAME("cache_oblivious"), CTL(config_cache_oblivious)}, {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("malloc_conf"), CTL(config_malloc_conf)}, {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("junk"), CTL(opt_junk)}, {NAME("zero"), CTL(opt_zero)}, {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("tcache"), CTL(opt_tcache)}, {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_debug, 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_malloc_conf, const char *) 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_utrace, opt_utrace, opt_utrace, 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_tcache, opt_tcache, bool) CTL_RO_NL_GEN(opt_tcache_max, opt_tcache_max, size_t) diff --git a/src/jemalloc.c b/src/jemalloc.c index ad1e71a5..6f04da2e 100644 --- a/src/jemalloc.c +++ b/src/jemalloc.c @@ -182,7 +182,6 @@ void (*JET_MUTABLE invalid_conf_abort)(void) = &abort; bool opt_utrace = false; bool opt_xmalloc = false; -bool opt_experimental_infallible_new = false; bool opt_experimental_tcache_gc = true; bool opt_zero = false; unsigned opt_narenas = 0; diff --git a/src/jemalloc_cpp.cpp b/src/jemalloc_cpp.cpp index 8af27da0..dff67150 100644 --- a/src/jemalloc_cpp.cpp +++ b/src/jemalloc_cpp.cpp @@ -66,20 +66,20 @@ void operator delete[]( JEMALLOC_NOINLINE static void * handleOOM(std::size_t size, bool nothrow) { - if (opt_experimental_infallible_new) { - const char *huge_warning = (size >= ((std::size_t)1 << 30)) - ? "This may be caused by heap corruption, if the large size " - "is unexpected (suggest building with sanitizers for " - "debugging)." - : ""; - - safety_check_fail( - ": Allocation of size %zu failed. " - "%s opt.experimental_infallible_new is true. Aborting.\n", - size, huge_warning); +#ifdef JEMALLOC_INFALLIBLE_NEW + if (nothrow) { return nullptr; } - + const char *huge_warning = (size >= ((std::size_t)1 << 30)) + ? "This may be caused by heap corruption, if the large size " + "is unexpected (suggest building with sanitizers for " + "debugging). " + : ""; + safety_check_fail( + ": Allocation of size %zu failed. %sAborting.\n", + size, huge_warning); + return nullptr; +#else void *ptr = nullptr; while (ptr == nullptr) { @@ -108,6 +108,7 @@ handleOOM(std::size_t size, bool nothrow) { #endif } return ptr; +#endif } template diff --git a/src/stats.c b/src/stats.c index 603bb528..2c741277 100644 --- a/src/stats.c +++ b/src/stats.c @@ -1623,6 +1623,7 @@ stats_general_print(emitter_t *emitter) { CONFIG_WRITE_BOOL(cache_oblivious); CONFIG_WRITE_BOOL(debug); CONFIG_WRITE_BOOL(fill); + CONFIG_WRITE_BOOL(infallible_new); CONFIG_WRITE_BOOL(lazy_lock); emitter_kv(emitter, "malloc_conf", "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("utrace") OPT_WRITE_BOOL("xmalloc") - OPT_WRITE_BOOL("experimental_infallible_new") OPT_WRITE_BOOL("experimental_tcache_gc") OPT_WRITE_BOOL("tcache") OPT_WRITE_SIZE_T("tcache_max") diff --git a/test/integration/cpp/infallible_new_false.cpp b/test/integration/cpp/failing_new.cpp similarity index 100% rename from test/integration/cpp/infallible_new_false.cpp rename to test/integration/cpp/failing_new.cpp diff --git a/test/integration/cpp/failing_new.sh b/test/integration/cpp/failing_new.sh new file mode 100644 index 00000000..542d55ac --- /dev/null +++ b/test/integration/cpp/failing_new.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +XMALLOC_STR="" +if [ "x${enable_xmalloc}" = "x1" ] ; then + XMALLOC_STR="xmalloc:false" +fi + +export MALLOC_CONF="${XMALLOC_STR}" diff --git a/test/integration/cpp/infallible_new_true.cpp b/test/integration/cpp/infallible_new.cpp similarity index 87% rename from test/integration/cpp/infallible_new_true.cpp rename to test/integration/cpp/infallible_new.cpp index 8c011b8f..99e52bb3 100644 --- a/test/integration/cpp/infallible_new_true.cpp +++ b/test/integration/cpp/infallible_new.cpp @@ -3,8 +3,9 @@ #include "test/jemalloc_test.h" /* - * We can't test C++ in unit tests. In order to intercept abort, use the - * internal test hook in integration tests. + * Verifies that, when jemalloc is built with --enable-cxx-infallible-new, + * 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; void diff --git a/test/integration/cpp/infallible_new.sh b/test/integration/cpp/infallible_new.sh new file mode 100644 index 00000000..542d55ac --- /dev/null +++ b/test/integration/cpp/infallible_new.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +XMALLOC_STR="" +if [ "x${enable_xmalloc}" = "x1" ] ; then + XMALLOC_STR="xmalloc:false" +fi + +export MALLOC_CONF="${XMALLOC_STR}" diff --git a/test/integration/cpp/infallible_new_false.sh b/test/integration/cpp/infallible_new_false.sh deleted file mode 100644 index 7d41812c..00000000 --- a/test/integration/cpp/infallible_new_false.sh +++ /dev/null @@ -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" diff --git a/test/integration/cpp/infallible_new_true.sh b/test/integration/cpp/infallible_new_true.sh deleted file mode 100644 index 4a0ff542..00000000 --- a/test/integration/cpp/infallible_new_true.sh +++ /dev/null @@ -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" diff --git a/test/unit/mallctl.c b/test/unit/mallctl.c index ed3ee222..08452868 100644 --- a/test/unit/mallctl.c +++ b/test/unit/mallctl.c @@ -266,6 +266,7 @@ TEST_BEGIN(test_mallctl_config) { TEST_MALLCTL_CONFIG(cache_oblivious, bool); TEST_MALLCTL_CONFIG(debug, bool); TEST_MALLCTL_CONFIG(fill, bool); + TEST_MALLCTL_CONFIG(infallible_new, bool); TEST_MALLCTL_CONFIG(lazy_lock, bool); TEST_MALLCTL_CONFIG(malloc_conf, const char *); TEST_MALLCTL_CONFIG(prof, bool);