From 4452a4812ff8bc2a5127a9b220de05999a0652f1 Mon Sep 17 00:00:00 2001 From: David Goldblatt Date: Mon, 21 Jun 2021 13:40:30 -0700 Subject: [PATCH] Add opt.experimental_infallible_new. This allows a guarantee that operator new never throws. Fix the .gitignore rules to include test/integration/cpp while we're here. --- .gitignore | 1 + Makefile.in | 4 +- configure.ac | 3 + .../internal/jemalloc_internal_defs.h.in | 3 + .../internal/jemalloc_internal_externs.h | 1 + .../jemalloc/internal/jemalloc_preamble.h.in | 9 +++ src/jemalloc.c | 7 +++ src/jemalloc_cpp.cpp | 7 ++- src/stats.c | 1 + test/integration/cpp/basic.cpp | 1 - test/integration/cpp/infallible_new_false.cpp | 23 +++++++ test/integration/cpp/infallible_new_false.sh | 8 +++ test/integration/cpp/infallible_new_true.cpp | 61 +++++++++++++++++++ test/integration/cpp/infallible_new_true.sh | 8 +++ 14 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 test/integration/cpp/infallible_new_false.cpp create mode 100644 test/integration/cpp/infallible_new_false.sh create mode 100644 test/integration/cpp/infallible_new_true.cpp create mode 100644 test/integration/cpp/infallible_new_true.sh diff --git a/.gitignore b/.gitignore index 0c3c040e..1c0b3385 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,7 @@ test/include/test/jemalloc_test.h test/include/test/jemalloc_test_defs.h /test/integration/[A-Za-z]* +!/test/integration/cpp/ !/test/integration/[A-Za-z]*.* /test/integration/*.[od] /test/integration/*.out diff --git a/Makefile.in b/Makefile.in index 130fa1ee..c36b818b 100644 --- a/Makefile.in +++ b/Makefile.in @@ -309,7 +309,9 @@ TESTS_INTEGRATION += \ endif ifeq (@enable_cxx@, 1) 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 \ + $(srcroot)test/integration/cpp/infallible_new_true.cpp \ + $(srcroot)test/integration/cpp/infallible_new_false.cpp else CPP_SRCS := TESTS_INTEGRATION_CPP := diff --git a/configure.ac b/configure.ac index 17838003..5eb4d46f 100644 --- a/configure.ac +++ b/configure.ac @@ -324,6 +324,9 @@ if test "x$enable_cxx" = "x1" ; then enable_cxx="0" fi fi +if test "x$enable_cxx" = "x1"; then + AC_DEFINE([JEMALLOC_ENABLE_CXX], [ ]) +fi AC_SUBST([enable_cxx]) AC_SUBST([CONFIGURE_CXXFLAGS]) AC_SUBST([SPECIFIED_CXXFLAGS]) diff --git a/include/jemalloc/internal/jemalloc_internal_defs.h.in b/include/jemalloc/internal/jemalloc_internal_defs.h.in index 093c8be0..78d1213e 100644 --- a/include/jemalloc/internal/jemalloc_internal_defs.h.in +++ b/include/jemalloc/internal/jemalloc_internal_defs.h.in @@ -401,6 +401,9 @@ /* Performs additional safety checks when defined. */ #undef JEMALLOC_OPT_SAFETY_CHECKS +/* Is C++ support being built? */ +#undef JEMALLOC_ENABLE_CXX + /* 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 de5731fc..af6dc0a2 100644 --- a/include/jemalloc/internal/jemalloc_internal_externs.h +++ b/include/jemalloc/internal/jemalloc_internal_externs.h @@ -26,6 +26,7 @@ extern void (*junk_free_callback)(void *ptr, size_t size); extern void (*junk_alloc_callback)(void *ptr, size_t size); extern bool opt_utrace; extern bool opt_xmalloc; +extern bool opt_experimental_infallible_new; extern bool opt_zero; extern unsigned opt_narenas; extern zero_realloc_action_t opt_zero_realloc_action; diff --git a/include/jemalloc/internal/jemalloc_preamble.h.in b/include/jemalloc/internal/jemalloc_preamble.h.in index ef1cbaee..f5d83a66 100644 --- a/include/jemalloc/internal/jemalloc_preamble.h.in +++ b/include/jemalloc/internal/jemalloc_preamble.h.in @@ -198,6 +198,15 @@ static const bool config_opt_size_checks = #endif ; +/* Whether or not the C++ extensions are enabled. */ +static const bool config_enable_cxx = +#ifdef JEMALLOC_ENABLE_CXX + true +#else + false +#endif +; + #if defined(_WIN32) || defined(JEMALLOC_HAVE_SCHED_GETCPU) /* Currently percpu_arena depends on sched_getcpu. */ #define JEMALLOC_PERCPU_ARENA diff --git a/src/jemalloc.c b/src/jemalloc.c index 1f489932..c70244d1 100644 --- a/src/jemalloc.c +++ b/src/jemalloc.c @@ -141,6 +141,7 @@ void (*junk_free_callback)(void *ptr, size_t size) = &default_junk_free; bool opt_utrace = false; bool opt_xmalloc = false; +bool opt_experimental_infallible_new = false; bool opt_zero = false; unsigned opt_narenas = 0; fxp_t opt_narenas_ratio = FXP_INIT_INT(4); @@ -1307,6 +1308,12 @@ 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_tcache, "tcache") CONF_HANDLE_SIZE_T(opt_tcache_max, "tcache_max", 0, TCACHE_MAXCLASS_LIMIT, CONF_DONT_CHECK_MIN, diff --git a/src/jemalloc_cpp.cpp b/src/jemalloc_cpp.cpp index 47ba92a0..451655f1 100644 --- a/src/jemalloc_cpp.cpp +++ b/src/jemalloc_cpp.cpp @@ -56,6 +56,12 @@ void operator delete[](void* ptr, std::size_t size, std::align_val_t al) noexcep JEMALLOC_NOINLINE static void * handleOOM(std::size_t size, bool nothrow) { + if (opt_experimental_infallible_new) { + safety_check_fail(": Allocation failed and " + "opt.experimental_infallible_new is true. Aborting.\n"); + return nullptr; + } + void *ptr = nullptr; while (ptr == nullptr) { @@ -93,7 +99,6 @@ fallback_impl(std::size_t size) noexcept(IsNoExcept) { if (likely(ptr != nullptr)) { return ptr; } - return handleOOM(size, IsNoExcept); } diff --git a/src/stats.c b/src/stats.c index 2e8c4516..34cae0ab 100644 --- a/src/stats.c +++ b/src/stats.c @@ -1501,6 +1501,7 @@ 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("tcache") OPT_WRITE_SIZE_T("tcache_max") OPT_WRITE_UNSIGNED("tcache_nslots_small_min") diff --git a/test/integration/cpp/basic.cpp b/test/integration/cpp/basic.cpp index b48ec8aa..c1cf6cd8 100644 --- a/test/integration/cpp/basic.cpp +++ b/test/integration/cpp/basic.cpp @@ -1,4 +1,3 @@ -#include #include "test/jemalloc_test.h" TEST_BEGIN(test_basic) { diff --git a/test/integration/cpp/infallible_new_false.cpp b/test/integration/cpp/infallible_new_false.cpp new file mode 100644 index 00000000..42196d6a --- /dev/null +++ b/test/integration/cpp/infallible_new_false.cpp @@ -0,0 +1,23 @@ +#include + +#include "test/jemalloc_test.h" + +TEST_BEGIN(test_failing_alloc) { + bool saw_exception = false; + try { + /* Too big of an allocation to succeed. */ + void *volatile ptr = ::operator new((size_t)-1); + (void)ptr; + } catch (...) { + saw_exception = true; + } + expect_true(saw_exception, "Didn't get a failure"); +} +TEST_END + +int +main(void) { + return test( + test_failing_alloc); +} + diff --git a/test/integration/cpp/infallible_new_false.sh b/test/integration/cpp/infallible_new_false.sh new file mode 100644 index 00000000..7d41812c --- /dev/null +++ b/test/integration/cpp/infallible_new_false.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}experimental_infallible_new:false" diff --git a/test/integration/cpp/infallible_new_true.cpp b/test/integration/cpp/infallible_new_true.cpp new file mode 100644 index 00000000..9b943bd4 --- /dev/null +++ b/test/integration/cpp/infallible_new_true.cpp @@ -0,0 +1,61 @@ +#include + +/* + * We can't test C++ in unit tests, and we can't change the safety check failure + * hook in integration tests. So we check that we *actually* abort on failure, + * by forking and checking the child process exit code. + */ + +/* It's a unix system? */ +#ifdef __unix__ +/* I know this! */ +#include +#include +#include +static const bool can_fork = true; +#else +static const bool can_fork = false; +#endif + +#include "test/jemalloc_test.h" + +TEST_BEGIN(test_failing_alloc) { + test_skip_if(!can_fork); +#ifdef __unix__ + pid_t pid = fork(); + expect_d_ne(pid, -1, "Unexpected fork failure"); + if (pid == 0) { + /* + * In the child, we'll print an error message to stderr before + * exiting. Close stderr to avoid spamming output for this + * expected failure. + */ + fclose(stderr); + try { + /* Too big of an allocation to succeed. */ + void *volatile ptr = ::operator new((size_t)-1); + (void)ptr; + } catch (...) { + /* + * Swallow the exception; remember, we expect this to + * fail via an abort within new, not because an + * exception didn't get caught. + */ + } + } else { + int status; + pid_t err = waitpid(pid, &status, 0); + expect_d_ne(-1, err, "waitpid failure"); + expect_false(WIFEXITED(status), + "Should have seen an abnormal failure"); + } +#endif +} +TEST_END + +int +main(void) { + return test( + test_failing_alloc); +} + diff --git a/test/integration/cpp/infallible_new_true.sh b/test/integration/cpp/infallible_new_true.sh new file mode 100644 index 00000000..4a0ff542 --- /dev/null +++ b/test/integration/cpp/infallible_new_true.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}experimental_infallible_new:true"