diff --git a/Makefile.in b/Makefile.in index e4d21805..fede961d 100644 --- a/Makefile.in +++ b/Makefile.in @@ -235,7 +235,8 @@ TESTS_UNIT := \ $(srcroot)test/unit/zero.c \ $(srcroot)test/unit/zero_realloc_abort.c \ $(srcroot)test/unit/zero_realloc_free.c \ - $(srcroot)test/unit/zero_realloc_strict.c + $(srcroot)test/unit/zero_realloc_strict.c \ + $(srcroot)test/unit/zero_reallocs.c ifeq (@enable_prof@, 1) TESTS_UNIT += \ $(srcroot)test/unit/arena_reset_prof.c diff --git a/doc/jemalloc.xml.in b/doc/jemalloc.xml.in index 746c6bdb..77afb00c 100644 --- a/doc/jemalloc.xml.in +++ b/doc/jemalloc.xml.in @@ -2451,6 +2451,21 @@ struct extent_hooks_s { + + + stats.zero_reallocs + (size_t) + r- + [] + + Number of times that the realloc() + was called with a non-NULL pointer argument and a + 0 size argument. This is a fundamentally unsafe + pattern in portable programs; see + opt.zero_realloc for details. + + + stats.background_thread.num_threads diff --git a/include/jemalloc/internal/jemalloc_internal_externs.h b/include/jemalloc/internal/jemalloc_internal_externs.h index dae77b42..e9dbde80 100644 --- a/include/jemalloc/internal/jemalloc_internal_externs.h +++ b/include/jemalloc/internal/jemalloc_internal_externs.h @@ -20,6 +20,7 @@ extern bool opt_zero; extern unsigned opt_narenas; extern zero_realloc_action_t opt_zero_realloc_action; extern const char *zero_realloc_mode_names[]; +extern atomic_zu_t zero_realloc_count; /* Number of CPUs. */ extern unsigned ncpus; diff --git a/src/ctl.c b/src/ctl.c index b51207f8..abb82b57 100644 --- a/src/ctl.c +++ b/src/ctl.c @@ -224,6 +224,7 @@ CTL_PROTO(stats_metadata_thp) CTL_PROTO(stats_resident) CTL_PROTO(stats_mapped) CTL_PROTO(stats_retained) +CTL_PROTO(stats_zero_reallocs) CTL_PROTO(experimental_hooks_install) CTL_PROTO(experimental_hooks_remove) CTL_PROTO(experimental_utilization_query) @@ -593,7 +594,8 @@ static const ctl_named_node_t stats_node[] = { {NAME("background_thread"), CHILD(named, stats_background_thread)}, {NAME("mutexes"), CHILD(named, stats_mutexes)}, - {NAME("arenas"), CHILD(indexed, stats_arenas)} + {NAME("arenas"), CHILD(indexed, stats_arenas)}, + {NAME("zero_reallocs"), CTL(stats_zero_reallocs)}, }; static const ctl_named_node_t experimental_hooks_node[] = { @@ -2841,6 +2843,9 @@ CTL_RO_CGEN(config_stats, stats_background_thread_num_runs, CTL_RO_CGEN(config_stats, stats_background_thread_run_interval, nstime_ns(&ctl_stats->background_thread.run_interval), uint64_t) +CTL_RO_CGEN(config_stats, stats_zero_reallocs, + atomic_load_zu(&zero_realloc_count, ATOMIC_RELAXED), size_t) + CTL_RO_GEN(stats_arenas_i_dss, arenas_i(mib[2])->dss, const char *) CTL_RO_GEN(stats_arenas_i_dirty_decay_ms, arenas_i(mib[2])->dirty_decay_ms, ssize_t) diff --git a/src/jemalloc.c b/src/jemalloc.c index 35a9e7b5..88064df4 100644 --- a/src/jemalloc.c +++ b/src/jemalloc.c @@ -70,6 +70,8 @@ bool opt_junk_free = zero_realloc_action_t opt_zero_realloc_action = zero_realloc_action_strict; +atomic_zu_t zero_realloc_count = ATOMIC_INIT(0); + const char *zero_realloc_mode_names[] = { "strict", "free", @@ -3160,6 +3162,9 @@ je_rallocx(void *ptr, size_t size, int flags) { static void * do_realloc_nonnull_zero(void *ptr) { + if (config_stats) { + atomic_fetch_add_zu(&zero_realloc_count, 1, ATOMIC_RELAXED); + } if (opt_zero_realloc_action == zero_realloc_action_strict) { /* * The user might have gotten a strict setting while expecting a diff --git a/src/stats.c b/src/stats.c index c9bab4f7..41b990e2 100644 --- a/src/stats.c +++ b/src/stats.c @@ -1252,6 +1252,7 @@ stats_print_helper(emitter_t *emitter, bool merged, bool destroyed, size_t allocated, active, metadata, metadata_thp, resident, mapped, retained; size_t num_background_threads; + size_t zero_reallocs; uint64_t background_thread_num_runs, background_thread_run_interval; CTL_GET("stats.allocated", &allocated, size_t); @@ -1262,6 +1263,8 @@ stats_print_helper(emitter_t *emitter, bool merged, bool destroyed, CTL_GET("stats.mapped", &mapped, size_t); CTL_GET("stats.retained", &retained, size_t); + CTL_GET("stats.zero_reallocs", &zero_reallocs, size_t); + if (have_background_thread) { CTL_GET("stats.background_thread.num_threads", &num_background_threads, size_t); @@ -1285,12 +1288,18 @@ stats_print_helper(emitter_t *emitter, bool merged, bool destroyed, emitter_json_kv(emitter, "resident", emitter_type_size, &resident); emitter_json_kv(emitter, "mapped", emitter_type_size, &mapped); emitter_json_kv(emitter, "retained", emitter_type_size, &retained); + emitter_json_kv(emitter, "zero_reallocs", emitter_type_size, + &zero_reallocs); emitter_table_printf(emitter, "Allocated: %zu, active: %zu, " "metadata: %zu (n_thp %zu), resident: %zu, mapped: %zu, " "retained: %zu\n", allocated, active, metadata, metadata_thp, resident, mapped, retained); + /* Strange behaviors */ + emitter_table_printf(emitter, + "Count of realloc(non-null-ptr, 0) calls: %zu\n", zero_reallocs); + /* Background thread stats. */ emitter_json_object_kv_begin(emitter, "background_thread"); emitter_json_kv(emitter, "num_threads", emitter_type_size, diff --git a/test/unit/zero_reallocs.c b/test/unit/zero_reallocs.c new file mode 100644 index 00000000..fd33aaf6 --- /dev/null +++ b/test/unit/zero_reallocs.c @@ -0,0 +1,40 @@ +#include "test/jemalloc_test.h" + +static size_t +zero_reallocs() { + if (!config_stats) { + return 0; + } + size_t count = 12345; + size_t sz = sizeof(count); + + assert_d_eq(mallctl("stats.zero_reallocs", (void *)&count, &sz, + NULL, 0), 0, "Unexpected mallctl failure"); + return count; +} + +TEST_BEGIN(test_zero_reallocs) { + test_skip_if(!config_stats); + + for (size_t i = 0; i < 100; ++i) { + void *ptr = mallocx(i * i + 1, 0); + assert_ptr_not_null(ptr, "Unexpected mallocx error"); + size_t count = zero_reallocs(); + assert_zu_eq(i, count, "Incorrect zero realloc count"); + ptr = realloc(ptr, 0); + assert_ptr_null(ptr, "Realloc didn't free"); + count = zero_reallocs(); + assert_zu_eq(i + 1, count, "Realloc didn't adjust count"); + } +} +TEST_END + +int +main(void) { + /* + * We expect explicit counts; reentrant tests run multiple times, so + * counts leak across runs. + */ + return test_no_reentrancy( + test_zero_reallocs); +} diff --git a/test/unit/zero_reallocs.sh b/test/unit/zero_reallocs.sh new file mode 100644 index 00000000..51b01c91 --- /dev/null +++ b/test/unit/zero_reallocs.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +export MALLOC_CONF="zero_realloc:free"