diff --git a/src/ctl.c b/src/ctl.c index 771ef716..cc803df4 100644 --- a/src/ctl.c +++ b/src/ctl.c @@ -1860,99 +1860,99 @@ ctl_mtx_assert_held(tsdn_t *tsdn) { /******************************************************************************/ /* *_ctl() functions. */ -#define READONLY() \ - do { \ - if (newp != NULL || newlen != 0) { \ - ret = EPERM; \ - goto label_return; \ - } \ - } while (0) +static inline int +ctl_writeonly(void *oldp, size_t *oldlenp) { + if (oldp != NULL || oldlenp != NULL) { + return EPERM; + } + return 0; +} -#define WRITEONLY() \ - do { \ - if (oldp != NULL || oldlenp != NULL) { \ - ret = EPERM; \ - goto label_return; \ - } \ - } while (0) +static inline int +ctl_assured_write(void *dst, size_t dst_size, const void *newp, + size_t newlen) { + if (newp == NULL || newlen != dst_size) { + return EINVAL; + } + memcpy(dst, newp, dst_size); + return 0; +} -/* Can read or write, but not both. */ -#define READ_XOR_WRITE() \ - do { \ - if ((oldp != NULL && oldlenp != NULL) \ - && (newp != NULL || newlen != 0)) { \ - ret = EPERM; \ - goto label_return; \ - } \ - } while (0) +static inline int +ctl_read(void *oldp, size_t *oldlenp, const void *src, size_t src_size) { + if (oldp == NULL || oldlenp == NULL) { + return 0; + } + const size_t oldlen = *oldlenp; + if (oldlen != src_size) { + size_t copylen = (src_size <= oldlen) ? src_size : oldlen; + memcpy(oldp, src, copylen); + *oldlenp = copylen; + return EINVAL; + } + memcpy(oldp, src, src_size); + return 0; +} -/* Can neither read nor write. */ -#define NEITHER_READ_NOR_WRITE() \ - do { \ - if (oldp != NULL || oldlenp != NULL || newp != NULL \ - || newlen != 0) { \ - ret = EPERM; \ - goto label_return; \ - } \ - } while (0) +static inline int +ctl_write(void *dst, size_t dst_size, const void *newp, size_t newlen) { + if (newp == NULL) { + return 0; + } + if (newlen != dst_size) { + return EINVAL; + } + memcpy(dst, newp, dst_size); + return 0; +} -/* Verify that the space provided is enough. */ -#define VERIFY_READ(t) \ - do { \ - if (oldp == NULL || oldlenp == NULL \ - || *oldlenp != sizeof(t)) { \ - if (oldlenp != NULL) { \ - *oldlenp = 0; \ - } \ - ret = EINVAL; \ - goto label_return; \ - } \ - } while (0) +JET_EXTERN int +ctl_readonly(const void *newp, size_t newlen) { + if (newp != NULL || newlen != 0) { + return EPERM; + } + return 0; +} -#define READ(v, t) \ - do { \ - if (oldp != NULL && oldlenp != NULL) { \ - if (*oldlenp != sizeof(t)) { \ - size_t copylen = (sizeof(t) <= *oldlenp) \ - ? sizeof(t) \ - : *oldlenp; \ - memcpy(oldp, (void *)&(v), copylen); \ - *oldlenp = copylen; \ - ret = EINVAL; \ - goto label_return; \ - } \ - *(t *)oldp = (v); \ - } \ - } while (0) +JET_EXTERN int +ctl_neither_read_nor_write(void *oldp, size_t *oldlenp, const void *newp, + size_t newlen) { + if (oldp != NULL || oldlenp != NULL || newp != NULL || newlen != 0) { + return EPERM; + } + return 0; +} -#define WRITE(v, t) \ - do { \ - if (newp != NULL) { \ - if (newlen != sizeof(t)) { \ - ret = EINVAL; \ - goto label_return; \ - } \ - (v) = *(t *)newp; \ - } \ - } while (0) +JET_EXTERN int +ctl_read_xor_write(void *oldp, size_t *oldlenp, const void *newp, + size_t newlen) { + if ((oldp != NULL && oldlenp != NULL) + && (newp != NULL || newlen != 0)) { + return EPERM; + } + return 0; +} -#define ASSURED_WRITE(v, t) \ - do { \ - if (newp == NULL || newlen != sizeof(t)) { \ - ret = EINVAL; \ - goto label_return; \ - } \ - (v) = *(t *)newp; \ - } while (0) +JET_EXTERN int +ctl_verify_read(void *oldp, size_t *oldlenp, size_t expected_size) { + if (oldp == NULL || oldlenp == NULL || *oldlenp != expected_size) { + if (oldlenp != NULL) { + *oldlenp = 0; + } + return EINVAL; + } + return 0; +} -#define MIB_UNSIGNED(v, i) \ - do { \ - if (mib[i] > UINT_MAX) { \ - ret = EFAULT; \ - goto label_return; \ - } \ - v = (unsigned)mib[i]; \ - } while (0) +JET_EXTERN int +ctl_mib_unsigned(unsigned *dst, const size_t *mib, size_t mib_index) { + const size_t value = mib[mib_index]; + if (value > UINT_MAX) { + return EFAULT; + } + *dst = (unsigned)value; + return 0; +} /* * There's a lot of code duplication in the following macros due to limitations @@ -1961,19 +1961,15 @@ ctl_mtx_assert_held(tsdn_t *tsdn) { #define CTL_RO_CGEN(c, n, v, t) \ static int n##_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, \ void *oldp, size_t *oldlenp, void *newp, size_t newlen) { \ - int ret; \ - t oldval; \ - \ if (!(c)) { \ return ENOENT; \ } \ malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); \ - READONLY(); \ - oldval = (v); \ - READ(oldval, t); \ - \ - ret = 0; \ - label_return: \ + int ret = ctl_readonly(newp, newlen); \ + if (ret == 0) { \ + t oldval = (v); \ + ret = ctl_read(oldp, oldlenp, &oldval, sizeof(t)); \ + } \ malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); \ return ret; \ } @@ -1981,16 +1977,12 @@ ctl_mtx_assert_held(tsdn_t *tsdn) { #define CTL_RO_GEN(n, v, t) \ static int n##_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, \ void *oldp, size_t *oldlenp, void *newp, size_t newlen) { \ - int ret; \ - t oldval; \ - \ malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); \ - READONLY(); \ - oldval = (v); \ - READ(oldval, t); \ - \ - ret = 0; \ - label_return: \ + int ret = ctl_readonly(newp, newlen); \ + if (ret == 0) { \ + t oldval = (v); \ + ret = ctl_read(oldp, oldlenp, &oldval, sizeof(t)); \ + } \ malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); \ return ret; \ } @@ -2002,48 +1994,36 @@ ctl_mtx_assert_held(tsdn_t *tsdn) { #define CTL_RO_NL_CGEN(c, n, v, t) \ static int n##_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, \ void *oldp, size_t *oldlenp, void *newp, size_t newlen) { \ - int ret; \ - t oldval; \ - \ if (!(c)) { \ return ENOENT; \ } \ - READONLY(); \ - oldval = (v); \ - READ(oldval, t); \ - \ - ret = 0; \ - label_return: \ + int ret = ctl_readonly(newp, newlen); \ + if (ret == 0) { \ + t oldval = (v); \ + ret = ctl_read(oldp, oldlenp, &oldval, sizeof(t)); \ + } \ return ret; \ } #define CTL_RO_NL_GEN(n, v, t) \ static int n##_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, \ void *oldp, size_t *oldlenp, void *newp, size_t newlen) { \ - int ret; \ - t oldval; \ - \ - READONLY(); \ - oldval = (v); \ - READ(oldval, t); \ - \ - ret = 0; \ - label_return: \ + int ret = ctl_readonly(newp, newlen); \ + if (ret == 0) { \ + t oldval = (v); \ + ret = ctl_read(oldp, oldlenp, &oldval, sizeof(t)); \ + } \ return ret; \ } #define CTL_RO_CONFIG_GEN(n, t) \ static int n##_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, \ void *oldp, size_t *oldlenp, void *newp, size_t newlen) { \ - int ret; \ - t oldval; \ - \ - READONLY(); \ - oldval = n; \ - READ(oldval, t); \ - \ - ret = 0; \ - label_return: \ + int ret = ctl_readonly(newp, newlen); \ + if (ret == 0) { \ + t oldval = n; \ + ret = ctl_read(oldp, oldlenp, &oldval, sizeof(t)); \ + } \ return ret; \ } @@ -2054,18 +2034,18 @@ CTL_RO_NL_GEN(version, JEMALLOC_VERSION, const char *) static int epoch_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; UNUSED uint64_t newval; malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); - WRITE(newval, uint64_t); - if (newp != NULL) { - ctl_refresh(tsd_tsdn(tsd)); + int ret = ctl_write(&newval, sizeof(newval), newp, newlen); + if (ret == 0) { + if (newp != NULL) { + ctl_refresh(tsd_tsdn(tsd)); + } + ret = ctl_read(oldp, oldlenp, &ctl_arenas->epoch, + sizeof(ctl_arenas->epoch)); } - READ(ctl_arenas->epoch, uint64_t); - ret = 0; -label_return: malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); return ret; } @@ -2074,7 +2054,7 @@ static int background_thread_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; - bool oldval; + bool oldval = false; if (!have_background_thread) { return ENOENT; @@ -2083,37 +2063,31 @@ background_thread_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); malloc_mutex_lock(tsd_tsdn(tsd), &background_thread_lock); - if (newp == NULL) { - oldval = background_thread_enabled(); - READ(oldval, bool); - } else { - if (newlen != sizeof(bool)) { - ret = EINVAL; - goto label_return; - } - oldval = background_thread_enabled(); - READ(oldval, bool); - bool newval = *(bool *)newp; - if (newval == oldval) { - ret = 0; - goto label_return; - } + if (newp != NULL && newlen != sizeof(bool)) { + ret = EINVAL; + goto label_return; + } + oldval = background_thread_enabled(); + ret = ctl_read(oldp, oldlenp, &oldval, sizeof(oldval)); + if (ret != 0 || newp == NULL) { + goto label_return; + } + bool newval; + memcpy(&newval, newp, sizeof(newval)); + if (newval != oldval) { background_thread_enabled_set(tsd_tsdn(tsd), newval); if (newval) { if (background_threads_enable(tsd)) { ret = EFAULT; - goto label_return; } } else { if (background_threads_disable(tsd)) { ret = EFAULT; - goto label_return; } } } - ret = 0; label_return: malloc_mutex_unlock(tsd_tsdn(tsd), &background_thread_lock); malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); @@ -2125,7 +2099,7 @@ static int max_background_threads_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; - size_t oldval; + size_t oldval = 0; if (!have_background_thread) { return ENOENT; @@ -2134,44 +2108,41 @@ max_background_threads_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); malloc_mutex_lock(tsd_tsdn(tsd), &background_thread_lock); - if (newp == NULL) { - oldval = max_background_threads; - READ(oldval, size_t); - } else { - if (newlen != sizeof(size_t)) { - ret = EINVAL; - goto label_return; - } - oldval = max_background_threads; - READ(oldval, size_t); - size_t newval = *(size_t *)newp; - if (newval == oldval) { - ret = 0; - goto label_return; - } - if (newval > opt_max_background_threads || newval == 0) { - ret = EINVAL; - goto label_return; - } - - if (background_thread_enabled()) { - background_thread_enabled_set(tsd_tsdn(tsd), false); - if (background_threads_disable(tsd)) { - ret = EFAULT; - goto label_return; - } - max_background_threads = newval; - background_thread_enabled_set(tsd_tsdn(tsd), true); - if (background_threads_enable(tsd)) { - ret = EFAULT; - goto label_return; - } - } else { - max_background_threads = newval; - } + if (newp != NULL && newlen != sizeof(size_t)) { + ret = EINVAL; + goto label_return; + } + oldval = max_background_threads; + ret = ctl_read(oldp, oldlenp, &oldval, sizeof(oldval)); + if (ret != 0 || newp == NULL) { + goto label_return; + } + + size_t newval; + memcpy(&newval, newp, sizeof(newval)); + if (newval == oldval) { + goto label_return; + } + if (newval > opt_max_background_threads || newval == 0) { + ret = EINVAL; + goto label_return; + } + if (background_thread_enabled()) { + background_thread_enabled_set(tsd_tsdn(tsd), false); + if (background_threads_disable(tsd)) { + ret = EFAULT; + goto label_return; + } + max_background_threads = newval; + background_thread_enabled_set(tsd_tsdn(tsd), true); + if (background_threads_enable(tsd)) { + ret = EFAULT; + goto label_return; + } + } else { + max_background_threads = newval; } - ret = 0; label_return: malloc_mutex_unlock(tsd_tsdn(tsd), &background_thread_lock); malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); @@ -2320,7 +2291,6 @@ CTL_RO_NL_CGEN( static int thread_arena_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; arena_t *oldarena; unsigned newind, oldind; @@ -2329,43 +2299,42 @@ thread_arena_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, return EAGAIN; } newind = oldind = arena_ind_get(oldarena); - WRITE(newind, unsigned); - READ(oldind, unsigned); - - if (newind != oldind) { - arena_t *newarena; - - if (newind >= narenas_total_get()) { - /* New arena index is out of range. */ - ret = EFAULT; - goto label_return; - } - - if (have_percpu_arena - && PERCPU_ARENA_ENABLED(opt_percpu_arena)) { - if (newind < percpu_arena_ind_limit(opt_percpu_arena)) { - /* - * If perCPU arena is enabled, thread_arena - * control is not allowed for the auto arena - * range. - */ - ret = EPERM; - goto label_return; - } - } - - /* Initialize arena if necessary. */ - newarena = arena_get(tsd_tsdn(tsd), newind, true); - if (newarena == NULL) { - ret = EAGAIN; - goto label_return; - } - thread_migrate_arena(tsd, oldarena, newarena); + int ret = ctl_write(&newind, sizeof(newind), newp, newlen); + if (ret != 0) { + return ret; + } + ret = ctl_read(oldp, oldlenp, &oldind, sizeof(oldind)); + if (ret != 0) { + return ret; } - ret = 0; -label_return: - return ret; + if (newind == oldind) { + return 0; + } + + if (newind >= narenas_total_get()) { + /* New arena index is out of range. */ + return EFAULT; + } + + if (have_percpu_arena && PERCPU_ARENA_ENABLED(opt_percpu_arena)) { + if (newind < percpu_arena_ind_limit(opt_percpu_arena)) { + /* + * If perCPU arena is enabled, thread_arena control is + * not allowed for the auto arena range. + */ + return EPERM; + } + } + + /* Initialize arena if necessary. */ + arena_t *newarena = arena_get(tsd_tsdn(tsd), newind, true); + if (newarena == NULL) { + return EAGAIN; + } + thread_migrate_arena(tsd, oldarena, newarena); + + return 0; } CTL_RO_NL_GEN(thread_allocated, tsd_thread_allocated_get(tsd), uint64_t) @@ -2374,70 +2343,60 @@ CTL_RO_NL_GEN(thread_allocatedp, tsd_thread_allocatedp_get(tsd), uint64_t *) static int thread_tcache_ncached_max_read_sizeclass_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; size_t bin_size = 0; /* Read the bin size from newp. */ - if (newp == NULL) { - ret = EINVAL; - goto label_return; + int ret = ctl_assured_write(&bin_size, sizeof(bin_size), newp, newlen); + if (ret != 0) { + return ret; } - WRITE(bin_size, size_t); cache_bin_sz_t ncached_max = 0; if (tcache_bin_ncached_max_read(tsd, bin_size, &ncached_max)) { - ret = EINVAL; - goto label_return; + return EINVAL; } size_t result = (size_t)ncached_max; - READ(result, size_t); - ret = 0; -label_return: - return ret; + return ctl_read(oldp, oldlenp, &result, sizeof(result)); } static int thread_tcache_ncached_max_write_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - WRITEONLY(); - if (newp != NULL) { - if (!tcache_available(tsd)) { - ret = ENOENT; - goto label_return; - } - char *settings = NULL; - WRITE(settings, char *); - if (settings == NULL) { - ret = EINVAL; - goto label_return; - } - /* Get the length of the setting string safely. */ - char *end = (char *)memchr( - settings, '\0', CTL_MULTI_SETTING_MAX_LEN); - if (end == NULL) { - ret = EINVAL; - goto label_return; - } - /* - * Exclude the last '\0' for len since it is not handled by - * multi_setting_parse_next. - */ - size_t len = (uintptr_t)end - (uintptr_t)settings; - if (len == 0) { - ret = 0; - goto label_return; - } - - if (tcache_bins_ncached_max_write(tsd, settings, len)) { - ret = EINVAL; - goto label_return; - } + int ret = ctl_writeonly(oldp, oldlenp); + if (ret != 0) { + return ret; + } + if (newp == NULL) { + return 0; + } + if (!tcache_available(tsd)) { + return ENOENT; } - ret = 0; -label_return: - return ret; + char *settings = NULL; + ret = ctl_write(&settings, sizeof(settings), newp, newlen); + if (ret != 0) { + return ret; + } + if (settings == NULL) { + return EINVAL; + } + /* Get the length of the setting string safely. */ + char *end = (char *)memchr( + settings, '\0', CTL_MULTI_SETTING_MAX_LEN); + if (end == NULL) { + return EINVAL; + } + /* + * Exclude the last '\0' for len since it is not handled by + * multi_setting_parse_next. + */ + size_t len = (uintptr_t)end - (uintptr_t)settings; + if (len == 0) { + return 0; + } + + return tcache_bins_ncached_max_write(tsd, settings, len) ? EINVAL : 0; } CTL_RO_NL_GEN(thread_deallocated, tsd_thread_deallocated_get(tsd), uint64_t) @@ -2446,43 +2405,39 @@ CTL_RO_NL_GEN(thread_deallocatedp, tsd_thread_deallocatedp_get(tsd), uint64_t *) static int thread_tcache_enabled_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - bool oldval; + bool oldval = tcache_enabled_get(tsd); - oldval = tcache_enabled_get(tsd); - if (newp != NULL) { - if (newlen != sizeof(bool)) { - ret = EINVAL; - goto label_return; - } - tcache_enabled_set(tsd, *(bool *)newp); + bool newval = false; + int ret = ctl_write(&newval, sizeof(newval), newp, newlen); + if (ret == 0 && newp != NULL) { + tcache_enabled_set(tsd, newval); + } + if (ret == 0) { + ret = ctl_read(oldp, oldlenp, &oldval, sizeof(oldval)); } - READ(oldval, bool); - - ret = 0; -label_return: return ret; } static int thread_tcache_max_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; size_t oldval; /* pointer to tcache_t always exists even with tcache disabled. */ tcache_t *tcache = tsd_tcachep_get(tsd); assert(tcache != NULL); oldval = tcache_max_get(tcache->tcache_slow); - READ(oldval, size_t); + int ret = ctl_read(oldp, oldlenp, &oldval, sizeof(oldval)); + if (ret != 0) { + return ret; + } + size_t new_tcache_max = oldval; + ret = ctl_write(&new_tcache_max, sizeof(new_tcache_max), newp, newlen); + if (ret != 0) { + return ret; + } if (newp != NULL) { - if (newlen != sizeof(size_t)) { - ret = EINVAL; - goto label_return; - } - size_t new_tcache_max = oldval; - WRITE(new_tcache_max, size_t); if (new_tcache_max > TCACHE_MAXCLASS_LIMIT) { new_tcache_max = TCACHE_MAXCLASS_LIMIT; } @@ -2492,129 +2447,109 @@ thread_tcache_max_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, } } - ret = 0; -label_return: - return ret; + return 0; } static int thread_tcache_flush_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - if (!tcache_available(tsd)) { - ret = EFAULT; - goto label_return; + return EFAULT; } - NEITHER_READ_NOR_WRITE(); - - tcache_flush(tsd); - - ret = 0; -label_return: + int ret = ctl_neither_read_nor_write(oldp, oldlenp, newp, newlen); + if (ret == 0) { + tcache_flush(tsd); + } return ret; } static int thread_peak_read_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; if (!config_stats) { return ENOENT; } - READONLY(); - peak_event_update(tsd); - uint64_t result = peak_event_max(tsd); - READ(result, uint64_t); - ret = 0; -label_return: + int ret = ctl_readonly(newp, newlen); + if (ret == 0) { + peak_event_update(tsd); + uint64_t result = peak_event_max(tsd); + ret = ctl_read(oldp, oldlenp, &result, sizeof(result)); + } return ret; } static int thread_peak_reset_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; if (!config_stats) { return ENOENT; } - NEITHER_READ_NOR_WRITE(); - peak_event_zero(tsd); - ret = 0; -label_return: + int ret = ctl_neither_read_nor_write(oldp, oldlenp, newp, newlen); + if (ret == 0) { + peak_event_zero(tsd); + } return ret; } static int thread_prof_name_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - if (!config_prof || !opt_prof) { return ENOENT; } - READ_XOR_WRITE(); - - if (newp != NULL) { - const char *newval = *(const char **)newp; - if (newlen != sizeof(const char *) || newval == NULL) { - ret = EINVAL; - goto label_return; + int ret = ctl_read_xor_write(oldp, oldlenp, newp, newlen); + if (ret == 0 && newp != NULL) { + const char *newval = NULL; + ret = ctl_write(&newval, sizeof(newval), newp, newlen); + if (ret == 0) { + if (newval == NULL) { + ret = EINVAL; + } else { + ret = prof_thread_name_set(tsd, newval); + } } - - if ((ret = prof_thread_name_set(tsd, newval)) != 0) { - goto label_return; - } - } else { + } else if (ret == 0) { const char *oldname = prof_thread_name_get(tsd); - READ(oldname, const char *); + ret = ctl_read(oldp, oldlenp, &oldname, sizeof(oldname)); } - - ret = 0; -label_return: return ret; } static int thread_prof_active_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - bool oldval; - if (!config_prof) { return ENOENT; } - oldval = opt_prof ? prof_thread_active_get(tsd) : false; + bool oldval = opt_prof ? prof_thread_active_get(tsd) : false; + int ret = 0; if (newp != NULL) { if (!opt_prof) { ret = ENOENT; - goto label_return; - } - if (newlen != sizeof(bool)) { - ret = EINVAL; - goto label_return; - } - if (prof_thread_active_set(tsd, *(bool *)newp)) { - ret = EAGAIN; - goto label_return; + } else { + bool newval; + ret = ctl_write(&newval, sizeof(newval), newp, newlen); + if (ret == 0 && prof_thread_active_set(tsd, newval)) { + ret = EAGAIN; + } } } - READ(oldval, bool); - - ret = 0; -label_return: + if (ret == 0) { + ret = ctl_read(oldp, oldlenp, &oldval, sizeof(oldval)); + } return ret; } static int thread_idle_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - - NEITHER_READ_NOR_WRITE(); + int ret = ctl_neither_read_nor_write(oldp, oldlenp, newp, newlen); + if (ret != 0) { + return ret; + } if (tcache_available(tsd)) { tcache_flush(tsd); @@ -2636,9 +2571,7 @@ thread_idle_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, */ } - ret = 0; -label_return: - return ret; + return 0; } /******************************************************************************/ @@ -2646,50 +2579,59 @@ label_return: static int tcache_create_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; unsigned tcache_ind; - READONLY(); - VERIFY_READ(unsigned); - if (tcaches_create(tsd, b0get(), &tcache_ind)) { - ret = EFAULT; - goto label_return; + int ret = ctl_readonly(newp, newlen); + if (ret == 0) { + ret = ctl_verify_read(oldp, oldlenp, sizeof(tcache_ind)); + } + if (ret == 0) { + if (tcaches_create(tsd, b0get(), &tcache_ind)) { + ret = EFAULT; + } else { + ret = ctl_read(oldp, oldlenp, &tcache_ind, + sizeof(tcache_ind)); + } } - READ(tcache_ind, unsigned); - - ret = 0; -label_return: return ret; } static int tcache_flush_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; unsigned tcache_ind; - WRITEONLY(); - ASSURED_WRITE(tcache_ind, unsigned); - tcaches_flush(tsd, tcache_ind); + int ret = ctl_writeonly(oldp, oldlenp); + if (ret != 0) { + return ret; + } - ret = 0; -label_return: - return ret; + ret = ctl_assured_write(&tcache_ind, sizeof(tcache_ind), newp, newlen); + if (ret != 0) { + return ret; + } + + tcaches_flush(tsd, tcache_ind); + return 0; } static int tcache_destroy_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; unsigned tcache_ind; - WRITEONLY(); - ASSURED_WRITE(tcache_ind, unsigned); - tcaches_destroy(tsd, tcache_ind); + int ret = ctl_writeonly(oldp, oldlenp); + if (ret != 0) { + return ret; + } - ret = 0; -label_return: - return ret; + ret = ctl_assured_write(&tcache_ind, sizeof(tcache_ind), newp, newlen); + if (ret != 0) { + return ret; + } + + tcaches_destroy(tsd, tcache_ind); + return 0; } /******************************************************************************/ @@ -2697,22 +2639,21 @@ label_return: static int arena_i_initialized_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; tsdn_t *tsdn = tsd_tsdn(tsd); - unsigned arena_ind; + unsigned arena_ind = 0; bool initialized; - READONLY(); - MIB_UNSIGNED(arena_ind, 1); + int ret = ctl_readonly(newp, newlen); + if (ret == 0) { + ret = ctl_mib_unsigned(&arena_ind, mib, 1); + } + if (ret == 0) { + malloc_mutex_lock(tsdn, &ctl_mtx); + initialized = arenas_i(arena_ind)->initialized; + malloc_mutex_unlock(tsdn, &ctl_mtx); - malloc_mutex_lock(tsdn, &ctl_mtx); - initialized = arenas_i(arena_ind)->initialized; - malloc_mutex_unlock(tsdn, &ctl_mtx); - - READ(initialized, bool); - - ret = 0; -label_return: + ret = ctl_read(oldp, oldlenp, &initialized, sizeof(initialized)); + } return ret; } @@ -2749,30 +2690,30 @@ arena_i_decay(tsdn_t *tsdn, unsigned arena_ind, bool all) { static int arena_i_decay_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - unsigned arena_ind; + unsigned arena_ind = 0; - NEITHER_READ_NOR_WRITE(); - MIB_UNSIGNED(arena_ind, 1); - arena_i_decay(tsd_tsdn(tsd), arena_ind, false); - - ret = 0; -label_return: + int ret = ctl_neither_read_nor_write(oldp, oldlenp, newp, newlen); + if (ret == 0) { + ret = ctl_mib_unsigned(&arena_ind, mib, 1); + } + if (ret == 0) { + arena_i_decay(tsd_tsdn(tsd), arena_ind, false); + } return ret; } static int arena_i_purge_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - unsigned arena_ind; + unsigned arena_ind = 0; - NEITHER_READ_NOR_WRITE(); - MIB_UNSIGNED(arena_ind, 1); - arena_i_decay(tsd_tsdn(tsd), arena_ind, true); - - ret = 0; -label_return: + int ret = ctl_neither_read_nor_write(oldp, oldlenp, newp, newlen); + if (ret == 0) { + ret = ctl_mib_unsigned(&arena_ind, mib, 1); + } + if (ret == 0) { + arena_i_decay(tsd_tsdn(tsd), arena_ind, true); + } return ret; } @@ -2780,19 +2721,16 @@ static int arena_i_reset_destroy_helper(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen, unsigned *arena_ind, arena_t **arena) { - int ret; - - NEITHER_READ_NOR_WRITE(); - MIB_UNSIGNED(*arena_ind, 1); - - *arena = arena_get(tsd_tsdn(tsd), *arena_ind, false); - if (*arena == NULL || arena_is_auto(*arena)) { - ret = EFAULT; - goto label_return; + int ret = ctl_neither_read_nor_write(oldp, oldlenp, newp, newlen); + if (ret == 0) { + ret = ctl_mib_unsigned(arena_ind, mib, 1); + } + if (ret == 0) { + *arena = arena_get(tsd_tsdn(tsd), *arena_ind, false); + if (*arena == NULL || arena_is_auto(*arena)) { + ret = EFAULT; + } } - - ret = 0; -label_return: return ret; } @@ -2895,14 +2833,20 @@ label_return: static int arena_i_dss_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; const char *dss = NULL; - unsigned arena_ind; + unsigned arena_ind = 0; dss_prec_t dss_prec = dss_prec_limit; malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); - WRITE(dss, const char *); - MIB_UNSIGNED(arena_ind, 1); + int ret = ctl_write(&dss, sizeof(dss), newp, newlen); + if (ret != 0) { + goto label_return; + } + ret = ctl_mib_unsigned(&arena_ind, mib, 1); + if (ret != 0) { + goto label_return; + } + if (dss != NULL) { int i; bool match = false; @@ -2925,7 +2869,7 @@ arena_i_dss_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, * Access via index narenas is deprecated, and scheduled for removal in * 6.0.0. */ - dss_prec_t dss_prec_old; + dss_prec_t dss_prec_old = dss_prec_limit; unsigned narenas = ctl_narenas_get(tsd_tsdn(tsd)); if (ctl_arena_ind_is_all(arena_ind, narenas)) { if (dss_prec != dss_prec_limit @@ -2946,9 +2890,7 @@ arena_i_dss_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, } dss = dss_prec_names[dss_prec_old]; - READ(dss, const char *); - - ret = 0; + ret = ctl_read(oldp, oldlenp, &dss, sizeof(dss)); label_return: malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); return ret; @@ -2957,69 +2899,59 @@ label_return: static int arena_i_oversize_threshold_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - unsigned arena_ind; - MIB_UNSIGNED(arena_ind, 1); + int ret = ctl_mib_unsigned(&arena_ind, mib, 1); + if (ret != 0) { + return ret; + } arena_t *arena = arena_get(tsd_tsdn(tsd), arena_ind, false); if (arena == NULL) { - ret = EFAULT; - goto label_return; + return EFAULT; } - if (oldp != NULL && oldlenp != NULL) { - size_t oldval = atomic_load_zu( - &arena->pa_shard.pac.oversize_threshold, ATOMIC_RELAXED); - READ(oldval, size_t); + size_t oldval = atomic_load_zu( + &arena->pa_shard.pac.oversize_threshold, ATOMIC_RELAXED); + ret = ctl_read(oldp, oldlenp, &oldval, sizeof(oldval)); + if (ret != 0 || newp == NULL) { + return ret; } - if (newp != NULL) { - if (newlen != sizeof(size_t)) { - ret = EINVAL; - goto label_return; - } - atomic_store_zu(&arena->pa_shard.pac.oversize_threshold, - *(size_t *)newp, ATOMIC_RELAXED); + + size_t newval; + ret = ctl_write(&newval, sizeof(newval), newp, newlen); + if (ret == 0) { + atomic_store_zu(&arena->pa_shard.pac.oversize_threshold, newval, + ATOMIC_RELAXED); } - ret = 0; -label_return: return ret; } static int arena_i_decay_ms_ctl_impl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen, bool dirty) { - int ret; unsigned arena_ind; - arena_t *arena; + int ret = ctl_mib_unsigned(&arena_ind, mib, 1); + if (ret != 0) { + return ret; + } - MIB_UNSIGNED(arena_ind, 1); - arena = arena_get(tsd_tsdn(tsd), arena_ind, false); + arena_t *arena = arena_get(tsd_tsdn(tsd), arena_ind, false); if (arena == NULL) { - ret = EFAULT; - goto label_return; + return EFAULT; } + extent_state_t state = dirty ? extent_state_dirty : extent_state_muzzy; - - if (oldp != NULL && oldlenp != NULL) { - ssize_t oldval = arena_decay_ms_get(arena, state); - READ(oldval, ssize_t); - } - if (newp != NULL) { - if (newlen != sizeof(ssize_t)) { - ret = EINVAL; - goto label_return; - } - - if (arena_decay_ms_set( - tsd_tsdn(tsd), arena, state, *(ssize_t *)newp)) { - ret = EFAULT; - goto label_return; - } + ssize_t oldval = arena_decay_ms_get(arena, state); + ret = ctl_read(oldp, oldlenp, &oldval, sizeof(oldval)); + if (ret != 0 || newp == NULL) { + return ret; } - ret = 0; -label_return: + ssize_t newval; + ret = ctl_write(&newval, sizeof(newval), newp, newlen); + if (ret == 0 && arena_decay_ms_set(tsd_tsdn(tsd), arena, state, newval)) { + ret = EFAULT; + } return ret; } @@ -3040,58 +2972,64 @@ arena_i_muzzy_decay_ms_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, static int arena_i_extent_hooks_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; unsigned arena_ind; arena_t *arena; malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); - MIB_UNSIGNED(arena_ind, 1); - if (arena_ind < narenas_total_get()) { + int ret = ctl_mib_unsigned(&arena_ind, mib, 1); + if (ret == 0 && arena_ind >= narenas_total_get()) { + ret = EFAULT; + } + if (ret == 0) { extent_hooks_t *old_extent_hooks; arena = arena_get(tsd_tsdn(tsd), arena_ind, false); if (arena == NULL) { if (arena_ind >= narenas_auto) { ret = EFAULT; - goto label_return; + } else { + old_extent_hooks = + (extent_hooks_t *)&ehooks_default_extent_hooks; + ret = ctl_read(oldp, oldlenp, &old_extent_hooks, + sizeof(extent_hooks_t *)); } - old_extent_hooks = - (extent_hooks_t *)&ehooks_default_extent_hooks; - READ(old_extent_hooks, extent_hooks_t *); - if (newp != NULL) { + if (ret == 0 && newp != NULL) { /* Initialize a new arena as a side effect. */ extent_hooks_t *new_extent_hooks JEMALLOC_CC_SILENCE_INIT(NULL); - WRITE(new_extent_hooks, extent_hooks_t *); - arena_config_t config = arena_config_default; - config.extent_hooks = new_extent_hooks; + ret = ctl_write(&new_extent_hooks, + sizeof(extent_hooks_t *), newp, newlen); + if (ret == 0) { + arena_config_t config = arena_config_default; + config.extent_hooks = new_extent_hooks; - arena = arena_init( - tsd_tsdn(tsd), arena_ind, &config); - if (arena == NULL) { - ret = EFAULT; - goto label_return; + arena = arena_init( + tsd_tsdn(tsd), arena_ind, &config); + if (arena == NULL) { + ret = EFAULT; + } } } } else { if (newp != NULL) { extent_hooks_t *new_extent_hooks JEMALLOC_CC_SILENCE_INIT(NULL); - WRITE(new_extent_hooks, extent_hooks_t *); - old_extent_hooks = arena_set_extent_hooks( - tsd, arena, new_extent_hooks); - READ(old_extent_hooks, extent_hooks_t *); + ret = ctl_write(&new_extent_hooks, + sizeof(extent_hooks_t *), newp, newlen); + if (ret == 0) { + old_extent_hooks = arena_set_extent_hooks( + tsd, arena, new_extent_hooks); + ret = ctl_read(oldp, oldlenp, + &old_extent_hooks, + sizeof(extent_hooks_t *)); + } } else { old_extent_hooks = ehooks_get_extent_hooks_ptr( arena_get_ehooks(arena)); - READ(old_extent_hooks, extent_hooks_t *); + ret = ctl_read(oldp, oldlenp, &old_extent_hooks, + sizeof(extent_hooks_t *)); } } - } else { - ret = EFAULT; - goto label_return; } - ret = 0; -label_return: malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); return ret; } @@ -3099,9 +3037,7 @@ label_return: static int arena_i_retain_grow_limit_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; unsigned arena_ind; - arena_t *arena; if (!opt_retain) { /* Only relevant when retain is enabled. */ @@ -3109,25 +3045,31 @@ arena_i_retain_grow_limit_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, } malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); - MIB_UNSIGNED(arena_ind, 1); - if (arena_ind < narenas_total_get() - && (arena = arena_get(tsd_tsdn(tsd), arena_ind, false)) != NULL) { - size_t old_limit, new_limit; - if (newp != NULL) { - WRITE(new_limit, size_t); - } - bool err = arena_retain_grow_limit_get_set( - tsd, arena, &old_limit, newp != NULL ? &new_limit : NULL); - if (!err) { - READ(old_limit, size_t); - ret = 0; - } else { + + int ret = ctl_mib_unsigned(&arena_ind, mib, 1); + arena_t *arena = NULL; + if (ret == 0) { + arena = arena_ind < narenas_total_get() ? + arena_get(tsd_tsdn(tsd), arena_ind, false) : NULL; + if (arena == NULL) { ret = EFAULT; } - } else { - ret = EFAULT; } -label_return: + + size_t old_limit; + size_t new_limit; + if (ret == 0) { + ret = ctl_write(&new_limit, sizeof(new_limit), newp, newlen); + } + if (ret == 0) { + bool err = arena_retain_grow_limit_get_set( + tsd, arena, &old_limit, newp != NULL ? &new_limit : NULL); + ret = err ? EFAULT : 0; + } + if (ret == 0) { + ret = ctl_read(oldp, oldlenp, &old_limit, sizeof(old_limit)); + } + malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); return ret; } @@ -3141,18 +3083,20 @@ label_return: static int arena_i_name_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - unsigned arena_ind; - char *name JEMALLOC_CLANG_ANALYZER_SILENCE_INIT(NULL); + unsigned arena_ind; malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); - MIB_UNSIGNED(arena_ind, 1); + + int ret = ctl_mib_unsigned(&arena_ind, mib, 1); + if (ret != 0) { + goto label_return; + } unsigned narenas = ctl_narenas_get(tsd_tsdn(tsd)); - if (ctl_arena_ind_is_all(arena_ind, narenas) - || arena_ind > narenas) { + if (ctl_arena_ind_is_all(arena_ind, narenas) || arena_ind > narenas) { ret = EINVAL; goto label_return; } + arena_t *arena = arena_get(tsd_tsdn(tsd), arena_ind, false); if (arena == NULL) { ret = EFAULT; @@ -3169,20 +3113,24 @@ arena_i_name_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, ret = EINVAL; goto label_return; } - name = *(char **)oldp; - arena_name_get(arena, name); + char *old_name = *(char **)oldp; + arena_name_get(arena, old_name); } + char *new_name = NULL; + ret = ctl_write(&new_name, sizeof(new_name), newp, newlen); + if (ret != 0) { + goto label_return; + } if (newp != NULL) { - /* Write the arena name. */ - WRITE(name, char *); - if (name == NULL) { + if (new_name == NULL) { ret = EINVAL; goto label_return; } - arena_name_set(arena, name); + /* Write the arena name. */ + arena_name_set(arena, new_name); } - ret = 0; + label_return: malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); return ret; @@ -3216,16 +3164,12 @@ label_return: static int arenas_narenas_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - unsigned narenas; - malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); - READONLY(); - narenas = ctl_narenas_get(tsd_tsdn(tsd)); - READ(narenas, unsigned); - - ret = 0; -label_return: + int ret = ctl_readonly(newp, newlen); + if (ret == 0) { + unsigned narenas = ctl_narenas_get(tsd_tsdn(tsd)); + ret = ctl_read(oldp, oldlenp, &narenas, sizeof(narenas)); + } malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); return ret; } @@ -3233,28 +3177,18 @@ label_return: static int arenas_decay_ms_ctl_impl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen, bool dirty) { - int ret; - - if (oldp != NULL && oldlenp != NULL) { - ssize_t oldval = (dirty ? arena_dirty_decay_ms_default_get() - : arena_muzzy_decay_ms_default_get()); - READ(oldval, ssize_t); - } - if (newp != NULL) { - if (newlen != sizeof(ssize_t)) { - ret = EINVAL; - goto label_return; - } - if (dirty - ? arena_dirty_decay_ms_default_set(*(ssize_t *)newp) - : arena_muzzy_decay_ms_default_set(*(ssize_t *)newp)) { + ssize_t oldval = dirty ? arena_dirty_decay_ms_default_get() + : arena_muzzy_decay_ms_default_get(); + int ret = ctl_read(oldp, oldlenp, &oldval, sizeof(oldval)); + if (ret == 0 && newp != NULL) { + ssize_t newval; + ret = ctl_write(&newval, sizeof(newval), newp, newlen); + if (ret == 0 + && (dirty ? arena_dirty_decay_ms_default_set(newval) + : arena_muzzy_decay_ms_default_set(newval))) { ret = EFAULT; - goto label_return; } } - - ret = 0; -label_return: return ret; } @@ -3305,33 +3239,28 @@ arenas_lextent_i_index( static int ctl_arena_create(tsd_t *tsd, void *oldp, size_t *oldlenp, const arena_config_t *config) { - int ret; - unsigned arena_ind; - - if ((arena_ind = ctl_arena_init(tsd, config)) == UINT_MAX) { - ret = EAGAIN; - goto label_return; + unsigned arena_ind = ctl_arena_init(tsd, config); + if (arena_ind == UINT_MAX) { + return EAGAIN; } - READ(arena_ind, unsigned); - - ret = 0; -label_return: - return ret; + return ctl_read(oldp, oldlenp, &arena_ind, sizeof(arena_ind)); } static int arenas_create_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); - VERIFY_READ(unsigned); + int ret = ctl_verify_read(oldp, oldlenp, sizeof(unsigned)); arena_config_t config = arena_config_default; - WRITE(config.extent_hooks, extent_hooks_t *); + if (ret == 0) { + ret = ctl_write(&config.extent_hooks, + sizeof(extent_hooks_t *), newp, newlen); + } - ret = ctl_arena_create(tsd, oldp, oldlenp, &config); -label_return: + if (ret == 0) { + ret = ctl_arena_create(tsd, oldp, oldlenp, &config); + } malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); return ret; } @@ -3339,16 +3268,17 @@ label_return: static int experimental_arenas_create_ext_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); arena_config_t config = arena_config_default; - VERIFY_READ(unsigned); - WRITE(config, arena_config_t); + int ret = ctl_verify_read(oldp, oldlenp, sizeof(unsigned)); + if (ret == 0) { + ret = ctl_write(&config, sizeof(config), newp, newlen); + } - ret = ctl_arena_create(tsd, oldp, oldlenp, &config); -label_return: + if (ret == 0) { + ret = ctl_arena_create(tsd, oldp, oldlenp, &config); + } malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); return ret; } @@ -3356,33 +3286,33 @@ label_return: static int arenas_lookup_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - unsigned arena_ind; - void *ptr; - emap_full_alloc_ctx_t alloc_ctx; - bool ptr_not_present; - arena_t *arena; + void *ptr = NULL; - ptr = NULL; - ret = EINVAL; malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); - WRITE(ptr, void *); - ptr_not_present = emap_full_alloc_ctx_try_lookup( - tsd_tsdn(tsd), &arena_emap_global, ptr, &alloc_ctx); - if (ptr_not_present || alloc_ctx.edata == NULL) { - goto label_return; + + int ret = ctl_write(&ptr, sizeof(ptr), newp, newlen); + emap_full_alloc_ctx_t alloc_ctx; + if (ret == 0) { + bool ptr_not_present = emap_full_alloc_ctx_try_lookup( + tsd_tsdn(tsd), &arena_emap_global, ptr, &alloc_ctx); + if (ptr_not_present || alloc_ctx.edata == NULL) { + ret = EINVAL; + } } - arena = arena_get_from_edata(alloc_ctx.edata); - if (arena == NULL) { - goto label_return; + arena_t *arena = NULL; + if (ret == 0) { + arena = arena_get_from_edata(alloc_ctx.edata); + if (arena == NULL) { + ret = EINVAL; + } } - arena_ind = arena_ind_get(arena); - READ(arena_ind, unsigned); + if (ret == 0) { + unsigned arena_ind = arena_ind_get(arena); + ret = ctl_read(oldp, oldlenp, &arena_ind, sizeof(arena_ind)); + } - ret = 0; -label_return: malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); return ret; } @@ -3392,130 +3322,119 @@ label_return: static int prof_thread_active_init_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - bool oldval; - if (!config_prof) { return ENOENT; } + bool oldval = false; + int ret = 0; if (newp != NULL) { if (!opt_prof) { ret = ENOENT; - goto label_return; + } else { + bool newval; + ret = ctl_write(&newval, sizeof(newval), newp, newlen); + if (ret == 0) { + oldval = prof_thread_active_init_set( + tsd_tsdn(tsd), newval); + } } - if (newlen != sizeof(bool)) { - ret = EINVAL; - goto label_return; - } - oldval = prof_thread_active_init_set( - tsd_tsdn(tsd), *(bool *)newp); } else { oldval = opt_prof ? prof_thread_active_init_get(tsd_tsdn(tsd)) : false; } - READ(oldval, bool); - - ret = 0; -label_return: + if (ret == 0) { + ret = ctl_read(oldp, oldlenp, &oldval, sizeof(oldval)); + } return ret; } static int prof_active_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - bool oldval; - if (!config_prof) { - ret = ENOENT; - goto label_return; + return ENOENT; } + bool oldval = false; + int ret = 0; if (newp != NULL) { - if (newlen != sizeof(bool)) { - ret = EINVAL; - goto label_return; - } - bool val = *(bool *)newp; - if (!opt_prof) { - if (val) { - ret = ENOENT; - goto label_return; + bool val; + ret = ctl_write(&val, sizeof(val), newp, newlen); + if (ret == 0) { + if (!opt_prof) { + if (val) { + ret = ENOENT; + } else { + /* No change needed (already off). */ + oldval = false; + } } else { - /* No change needed (already off). */ - oldval = false; + oldval = prof_active_set(tsd_tsdn(tsd), val); } - } else { - oldval = prof_active_set(tsd_tsdn(tsd), val); } } else { oldval = opt_prof ? prof_active_get(tsd_tsdn(tsd)) : false; } - READ(oldval, bool); - - ret = 0; -label_return: + if (ret == 0) { + ret = ctl_read(oldp, oldlenp, &oldval, sizeof(oldval)); + } return ret; } static int prof_dump_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; const char *filename = NULL; if (!config_prof || !opt_prof) { return ENOENT; } - WRITEONLY(); - WRITE(filename, const char *); - - if (prof_mdump(tsd, filename)) { - ret = EFAULT; - goto label_return; + int ret = ctl_writeonly(oldp, oldlenp); + if (ret != 0) { + return ret; } - ret = 0; -label_return: - return ret; + ret = ctl_write(&filename, sizeof(filename), newp, newlen); + if (ret != 0) { + return ret; + } + + return prof_mdump(tsd, filename) ? EFAULT : 0; } static int prof_gdump_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - bool oldval; - if (!config_prof) { return ENOENT; } + bool oldval = false; + int ret = 0; if (newp != NULL) { if (!opt_prof) { ret = ENOENT; - goto label_return; + } else { + bool newval; + ret = ctl_write(&newval, sizeof(newval), newp, newlen); + if (ret == 0) { + oldval = prof_gdump_set(tsd_tsdn(tsd), newval); + } } - if (newlen != sizeof(bool)) { - ret = EINVAL; - goto label_return; - } - oldval = prof_gdump_set(tsd_tsdn(tsd), *(bool *)newp); } else { oldval = opt_prof ? prof_gdump_get(tsd_tsdn(tsd)) : false; } - READ(oldval, bool); - - ret = 0; -label_return: + if (ret == 0) { + ret = ctl_read(oldp, oldlenp, &oldval, sizeof(oldval)); + } return ret; } static int prof_prefix_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; const char *prefix = NULL; if (!config_prof || !opt_prof) { @@ -3523,11 +3442,14 @@ prof_prefix_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, } malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); - WRITEONLY(); - WRITE(prefix, const char *); + int ret = ctl_writeonly(oldp, oldlenp); + if (ret == 0) { + ret = ctl_write(&prefix, sizeof(prefix), newp, newlen); + } + if (ret == 0) { + ret = prof_prefix_set(tsd_tsdn(tsd), prefix) ? EFAULT : 0; + } - ret = prof_prefix_set(tsd_tsdn(tsd), prefix) ? EFAULT : 0; -label_return: malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); return ret; } @@ -3535,24 +3457,27 @@ label_return: static int prof_reset_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; size_t lg_sample = lg_prof_sample; if (!config_prof || !opt_prof) { return ENOENT; } - WRITEONLY(); - WRITE(lg_sample, size_t); + int ret = ctl_writeonly(oldp, oldlenp); + if (ret != 0) { + return ret; + } + + ret = ctl_write(&lg_sample, sizeof(lg_sample), newp, newlen); + if (ret != 0) { + return ret; + } if (lg_sample >= (sizeof(uint64_t) << 3)) { lg_sample = (sizeof(uint64_t) << 3) - 1; } prof_reset(tsd, lg_sample); - - ret = 0; -label_return: - return ret; + return 0; } CTL_RO_NL_CGEN(config_prof, prof_interval, prof_interval, uint64_t) @@ -3561,25 +3486,23 @@ CTL_RO_NL_CGEN(config_prof, lg_prof_sample, lg_prof_sample, size_t) static int prof_log_start_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - const char *filename = NULL; if (!config_prof || !opt_prof) { return ENOENT; } - WRITEONLY(); - WRITE(filename, const char *); - - if (prof_log_start(tsd_tsdn(tsd), filename)) { - ret = EFAULT; - goto label_return; + int ret = ctl_writeonly(oldp, oldlenp); + if (ret != 0) { + return ret; } - ret = 0; -label_return: - return ret; + ret = ctl_write(&filename, sizeof(filename), newp, newlen); + if (ret != 0) { + return ret; + } + + return prof_log_start(tsd_tsdn(tsd), filename) ? EFAULT : 0; } static int @@ -3599,131 +3522,134 @@ prof_log_stop_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, static int experimental_hooks_prof_backtrace_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - if (oldp == NULL && newp == NULL) { - ret = EINVAL; - goto label_return; + return EINVAL; } + + int ret = 0; if (oldp != NULL) { prof_backtrace_hook_t old_hook = prof_backtrace_hook_get(); - READ(old_hook, prof_backtrace_hook_t); + ret = ctl_read(oldp, oldlenp, &old_hook, sizeof(old_hook)); } - if (newp != NULL) { + + prof_backtrace_hook_t new_hook = NULL; + if (ret == 0 && newp != NULL) { if (!opt_prof) { ret = ENOENT; - goto label_return; + } else { + ret = ctl_write(&new_hook, sizeof(new_hook), newp, + newlen); } - prof_backtrace_hook_t new_hook JEMALLOC_CC_SILENCE_INIT(NULL); - WRITE(new_hook, prof_backtrace_hook_t); + } + if (ret == 0 && newp != NULL) { if (new_hook == NULL) { ret = EINVAL; - goto label_return; + } else { + prof_backtrace_hook_set(new_hook); } - prof_backtrace_hook_set(new_hook); } - ret = 0; -label_return: + return ret; } static int experimental_hooks_prof_dump_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - if (oldp == NULL && newp == NULL) { - ret = EINVAL; - goto label_return; + return EINVAL; } + + int ret = 0; if (oldp != NULL) { prof_dump_hook_t old_hook = prof_dump_hook_get(); - READ(old_hook, prof_dump_hook_t); + ret = ctl_read(oldp, oldlenp, &old_hook, sizeof(old_hook)); } - if (newp != NULL) { + + prof_dump_hook_t new_hook = NULL; + if (ret == 0 && newp != NULL) { if (!opt_prof) { ret = ENOENT; - goto label_return; + } else { + ret = ctl_write(&new_hook, sizeof(new_hook), newp, + newlen); } - prof_dump_hook_t new_hook JEMALLOC_CC_SILENCE_INIT(NULL); - WRITE(new_hook, prof_dump_hook_t); + } + if (ret == 0 && newp != NULL) { prof_dump_hook_set(new_hook); } - ret = 0; -label_return: + return ret; } static int experimental_hooks_prof_sample_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - if (oldp == NULL && newp == NULL) { - ret = EINVAL; - goto label_return; + return EINVAL; } + + int ret = 0; if (oldp != NULL) { prof_sample_hook_t old_hook = prof_sample_hook_get(); - READ(old_hook, prof_sample_hook_t); + ret = ctl_read(oldp, oldlenp, &old_hook, sizeof(old_hook)); } - if (newp != NULL) { + + prof_sample_hook_t new_hook = NULL; + if (ret == 0 && newp != NULL) { if (!opt_prof) { ret = ENOENT; - goto label_return; + } else { + ret = ctl_write(&new_hook, sizeof(new_hook), newp, + newlen); } - prof_sample_hook_t new_hook JEMALLOC_CC_SILENCE_INIT(NULL); - WRITE(new_hook, prof_sample_hook_t); + } + if (ret == 0 && newp != NULL) { prof_sample_hook_set(new_hook); } - ret = 0; -label_return: + return ret; } static int experimental_hooks_prof_sample_free_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - if (oldp == NULL && newp == NULL) { - ret = EINVAL; - goto label_return; + return EINVAL; } + + int ret = 0; if (oldp != NULL) { prof_sample_free_hook_t old_hook = prof_sample_free_hook_get(); - READ(old_hook, prof_sample_free_hook_t); + ret = ctl_read(oldp, oldlenp, &old_hook, sizeof(old_hook)); } - if (newp != NULL) { + + prof_sample_free_hook_t new_hook = NULL; + if (ret == 0 && newp != NULL) { if (!opt_prof) { ret = ENOENT; - goto label_return; + } else { + ret = ctl_write(&new_hook, sizeof(new_hook), newp, + newlen); } - prof_sample_free_hook_t new_hook JEMALLOC_CC_SILENCE_INIT(NULL); - WRITE(new_hook, prof_sample_free_hook_t); + } + if (ret == 0 && newp != NULL) { prof_sample_free_hook_set(new_hook); } - ret = 0; -label_return: + return ret; } static int experimental_hooks_thread_event_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; + user_hook_object_t t_new = {NULL, 0, false}; - if (newp == NULL) { - ret = EINVAL; - goto label_return; + int ret = ctl_assured_write(&t_new, sizeof(t_new), newp, newlen); + if (ret != 0) { + return ret; } - user_hook_object_t t_new = {NULL, 0, false}; - WRITE(t_new, user_hook_object_t); - ret = te_register_user_handler(tsd_tsdn(tsd), &t_new); - -label_return: - return ret; + return te_register_user_handler(tsd_tsdn(tsd), &t_new); } /******************************************************************************/ @@ -3764,29 +3690,26 @@ CTL_RO_CGEN(config_stats, stats_zero_reallocs, static int approximate_stats_active_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - size_t approximate_nactive = 0; - size_t approximate_active_bytes = 0; + int ret = ctl_readonly(newp, newlen); + if (ret == 0) { + tsdn_t *tsdn = tsd_tsdn(tsd); + unsigned n = narenas_total_get(); - READONLY(); - - tsdn_t *tsdn = tsd_tsdn(tsd); - unsigned n = narenas_total_get(); - - for (unsigned i = 0; i < n; i++) { - arena_t *arena = arena_get(tsdn, i, false); - if (!arena) { - continue; + size_t approximate_nactive = 0; + for (unsigned i = 0; i < n; i++) { + arena_t *arena = arena_get(tsdn, i, false); + if (!arena) { + continue; + } + /* Accumulate nactive pages from each arena's pa_shard */ + approximate_nactive += + pa_shard_nactive(&arena->pa_shard); } - /* Accumulate nactive pages from each arena's pa_shard */ - approximate_nactive += pa_shard_nactive(&arena->pa_shard); + + size_t approximate_active_bytes = approximate_nactive << LG_PAGE; + ret = ctl_read(oldp, oldlenp, &approximate_active_bytes, + sizeof(approximate_active_bytes)); } - - approximate_active_bytes = approximate_nactive << LG_PAGE; - READ(approximate_active_bytes, size_t); - - ret = 0; -label_return: return ret; } @@ -4399,33 +4322,27 @@ label_return: static int experimental_prof_recent_alloc_max_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - if (!(config_prof && opt_prof)) { - ret = ENOENT; - goto label_return; + return ENOENT; } - ssize_t old_max; + ssize_t old_max = 0; + int ret = 0; if (newp != NULL) { - if (newlen != sizeof(ssize_t)) { + ssize_t max; + ret = ctl_write(&max, sizeof(max), newp, newlen); + if (ret == 0 && max < -1) { ret = EINVAL; - goto label_return; } - ssize_t max = *(ssize_t *)newp; - if (max < -1) { - ret = EINVAL; - goto label_return; + if (ret == 0) { + old_max = prof_recent_alloc_max_ctl_write(tsd, max); } - old_max = prof_recent_alloc_max_ctl_write(tsd, max); } else { old_max = prof_recent_alloc_max_ctl_read(); } - READ(old_max, ssize_t); - - ret = 0; - -label_return: + if (ret == 0) { + ret = ctl_read(oldp, oldlenp, &old_max, sizeof(old_max)); + } return ret; } @@ -4438,77 +4355,74 @@ struct write_cb_packet_s { static int experimental_prof_recent_alloc_dump_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - if (!(config_prof && opt_prof)) { - ret = ENOENT; - goto label_return; + return ENOENT; } assert(sizeof(write_cb_packet_t) == sizeof(void *) * 2); - WRITEONLY(); + int ret = ctl_writeonly(oldp, oldlenp); + if (ret != 0) { + return ret; + } + write_cb_packet_t write_cb_packet; - ASSURED_WRITE(write_cb_packet, write_cb_packet_t); + ret = ctl_assured_write( + &write_cb_packet, sizeof(write_cb_packet), newp, newlen); + if (ret != 0) { + return ret; + } prof_recent_alloc_dump( tsd, write_cb_packet.write_cb, write_cb_packet.cbopaque); - - ret = 0; - -label_return: - return ret; + return 0; } static int prof_stats_bins_i_live_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - unsigned binind; + unsigned binind = 0; prof_stats_t stats; if (!(config_prof && opt_prof && opt_prof_stats)) { - ret = ENOENT; - goto label_return; + return ENOENT; } - READONLY(); - MIB_UNSIGNED(binind, 3); - if (binind >= SC_NBINS) { + int ret = ctl_readonly(newp, newlen); + if (ret == 0) { + ret = ctl_mib_unsigned(&binind, mib, 3); + } + if (ret == 0 && binind >= SC_NBINS) { ret = EINVAL; - goto label_return; } - prof_stats_get_live(tsd, (szind_t)binind, &stats); - READ(stats, prof_stats_t); - - ret = 0; -label_return: + if (ret == 0) { + prof_stats_get_live(tsd, (szind_t)binind, &stats); + ret = ctl_read(oldp, oldlenp, &stats, sizeof(stats)); + } return ret; } static int prof_stats_bins_i_accum_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - unsigned binind; + unsigned binind = 0; prof_stats_t stats; if (!(config_prof && opt_prof && opt_prof_stats)) { - ret = ENOENT; - goto label_return; + return ENOENT; } - READONLY(); - MIB_UNSIGNED(binind, 3); - if (binind >= SC_NBINS) { + int ret = ctl_readonly(newp, newlen); + if (ret == 0) { + ret = ctl_mib_unsigned(&binind, mib, 3); + } + if (ret == 0 && binind >= SC_NBINS) { ret = EINVAL; - goto label_return; } - prof_stats_get_accum(tsd, (szind_t)binind, &stats); - READ(stats, prof_stats_t); - - ret = 0; -label_return: + if (ret == 0) { + prof_stats_get_accum(tsd, (szind_t)binind, &stats); + ret = ctl_read(oldp, oldlenp, &stats, sizeof(stats)); + } return ret; } @@ -4527,52 +4441,50 @@ prof_stats_bins_i_index( static int prof_stats_lextents_i_live_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - unsigned lextent_ind; + unsigned lextent_ind = 0; prof_stats_t stats; if (!(config_prof && opt_prof && opt_prof_stats)) { - ret = ENOENT; - goto label_return; + return ENOENT; } - READONLY(); - MIB_UNSIGNED(lextent_ind, 3); - if (lextent_ind >= SC_NSIZES - SC_NBINS) { + int ret = ctl_readonly(newp, newlen); + if (ret == 0) { + ret = ctl_mib_unsigned(&lextent_ind, mib, 3); + } + if (ret == 0 && lextent_ind >= SC_NSIZES - SC_NBINS) { ret = EINVAL; - goto label_return; } - prof_stats_get_live(tsd, (szind_t)(lextent_ind + SC_NBINS), &stats); - READ(stats, prof_stats_t); - - ret = 0; -label_return: + if (ret == 0) { + prof_stats_get_live( + tsd, (szind_t)(lextent_ind + SC_NBINS), &stats); + ret = ctl_read(oldp, oldlenp, &stats, sizeof(stats)); + } return ret; } static int prof_stats_lextents_i_accum_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { - int ret; - unsigned lextent_ind; + unsigned lextent_ind = 0; prof_stats_t stats; if (!(config_prof && opt_prof && opt_prof_stats)) { - ret = ENOENT; - goto label_return; + return ENOENT; } - READONLY(); - MIB_UNSIGNED(lextent_ind, 3); - if (lextent_ind >= SC_NSIZES - SC_NBINS) { + int ret = ctl_readonly(newp, newlen); + if (ret == 0) { + ret = ctl_mib_unsigned(&lextent_ind, mib, 3); + } + if (ret == 0 && lextent_ind >= SC_NSIZES - SC_NBINS) { ret = EINVAL; - goto label_return; } - prof_stats_get_accum(tsd, (szind_t)(lextent_ind + SC_NBINS), &stats); - READ(stats, prof_stats_t); - - ret = 0; -label_return: + if (ret == 0) { + prof_stats_get_accum( + tsd, (szind_t)(lextent_ind + SC_NBINS), &stats); + ret = ctl_read(oldp, oldlenp, &stats, sizeof(stats)); + } return ret; } diff --git a/test/unit/mallctl.c b/test/unit/mallctl.c index 08452868..031641a5 100644 --- a/test/unit/mallctl.c +++ b/test/unit/mallctl.c @@ -1,8 +1,19 @@ #include "test/jemalloc_test.h" #include "jemalloc/internal/ctl.h" +#include "jemalloc/internal/arena.h" #include "jemalloc/internal/util.h" +extern int ctl_mib_unsigned( + unsigned *dst, const size_t *mib, size_t mib_index); +extern int ctl_verify_read(void *oldp, size_t *oldlenp, + size_t expected_size); +extern int ctl_readonly(const void *newp, size_t newlen); +extern int ctl_neither_read_nor_write(void *oldp, size_t *oldlenp, + const void *newp, size_t newlen); +extern int ctl_read_xor_write(void *oldp, size_t *oldlenp, const void *newp, + size_t newlen); + TEST_BEGIN(test_mallctl_errors) { uint64_t epoch; size_t sz; @@ -81,6 +92,129 @@ TEST_BEGIN(test_mallctlbymib_errors) { } TEST_END +TEST_BEGIN(test_ctl_mib_unsigned) { + size_t mib[2]; + unsigned result = 0; + + mib[1] = UINT_MAX; + expect_d_eq(ctl_mib_unsigned(&result, mib, 1), 0, + "Unexpected ctl_mib_unsigned failure"); + expect_u_eq(result, UINT_MAX, "Unexpected unsigned mib value"); + + test_skip_if(SIZE_MAX <= UINT_MAX); + + result = 42; + mib[1] = (size_t)UINT_MAX + 1; + expect_d_eq(ctl_mib_unsigned(&result, mib, 1), EFAULT, + "Expected ctl_mib_unsigned overflow failure"); + expect_u_eq(result, 42, "ctl_mib_unsigned modified output on failure"); +} +TEST_END + +TEST_BEGIN(test_ctl_verify_read) { + unsigned old = 0; + size_t oldlen = sizeof(old); + + expect_d_eq(ctl_verify_read(&old, &oldlen, sizeof(old)), 0, + "Unexpected ctl_verify_read() failure"); + expect_zu_eq(oldlen, sizeof(old), "Unexpected oldlen update"); + + oldlen = sizeof(old); + expect_d_eq(ctl_verify_read(NULL, &oldlen, sizeof(old)), EINVAL, + "Unexpected ctl_verify_read() success"); + expect_zu_eq(oldlen, 0, "Unexpected oldlen value"); + + expect_d_eq(ctl_verify_read(&old, NULL, sizeof(old)), EINVAL, + "Unexpected ctl_verify_read() success"); + + oldlen = sizeof(old) - 1; + expect_d_eq(ctl_verify_read(&old, &oldlen, sizeof(old)), EINVAL, + "Unexpected ctl_verify_read() success"); + expect_zu_eq(oldlen, 0, "Unexpected oldlen value"); + + oldlen = sizeof(old) + 1; + expect_d_eq(ctl_verify_read(&old, &oldlen, sizeof(old)), EINVAL, + "Unexpected ctl_verify_read() success"); + expect_zu_eq(oldlen, 0, "Unexpected oldlen value"); +} +TEST_END + +TEST_BEGIN(test_ctl_readonly) { + unsigned newval = 0; + + /* No write input provided: read-only access is permitted. */ + expect_d_eq(ctl_readonly(NULL, 0), 0, + "Unexpected ctl_readonly() failure"); + + /* A non-NULL newp is a write attempt: forbidden. */ + expect_d_eq(ctl_readonly(&newval, 0), EPERM, + "Unexpected ctl_readonly() success"); + + /* A nonzero newlen is a write attempt: forbidden. */ + expect_d_eq(ctl_readonly(NULL, sizeof(newval)), EPERM, + "Unexpected ctl_readonly() success"); + + /* Both set: forbidden. */ + expect_d_eq(ctl_readonly(&newval, sizeof(newval)), EPERM, + "Unexpected ctl_readonly() success"); +} +TEST_END + +TEST_BEGIN(test_ctl_neither_read_nor_write) { + unsigned val = 0; + size_t len = sizeof(val); + + /* No input or output supplied at all: permitted. */ + expect_d_eq(ctl_neither_read_nor_write(NULL, NULL, NULL, 0), 0, + "Unexpected ctl_neither_read_nor_write() failure"); + + /* Any output pointer is a read attempt: forbidden. */ + expect_d_eq(ctl_neither_read_nor_write(&val, NULL, NULL, 0), EPERM, + "Unexpected success with non-NULL oldp"); + expect_d_eq(ctl_neither_read_nor_write(NULL, &len, NULL, 0), EPERM, + "Unexpected success with non-NULL oldlenp"); + + /* Any input is a write attempt: forbidden. */ + expect_d_eq(ctl_neither_read_nor_write(NULL, NULL, &val, 0), EPERM, + "Unexpected success with non-NULL newp"); + expect_d_eq(ctl_neither_read_nor_write(NULL, NULL, NULL, sizeof(val)), + EPERM, "Unexpected success with nonzero newlen"); +} +TEST_END + +TEST_BEGIN(test_ctl_read_xor_write) { + unsigned val = 0; + size_t len = sizeof(val); + + /* Read only: allowed. */ + expect_d_eq(ctl_read_xor_write(&val, &len, NULL, 0), 0, + "Unexpected failure for read-only"); + + /* Write only: allowed. */ + expect_d_eq(ctl_read_xor_write(NULL, NULL, &val, sizeof(val)), 0, + "Unexpected failure for write-only"); + + /* Neither: allowed. */ + expect_d_eq(ctl_read_xor_write(NULL, NULL, NULL, 0), 0, + "Unexpected failure for neither"); + + /* Both read and write: forbidden. */ + expect_d_eq(ctl_read_xor_write(&val, &len, &val, sizeof(val)), EPERM, + "Unexpected success for read+write"); + + /* Read plus a nonzero newlen also counts as both: forbidden. */ + expect_d_eq(ctl_read_xor_write(&val, &len, NULL, sizeof(val)), EPERM, + "Unexpected success for read + nonzero newlen"); + + /* + * A half-specified read (oldp set but oldlenp NULL) is not a read, so + * pairing it with a write is allowed. + */ + expect_d_eq(ctl_read_xor_write(&val, NULL, &val, sizeof(val)), 0, + "Unexpected failure when oldlenp is NULL"); +} +TEST_END + TEST_BEGIN(test_mallctl_read_write) { uint64_t old_epoch, new_epoch; size_t sz = sizeof(old_epoch); @@ -109,6 +243,89 @@ TEST_BEGIN(test_mallctl_read_write) { } TEST_END +TEST_BEGIN(test_mallctl_read_partial) { + uint64_t epoch; + size_t sz = sizeof(epoch); + + expect_d_eq(mallctl("epoch", (void *)&epoch, &sz, NULL, 0), 0, + "Unexpected mallctl() failure"); + expect_zu_eq(sz, sizeof(epoch), "Unexpected output size"); + + unsigned char misaligned[sizeof(epoch) + 1]; + memset(misaligned, 0, sizeof(misaligned)); + sz = sizeof(epoch); + expect_d_eq(mallctl("epoch", (void *)&misaligned[1], &sz, NULL, 0), 0, + "Unexpected mallctl() failure for misaligned output"); + expect_zu_eq(sz, sizeof(epoch), "Unexpected output size"); + expect_d_eq(memcmp(&epoch, &misaligned[1], sizeof(epoch)), 0, + "Unexpected value for misaligned output"); + + unsigned char short_buf[sizeof(epoch)]; + memset(short_buf, 0, sizeof(short_buf)); + sz = sizeof(epoch) - 1; + expect_d_eq(mallctl("epoch", (void *)short_buf, &sz, NULL, 0), EINVAL, + "Expected output size mismatch"); + expect_zu_eq(sz, sizeof(epoch) - 1, "Unexpected short output size"); + expect_d_eq(memcmp(&epoch, short_buf, sz), 0, + "Unexpected short partial output"); + + unsigned char long_buf[sizeof(epoch) + 1]; + memset(long_buf, 0xa5, sizeof(long_buf)); + sz = sizeof(epoch) + 1; + expect_d_eq(mallctl("epoch", (void *)long_buf, &sz, NULL, 0), EINVAL, + "Expected output size mismatch"); + expect_zu_eq(sz, sizeof(epoch), "Unexpected long output size"); + expect_d_eq(memcmp(&epoch, long_buf, sizeof(epoch)), 0, + "Unexpected long partial output"); + expect_u_eq(long_buf[sizeof(epoch)], 0xa5, + "Mallctl wrote past copied output"); +} +TEST_END + +TEST_BEGIN(test_tcache_create_errors) { + unsigned tcache_ind = 0; + size_t sz = sizeof(tcache_ind); + + /* A non-NULL newp is a write attempt on a read-only ctl: EPERM. */ + expect_d_eq(mallctl("tcache.create", (void *)&tcache_ind, &sz, + (void *)&tcache_ind, sizeof(tcache_ind)), + EPERM, "tcache.create should reject a write"); + + /* A nonzero newlen alone is still a write attempt: EPERM. */ + sz = sizeof(tcache_ind); + expect_d_eq(mallctl("tcache.create", (void *)&tcache_ind, &sz, NULL, + sizeof(tcache_ind)), + EPERM, "tcache.create should reject a nonzero newlen"); + + /* Missing output buffer: EINVAL with oldlenp zeroed. */ + sz = sizeof(tcache_ind); + expect_d_eq(mallctl("tcache.create", NULL, &sz, NULL, 0), EINVAL, + "tcache.create requires an output buffer"); + expect_zu_eq(sz, 0, "Unexpected oldlen after verify failure"); + + /* Undersized output buffer: EINVAL with oldlenp zeroed. */ + sz = sizeof(tcache_ind) - 1; + expect_d_eq(mallctl("tcache.create", (void *)&tcache_ind, &sz, NULL, 0), + EINVAL, "tcache.create should reject an undersized output"); + expect_zu_eq(sz, 0, "Unexpected oldlen after verify failure"); + + /* Oversized output buffer: EINVAL with oldlenp zeroed. */ + sz = sizeof(tcache_ind) + 1; + expect_d_eq(mallctl("tcache.create", (void *)&tcache_ind, &sz, NULL, 0), + EINVAL, "tcache.create should reject an oversized output"); + expect_zu_eq(sz, 0, "Unexpected oldlen after verify failure"); + + /* Valid read: succeeds, oldlen preserved, index is usable. */ + sz = sizeof(tcache_ind); + expect_d_eq(mallctl("tcache.create", (void *)&tcache_ind, &sz, NULL, 0), + 0, "Unexpected tcache.create failure"); + expect_zu_eq(sz, sizeof(tcache_ind), "Unexpected oldlen after success"); + expect_d_eq(mallctl("tcache.destroy", NULL, NULL, (void *)&tcache_ind, + sizeof(tcache_ind)), + 0, "Unexpected tcache.destroy failure"); +} +TEST_END + TEST_BEGIN(test_mallctlnametomib_short_mib) { size_t mib[4]; size_t miblen; @@ -556,6 +773,64 @@ TEST_BEGIN(test_thread_arena) { } TEST_END +TEST_BEGIN(test_thread_arena_bad_oldlen_no_migrate) { + unsigned original_arena_ind, new_arena_ind, after_arena_ind; + size_t sz = sizeof(unsigned); + + expect_d_eq(mallctl("thread.arena", (void *)&original_arena_ind, &sz, + NULL, 0), + 0, "Unexpected mallctl() failure"); + expect_zu_eq(sz, sizeof(original_arena_ind), "Unexpected output size"); + + sz = sizeof(new_arena_ind); + expect_d_eq(mallctl("arenas.create", (void *)&new_arena_ind, &sz, NULL, + 0), + 0, "Unexpected arenas.create failure"); + expect_zu_eq(sz, sizeof(new_arena_ind), "Unexpected output size"); + expect_u_ne(new_arena_ind, original_arena_ind, + "New arena unexpectedly matches current thread arena"); + + unsigned char short_buf[sizeof(original_arena_ind)]; + memset(short_buf, 0, sizeof(short_buf)); + sz = sizeof(original_arena_ind) - 1; + expect_d_eq(mallctl("thread.arena", (void *)short_buf, &sz, + (void *)&new_arena_ind, sizeof(new_arena_ind)), + EINVAL, "Expected output size mismatch"); + expect_zu_eq(sz, sizeof(original_arena_ind) - 1, + "Unexpected short output size"); + expect_d_eq(memcmp(&original_arena_ind, short_buf, sz), 0, + "Unexpected short partial output"); + + sz = sizeof(after_arena_ind); + expect_d_eq(mallctl("thread.arena", (void *)&after_arena_ind, &sz, NULL, + 0), + 0, "Unexpected mallctl() failure"); + expect_u_eq(after_arena_ind, original_arena_ind, + "Thread migrated despite short output buffer"); + + unsigned char long_buf[sizeof(original_arena_ind) + 1]; + memset(long_buf, 0xa5, sizeof(long_buf)); + sz = sizeof(original_arena_ind) + 1; + expect_d_eq(mallctl("thread.arena", (void *)long_buf, &sz, + (void *)&new_arena_ind, sizeof(new_arena_ind)), + EINVAL, "Expected output size mismatch"); + expect_zu_eq(sz, sizeof(original_arena_ind), + "Unexpected long output size"); + expect_d_eq(memcmp(&original_arena_ind, long_buf, + sizeof(original_arena_ind)), + 0, "Unexpected long partial output"); + expect_u_eq(long_buf[sizeof(original_arena_ind)], 0xa5, + "Mallctl wrote past copied output"); + + sz = sizeof(after_arena_ind); + expect_d_eq(mallctl("thread.arena", (void *)&after_arena_ind, &sz, NULL, + 0), + 0, "Unexpected mallctl() failure"); + expect_u_eq(after_arena_ind, original_arena_ind, + "Thread migrated despite long output buffer"); +} +TEST_END + TEST_BEGIN(test_arena_i_initialized) { unsigned narenas, i; size_t sz; @@ -595,6 +870,472 @@ TEST_BEGIN(test_arena_i_initialized) { } TEST_END +TEST_BEGIN(test_arena_i_initialized_errors) { + bool initialized = false; + unsigned char buf[sizeof(bool) + 1]; + size_t sz; + + /* Write attempt on a read-only ctl: EPERM. */ + sz = sizeof(initialized); + expect_d_eq(mallctl("arena.0.initialized", (void *)&initialized, &sz, + (void *)&initialized, sizeof(initialized)), + EPERM, "arena.i.initialized should reject a write"); + + /* Undersized output buffer: EINVAL with oldlenp truncated. */ + sz = 0; + expect_d_eq(mallctl("arena.0.initialized", (void *)buf, &sz, NULL, 0), + EINVAL, "Expected output size mismatch"); + expect_zu_eq(sz, 0, "Unexpected truncated oldlen"); + + /* Oversized output buffer: EINVAL, only sizeof(bool) reported/copied. */ + memset(buf, 0xa5, sizeof(buf)); + sz = sizeof(bool) + 1; + expect_d_eq(mallctl("arena.0.initialized", (void *)buf, &sz, NULL, 0), + EINVAL, "Expected output size mismatch"); + expect_zu_eq(sz, sizeof(bool), "Unexpected truncated oldlen"); + expect_u_eq(buf[sizeof(bool)], 0xa5, + "Mallctl wrote past copied output"); +} +TEST_END + +TEST_BEGIN(test_thread_tcache_enabled_errors) { + bool enabled, prev; + size_t sz = sizeof(bool); + + /* Capture current state so we can restore it at the end. */ + expect_d_eq( + mallctl("thread.tcache.enabled", (void *)&prev, &sz, NULL, 0), 0, + "Unexpected mallctl() failure"); + + /* (1) Write with wrong newlen: EINVAL, and the state is unchanged. */ + bool newval = !prev; + expect_d_eq(mallctl("thread.tcache.enabled", NULL, NULL, + (void *)&newval, sizeof(bool) + 1), + EINVAL, "Expected input size mismatch"); + sz = sizeof(bool); + expect_d_eq( + mallctl("thread.tcache.enabled", (void *)&enabled, &sz, NULL, 0), 0, + "Unexpected mallctl() failure"); + expect_b_eq(enabled, prev, + "Enabled state should be unchanged after a failed write"); + + /* (2) Pure read, undersized oldlen: EINVAL with oldlenp truncated. */ + unsigned char buf[sizeof(bool) + 1]; + memset(buf, 0xa5, sizeof(buf)); + sz = 0; + expect_d_eq(mallctl("thread.tcache.enabled", (void *)buf, &sz, NULL, 0), + EINVAL, "Expected output size mismatch"); + expect_zu_eq(sz, 0, "Unexpected truncated oldlen"); + + /* (2) Pure read, oversized oldlen: EINVAL, copies sizeof(bool) only. */ + sz = sizeof(bool) + 1; + expect_d_eq(mallctl("thread.tcache.enabled", (void *)buf, &sz, NULL, 0), + EINVAL, "Expected output size mismatch"); + expect_zu_eq(sz, sizeof(bool), "Unexpected truncated oldlen"); + expect_u_eq(buf[sizeof(bool)], 0xa5, "Mallctl wrote past copied output"); + + /* (3) Valid newlen but bad oldlen: EINVAL, yet the write still applies. */ + sz = sizeof(bool) + 1; + newval = !prev; + expect_d_eq(mallctl("thread.tcache.enabled", (void *)buf, &sz, + (void *)&newval, sizeof(bool)), + EINVAL, "Expected output size mismatch"); + sz = sizeof(bool); + expect_d_eq( + mallctl("thread.tcache.enabled", (void *)&enabled, &sz, NULL, 0), 0, + "Unexpected mallctl() failure"); + expect_b_eq(enabled, newval, + "A valid write should take effect even when read-out fails"); + + /* Restore the original state. */ + expect_d_eq(mallctl("thread.tcache.enabled", NULL, NULL, (void *)&prev, + sizeof(bool)), + 0, "Unexpected mallctl() failure"); +} +TEST_END + +TEST_BEGIN(test_thread_peak_read_errors) { + test_skip_if(!config_stats); + + uint64_t peak = 0; + size_t sz = sizeof(peak); + + /* Write attempt on a read-only ctl: EPERM. */ + expect_d_eq(mallctl("thread.peak.read", (void *)&peak, &sz, + (void *)&peak, sizeof(peak)), + EPERM, "thread.peak.read should reject a write"); + + /* Undersized output buffer: EINVAL, copies sizeof - 1, no overrun. */ + unsigned char buf[sizeof(uint64_t) + 1]; + memset(buf, 0xa5, sizeof(buf)); + sz = sizeof(uint64_t) - 1; + expect_d_eq(mallctl("thread.peak.read", (void *)buf, &sz, NULL, 0), + EINVAL, "Expected output size mismatch"); + expect_zu_eq(sz, sizeof(uint64_t) - 1, "Unexpected truncated oldlen"); + expect_u_eq(buf[sizeof(uint64_t) - 1], 0xa5, + "Mallctl wrote past requested output"); + + /* Oversized output buffer: EINVAL, copies sizeof only, no overrun. */ + memset(buf, 0xa5, sizeof(buf)); + sz = sizeof(uint64_t) + 1; + expect_d_eq(mallctl("thread.peak.read", (void *)buf, &sz, NULL, 0), + EINVAL, "Expected output size mismatch"); + expect_zu_eq(sz, sizeof(uint64_t), "Unexpected truncated oldlen"); + expect_u_eq(buf[sizeof(uint64_t)], 0xa5, + "Mallctl wrote past copied output"); +} +TEST_END + +/* + * Shared error-path checks for a scalar read+write ctl: a wrong newlen is + * rejected with EINVAL without changing the value, and a wrong oldlen on read + * truncates oldlenp to the number of bytes copied. + */ +static void +expect_scalar_rw_errors(const char *name, size_t valsz) { + unsigned char buf[16]; + unsigned char saved[16]; + size_t sz; + + sz = valsz; + expect_d_eq(mallctl(name, (void *)saved, &sz, NULL, 0), 0, + "Unexpected read failure for %s", name); + expect_zu_eq(sz, valsz, "Unexpected output size for %s", name); + + /* Wrong newlen: EINVAL, value unchanged. */ + expect_d_eq(mallctl(name, NULL, NULL, (void *)saved, valsz + 1), EINVAL, + "Expected input size mismatch for %s", name); + memset(buf, 0, sizeof(buf)); + sz = valsz; + expect_d_eq(mallctl(name, (void *)buf, &sz, NULL, 0), 0, + "Unexpected read failure for %s", name); + expect_d_eq(memcmp(buf, saved, valsz), 0, + "%s changed after a failed write", name); + + /* Wrong oldlen on read: EINVAL, oldlenp truncated to the copied len. */ + sz = valsz + 1; + expect_d_eq(mallctl(name, (void *)buf, &sz, NULL, 0), EINVAL, + "Expected output size mismatch for %s", name); + expect_zu_eq(sz, valsz, "Unexpected truncated oldlen for %s", name); +} + +TEST_BEGIN(test_arenas_narenas_errors) { + unsigned narenas; + size_t sz = sizeof(narenas); + + expect_d_eq(mallctl("arenas.narenas", (void *)&narenas, &sz, + (void *)&narenas, sizeof(narenas)), + EPERM, "arenas.narenas should reject a write"); + + sz = sizeof(narenas) + 1; + expect_d_eq(mallctl("arenas.narenas", (void *)&narenas, &sz, NULL, 0), + EINVAL, "Expected output size mismatch"); + expect_zu_eq(sz, sizeof(narenas), "Unexpected truncated oldlen"); +} +TEST_END + +TEST_BEGIN(test_approximate_stats_active_errors) { + size_t active; + size_t sz = sizeof(active); + + expect_d_eq(mallctl("approximate_stats.active", (void *)&active, &sz, + (void *)&active, sizeof(active)), + EPERM, "approximate_stats.active should reject a write"); + + sz = sizeof(active) + 1; + expect_d_eq( + mallctl("approximate_stats.active", (void *)&active, &sz, NULL, 0), + EINVAL, "Expected output size mismatch"); + expect_zu_eq(sz, sizeof(active), "Unexpected truncated oldlen"); +} +TEST_END + +TEST_BEGIN(test_decay_ms_oversize_errors) { + expect_scalar_rw_errors("arena.0.dirty_decay_ms", sizeof(ssize_t)); + expect_scalar_rw_errors("arena.0.muzzy_decay_ms", sizeof(ssize_t)); + expect_scalar_rw_errors("arenas.dirty_decay_ms", sizeof(ssize_t)); + expect_scalar_rw_errors("arenas.muzzy_decay_ms", sizeof(ssize_t)); + expect_scalar_rw_errors("arena.0.oversize_threshold", sizeof(size_t)); +} +TEST_END + +TEST_BEGIN(test_background_thread_errors) { + bool enabled; + size_t sz = sizeof(enabled); + + /* Skip when background threads are unavailable in this build. */ + test_skip_if( + mallctl("background_thread", (void *)&enabled, &sz, NULL, 0) != 0); + + /* Wrong newlen: EINVAL, enabled state unchanged. */ + bool toggled = !enabled; + expect_d_eq(mallctl("background_thread", NULL, NULL, (void *)&toggled, + sizeof(bool) + 1), + EINVAL, "Expected input size mismatch"); + bool again; + sz = sizeof(again); + expect_d_eq(mallctl("background_thread", (void *)&again, &sz, NULL, 0), + 0, "Unexpected read failure"); + expect_b_eq(again, enabled, + "background_thread changed after a failed write"); + + /* max_background_threads: wrong newlen -> EINVAL. */ + size_t maxbt; + sz = sizeof(maxbt); + expect_d_eq( + mallctl("max_background_threads", (void *)&maxbt, &sz, NULL, 0), 0, + "Unexpected read failure"); + expect_d_eq(mallctl("max_background_threads", NULL, NULL, (void *)&maxbt, + sizeof(size_t) + 1), + EINVAL, "Expected input size mismatch"); +} +TEST_END + +TEST_BEGIN(test_prof_toggle_errors) { + bool prof_enabled; + size_t sz = sizeof(prof_enabled); + test_skip_if( + mallctl("opt.prof", (void *)&prof_enabled, &sz, NULL, 0) != 0 + || !prof_enabled); + + const char *names[] = {"prof.active", "prof.gdump", + "prof.thread_active_init", "thread.prof.active"}; + for (unsigned i = 0; i < sizeof(names) / sizeof(names[0]); i++) { + bool cur; + size_t s = sizeof(cur); + expect_d_eq(mallctl(names[i], (void *)&cur, &s, NULL, 0), 0, + "Unexpected read failure for %s", names[i]); + expect_d_eq(mallctl(names[i], NULL, NULL, (void *)&cur, + sizeof(bool) + 1), + EINVAL, "Expected input size mismatch for %s", names[i]); + bool after; + s = sizeof(after); + expect_d_eq(mallctl(names[i], (void *)&after, &s, NULL, 0), 0, + "Unexpected read failure for %s", names[i]); + expect_b_eq(after, cur, "%s changed after a failed write", + names[i]); + } +} +TEST_END + +TEST_BEGIN(test_experimental_prof_recent_alloc_max_errors) { + bool prof_enabled; + size_t sz = sizeof(prof_enabled); + test_skip_if( + mallctl("opt.prof", (void *)&prof_enabled, &sz, NULL, 0) != 0 + || !prof_enabled); + + const char *name = "experimental.prof_recent.alloc_max"; + ssize_t orig; + sz = sizeof(orig); + expect_d_eq(mallctl(name, (void *)&orig, &sz, NULL, 0), 0, + "Unexpected read failure"); + + /* Wrong newlen: EINVAL. */ + expect_d_eq( + mallctl(name, NULL, NULL, (void *)&orig, sizeof(ssize_t) + 1), + EINVAL, "Expected input size mismatch"); + + /* Out-of-range value (< -1): EINVAL. */ + ssize_t bad = -2; + expect_d_eq(mallctl(name, NULL, NULL, (void *)&bad, sizeof(bad)), EINVAL, + "Expected EINVAL for max < -1"); + + /* Wrong oldlen on read: EINVAL with oldlenp truncated. */ + ssize_t scratch; + sz = sizeof(ssize_t) + 1; + expect_d_eq(mallctl(name, (void *)&scratch, &sz, NULL, 0), EINVAL, + "Expected output size mismatch"); + expect_zu_eq(sz, sizeof(ssize_t), "Unexpected truncated oldlen"); + + /* The failed writes left the value unchanged. */ + ssize_t after; + sz = sizeof(after); + expect_d_eq(mallctl(name, (void *)&after, &sz, NULL, 0), 0, + "Unexpected read failure"); + expect_zd_eq(after, orig, "alloc_max changed after failed writes"); +} +TEST_END + +TEST_BEGIN(test_prof_stats_errors) { + bool b; + size_t sz = sizeof(b); + test_skip_if(mallctl("opt.prof", (void *)&b, &sz, NULL, 0) != 0 || !b); + sz = sizeof(b); + test_skip_if( + mallctl("opt.prof_stats", (void *)&b, &sz, NULL, 0) != 0 || !b); + + const char *names[] = {"prof.stats.bins.0.live", + "prof.stats.bins.0.accum", "prof.stats.lextents.0.live", + "prof.stats.lextents.0.accum"}; + for (unsigned i = 0; i < sizeof(names) / sizeof(names[0]); i++) { + unsigned char buf[8]; + size_t s = sizeof(buf); + + /* Read-only: a write is rejected before any size check. */ + expect_d_eq(mallctl(names[i], (void *)buf, &s, (void *)buf, + sizeof(buf)), + EPERM, "%s should reject a write", names[i]); + + /* Size mismatch on read: EINVAL, oldlenp = copied length. */ + s = 1; + expect_d_eq(mallctl(names[i], (void *)buf, &s, NULL, 0), EINVAL, + "Expected output size mismatch for %s", names[i]); + expect_zu_eq(s, 1, "Unexpected truncated oldlen for %s", + names[i]); + } +} +TEST_END + +TEST_BEGIN(test_thread_tcache_flush_errors) { + /* + * A successful flush both confirms tcache is available (so the EPERM + * path below is reached rather than EFAULT) and covers the happy path. + */ + test_skip_if(mallctl("thread.tcache.flush", NULL, NULL, NULL, 0) != 0); + + unsigned val = 0; + size_t sz = sizeof(val); + + /* Command ctl: a read attempt is rejected with EPERM. */ + expect_d_eq(mallctl("thread.tcache.flush", (void *)&val, &sz, NULL, 0), + EPERM, "thread.tcache.flush should reject a read"); + + /* A write attempt is rejected with EPERM. */ + expect_d_eq(mallctl("thread.tcache.flush", NULL, NULL, (void *)&val, + sizeof(val)), + EPERM, "thread.tcache.flush should reject a write"); +} +TEST_END + +TEST_BEGIN(test_thread_peak_reset_errors) { + test_skip_if(!config_stats); + + unsigned val = 0; + size_t sz = sizeof(val); + + /* Command ctl: a read attempt is rejected with EPERM. */ + expect_d_eq(mallctl("thread.peak.reset", (void *)&val, &sz, NULL, 0), + EPERM, "thread.peak.reset should reject a read"); + + /* A write attempt is rejected with EPERM. */ + expect_d_eq(mallctl("thread.peak.reset", NULL, NULL, (void *)&val, + sizeof(val)), + EPERM, "thread.peak.reset should reject a write"); +} +TEST_END + +TEST_BEGIN(test_thread_idle_errors) { + unsigned val = 0; + size_t sz = sizeof(val); + + /* Command ctl: a read attempt is rejected with EPERM. */ + expect_d_eq(mallctl("thread.idle", (void *)&val, &sz, NULL, 0), EPERM, + "thread.idle should reject a read"); + + /* A write attempt is rejected with EPERM. */ + expect_d_eq( + mallctl("thread.idle", NULL, NULL, (void *)&val, sizeof(val)), + EPERM, "thread.idle should reject a write"); +} +TEST_END + +TEST_BEGIN(test_arena_i_decay_errors) { + unsigned val = 0; + size_t sz = sizeof(val); + + /* Command ctl: a read attempt is rejected with EPERM. */ + expect_d_eq(mallctl("arena.0.decay", (void *)&val, &sz, NULL, 0), EPERM, + "arena.i.decay should reject a read"); + + /* A write attempt is rejected with EPERM. */ + expect_d_eq( + mallctl("arena.0.decay", NULL, NULL, (void *)&val, sizeof(val)), + EPERM, "arena.i.decay should reject a write"); +} +TEST_END + +TEST_BEGIN(test_arena_i_purge_errors) { + unsigned val = 0; + size_t sz = sizeof(val); + + /* Command ctl: a read attempt is rejected with EPERM. */ + expect_d_eq(mallctl("arena.0.purge", (void *)&val, &sz, NULL, 0), EPERM, + "arena.i.purge should reject a read"); + + /* A write attempt is rejected with EPERM. */ + expect_d_eq( + mallctl("arena.0.purge", NULL, NULL, (void *)&val, sizeof(val)), + EPERM, "arena.i.purge should reject a write"); +} +TEST_END + +TEST_BEGIN(test_arena_i_reset_destroy_errors) { + unsigned val = 0; + size_t sz = sizeof(val); + + /* + * reset/destroy are command ctls sharing one helper that rejects any + * I/O with EPERM before touching the arena, so this does not actually + * reset or destroy arena 0. + */ + expect_d_eq(mallctl("arena.0.reset", (void *)&val, &sz, NULL, 0), EPERM, + "arena.i.reset should reject a read"); + expect_d_eq( + mallctl("arena.0.reset", NULL, NULL, (void *)&val, sizeof(val)), + EPERM, "arena.i.reset should reject a write"); + expect_d_eq(mallctl("arena.0.destroy", (void *)&val, &sz, NULL, 0), + EPERM, "arena.i.destroy should reject a read"); + expect_d_eq( + mallctl("arena.0.destroy", NULL, NULL, (void *)&val, sizeof(val)), + EPERM, "arena.i.destroy should reject a write"); +} +TEST_END + +TEST_BEGIN(test_thread_prof_name_errors) { + bool prof_enabled; + size_t sz = sizeof(prof_enabled); + test_skip_if( + mallctl("opt.prof", (void *)&prof_enabled, &sz, NULL, 0) != 0 + || !prof_enabled); + + /* + * thread.prof.name takes a (const char *); a wrong newlen is rejected + * with EINVAL. (EPERM on read+write, NULL input, and the read path are + * covered by test/unit/prof_thread_name.c.) + */ + const char *name = "x"; + expect_d_eq(mallctl("thread.prof.name", NULL, NULL, (void *)&name, + sizeof(name) + 1), + EINVAL, "thread.prof.name should reject a wrong newlen"); +} +TEST_END + +TEST_BEGIN(test_ctl_ro_macro_errors) { + /* config.debug: a CTL_RO_CONFIG_GEN getter (read-only bool). */ + bool dbg; + size_t sz = sizeof(dbg); + expect_d_eq(mallctl("config.debug", (void *)&dbg, &sz, (void *)&dbg, + sizeof(dbg)), + EPERM, "config.debug should reject a write"); + sz = sizeof(dbg) + 1; + expect_d_eq(mallctl("config.debug", (void *)&dbg, &sz, NULL, 0), EINVAL, + "Expected output size mismatch"); + expect_zu_eq(sz, sizeof(dbg), "Unexpected truncated oldlen"); + + /* arenas.quantum: a CTL_RO_NL_GEN getter (read-only size_t). */ + size_t q; + sz = sizeof(q); + expect_d_eq( + mallctl("arenas.quantum", (void *)&q, &sz, (void *)&q, sizeof(q)), + EPERM, "arenas.quantum should reject a write"); + sz = sizeof(q) + 1; + expect_d_eq(mallctl("arenas.quantum", (void *)&q, &sz, NULL, 0), EINVAL, + "Expected output size mismatch"); + expect_zu_eq(sz, sizeof(q), "Unexpected truncated oldlen"); +} +TEST_END + TEST_BEGIN(test_arena_i_dirty_decay_ms) { ssize_t dirty_decay_ms, orig_dirty_decay_ms, prev_dirty_decay_ms; size_t sz = sizeof(ssize_t); @@ -734,6 +1475,31 @@ TEST_BEGIN(test_arena_i_dss) { expect_str_ne( dss_prec_old, "primary", "Unexpected value for dss precedence"); + const char *dss_prec_invalid = "invalid"; + expect_d_eq(mallctlbymib(mib, miblen, NULL, NULL, + (void *)&dss_prec_invalid, sizeof(dss_prec_invalid)), + EINVAL, "Expected invalid dss precedence failure"); + + const char *dss_prec_current; + sz = sizeof(dss_prec_current); + expect_d_eq(mallctlbymib(mib, miblen, (void *)&dss_prec_current, &sz, + NULL, 0), + 0, "Unexpected mallctl() failure"); + expect_str_eq(dss_prec_current, dss_prec_old, + "Invalid dss precedence changed current value"); + + dss_prec_new = "disabled"; + expect_d_eq(mallctlbymib(mib, miblen, NULL, NULL, + (void *)&dss_prec_new, sizeof(dss_prec_new) - 1), + EINVAL, "Expected dss precedence input size mismatch"); + + sz = sizeof(dss_prec_current); + expect_d_eq(mallctlbymib(mib, miblen, (void *)&dss_prec_current, &sz, + NULL, 0), + 0, "Unexpected mallctl() failure"); + expect_str_eq(dss_prec_current, dss_prec_old, + "Bad dss precedence input size changed current value"); + mib[1] = narenas_total_get(); dss_prec_new = "disabled"; expect_d_eq(mallctlbymib(mib, miblen, (void *)&dss_prec_old, &sz, @@ -794,6 +1560,48 @@ TEST_BEGIN(test_arena_i_name) { 0, "Unexpected mallctl() failure"); int cmp = strncmp(name_old, super_long_name, ARENA_NAME_LEN - 1); expect_true(cmp == 0, "Unexpected value for long arena name "); + + const char *name_preserved = "preserved name"; + expect_d_eq(mallctlbymib(mib, miblen, NULL, NULL, + (void *)&name_preserved, sizeof(name_preserved)), 0, + "Unexpected mallctl() failure"); + + const char *name_bad_oldlen = "bad oldlen update"; + sz = sizeof(name_oldp) - 1; + memset(name_old, 'x', sizeof(name_old)); + expect_d_eq(mallctlbymib(mib, miblen, (void *)&name_oldp, &sz, + (void *)&name_bad_oldlen, sizeof(name_bad_oldlen)), EINVAL, + "Unexpected mallctl() success"); + expect_zu_eq(sz, sizeof(name_oldp) - 1, + "Unexpected oldlen update"); + expect_c_eq(name_old[0], 'x', "Unexpected output write"); + + sz = sizeof(name_oldp); + expect_d_eq(mallctlbymib(mib, miblen, (void *)&name_oldp, &sz, NULL, + 0), 0, "Unexpected mallctl() failure"); + expect_str_eq(name_old, name_preserved, + "Malformed oldlen write should not change arena name"); + + const char *name_bad_newlen = "bad newlen update"; + expect_d_eq(mallctlbymib(mib, miblen, NULL, NULL, + (void *)&name_bad_newlen, sizeof(name_bad_newlen) - 1), EINVAL, + "Unexpected mallctl() success"); + + sz = sizeof(name_oldp); + expect_d_eq(mallctlbymib(mib, miblen, (void *)&name_oldp, &sz, NULL, + 0), 0, "Unexpected mallctl() failure"); + expect_str_eq(name_old, name_preserved, + "Malformed newlen write should not change arena name"); + + char *name_null = NULL; + expect_d_eq(mallctlbymib(mib, miblen, NULL, NULL, (void *)&name_null, + sizeof(name_null)), EINVAL, "Unexpected mallctl() success"); + + sz = sizeof(name_oldp); + expect_d_eq(mallctlbymib(mib, miblen, (void *)&name_oldp, &sz, NULL, + 0), 0, "Unexpected mallctl() failure"); + expect_str_eq(name_old, name_preserved, + "Null name write should not change arena name"); } TEST_END @@ -1087,6 +1895,72 @@ TEST_BEGIN(test_arenas_create) { expect_u_eq(narenas_before + 1, narenas_after, "Unexpected number of arenas before versus after extension"); expect_u_eq(arena, narenas_after - 1, "Unexpected arena index"); + + sz = sizeof(narenas_before); + expect_d_eq( + mallctl("arenas.narenas", (void *)&narenas_before, &sz, NULL, 0), 0, + "Unexpected mallctl() failure"); + + sz = sizeof(arena) - 1; + expect_d_eq(mallctl("arenas.create", (void *)&arena, &sz, NULL, 0), + EINVAL, "Unexpected mallctl() success"); + expect_zu_eq(sz, 0, "Unexpected oldlen update"); + + sz = sizeof(narenas_after); + expect_d_eq( + mallctl("arenas.narenas", (void *)&narenas_after, &sz, NULL, 0), 0, + "Unexpected mallctl() failure"); + expect_u_eq(narenas_before, narenas_after, + "Malformed oldlen should not create an arena"); + + extent_hooks_t *hooks = NULL; + sz = sizeof(arena); + expect_d_eq(mallctl("arenas.create", (void *)&arena, &sz, + (void *)&hooks, sizeof(hooks) - 1), EINVAL, + "Unexpected mallctl() success"); + + sz = sizeof(narenas_after); + expect_d_eq( + mallctl("arenas.narenas", (void *)&narenas_after, &sz, NULL, 0), 0, + "Unexpected mallctl() failure"); + expect_u_eq(narenas_before, narenas_after, + "Malformed newlen should not create an arena"); +} +TEST_END + +TEST_BEGIN(test_experimental_arenas_create_ext_errors) { + unsigned narenas_before, arena, narenas_after; + size_t sz = sizeof(unsigned); + arena_config_t config = arena_config_default; + + expect_d_eq( + mallctl("arenas.narenas", (void *)&narenas_before, &sz, NULL, 0), 0, + "Unexpected mallctl() failure"); + + sz = sizeof(arena) - 1; + expect_d_eq(mallctl("experimental.arenas_create_ext", (void *)&arena, + &sz, (void *)&config, sizeof(config)), EINVAL, + "Unexpected mallctl() success"); + expect_zu_eq(sz, 0, "Unexpected oldlen update"); + + sz = sizeof(narenas_after); + expect_d_eq( + mallctl("arenas.narenas", (void *)&narenas_after, &sz, NULL, 0), 0, + "Unexpected mallctl() failure"); + expect_u_eq(narenas_before, narenas_after, + "Malformed oldlen should not create an arena"); + + sz = sizeof(arena); + expect_d_eq(mallctl("experimental.arenas_create_ext", (void *)&arena, + &sz, (void *)&config, sizeof(config) - 1), EINVAL, + "Unexpected mallctl() success"); + + sz = sizeof(narenas_after); + expect_d_eq( + mallctl("arenas.narenas", (void *)&narenas_after, &sz, NULL, 0), 0, + "Unexpected mallctl() failure"); + expect_u_eq(narenas_before, narenas_after, + "Malformed newlen should not create an arena"); } TEST_END @@ -1102,6 +1976,37 @@ TEST_BEGIN(test_arenas_lookup) { expect_d_eq(mallctl("arenas.lookup", &arena1, &sz, &ptr, sizeof(ptr)), 0, "Unexpected mallctl() failure"); expect_u_eq(arena, arena1, "Unexpected arena index"); + + expect_d_eq( + mallctl("arenas.lookup", &arena1, &sz, &ptr, sizeof(ptr) - 1), + EINVAL, "Unexpected mallctl() success"); + + int stack_value; + void *stack_ptr = &stack_value; + expect_d_eq(mallctl("arenas.lookup", &arena1, &sz, &stack_ptr, + sizeof(stack_ptr)), EINVAL, "Unexpected mallctl() success"); + + unsigned char short_old[sizeof(unsigned)]; + memset(short_old, 0, sizeof(short_old)); + sz = sizeof(unsigned) - 1; + expect_d_eq(mallctl("arenas.lookup", short_old, &sz, &ptr, sizeof(ptr)), + EINVAL, "Unexpected mallctl() success"); + expect_zu_eq(sz, sizeof(unsigned) - 1, "Unexpected oldlen update"); + expect_d_eq(memcmp(short_old, &arena, sizeof(unsigned) - 1), 0, + "Unexpected partial arena index"); + + unsigned char long_old[sizeof(unsigned) + 1]; + memset(long_old, 0, sizeof(long_old)); + long_old[sizeof(unsigned)] = 0x5a; + sz = sizeof(long_old); + expect_d_eq(mallctl("arenas.lookup", long_old, &sz, &ptr, sizeof(ptr)), + EINVAL, "Unexpected mallctl() success"); + expect_zu_eq(sz, sizeof(unsigned), "Unexpected oldlen update"); + expect_d_eq(memcmp(long_old, &arena, sizeof(unsigned)), 0, + "Unexpected partial arena index"); + expect_u_eq(long_old[sizeof(unsigned)], 0x5a, + "Unexpected write past arena index"); + dallocx(ptr, 0); } TEST_END @@ -1394,20 +2299,38 @@ TEST_END int main(void) { return test(test_mallctl_errors, test_mallctlnametomib_errors, - test_mallctlbymib_errors, test_mallctl_read_write, + test_mallctlbymib_errors, test_ctl_mib_unsigned, + test_ctl_verify_read, test_ctl_readonly, + test_ctl_neither_read_nor_write, test_ctl_read_xor_write, + test_mallctl_read_write, + test_mallctl_read_partial, test_tcache_create_errors, test_mallctlnametomib_short_mib, test_mallctlnametomib_short_name, test_mallctlmibnametomib, test_mallctlbymibname, test_mallctl_config, test_mallctl_opt, test_manpage_example, test_tcache_none, test_tcache, test_thread_arena, - test_arena_i_initialized, test_arena_i_dirty_decay_ms, - test_arena_i_muzzy_decay_ms, test_arena_i_purge, test_arena_i_decay, - test_arena_i_dss, test_arena_i_name, test_arena_i_retain_grow_limit, + test_thread_arena_bad_oldlen_no_migrate, test_arena_i_initialized, + test_arena_i_initialized_errors, + test_thread_tcache_enabled_errors, + test_thread_peak_read_errors, + test_arenas_narenas_errors, test_approximate_stats_active_errors, + test_decay_ms_oversize_errors, test_background_thread_errors, + test_prof_toggle_errors, + test_experimental_prof_recent_alloc_max_errors, + test_prof_stats_errors, test_thread_tcache_flush_errors, + test_thread_peak_reset_errors, test_thread_idle_errors, + test_arena_i_decay_errors, test_arena_i_purge_errors, + test_arena_i_reset_destroy_errors, test_thread_prof_name_errors, + test_ctl_ro_macro_errors, + test_arena_i_dirty_decay_ms, test_arena_i_muzzy_decay_ms, + test_arena_i_purge, test_arena_i_decay, test_arena_i_dss, + test_arena_i_name, test_arena_i_retain_grow_limit, test_arenas_dirty_decay_ms, test_arenas_muzzy_decay_ms, test_arenas_constants, test_arenas_bin_constants, test_arenas_bin_oob, test_arenas_lextent_oob, test_stats_arenas_bins_oob, test_stats_arenas_lextents_oob, test_arenas_lextent_constants, test_arenas_create, - test_arenas_lookup, test_prof_active, test_stats_arenas, + test_experimental_arenas_create_ext_errors, test_arenas_lookup, + test_prof_active, test_stats_arenas, test_stats_arenas_hpa_shard_counters, test_stats_arenas_hpa_shard_slabs, test_thread_idle, test_thread_peak, test_thread_event_hook); diff --git a/test/unit/prof_hook.c b/test/unit/prof_hook.c index 8217f309..3c139810 100644 --- a/test/unit/prof_hook.c +++ b/test/unit/prof_hook.c @@ -108,6 +108,32 @@ TEST_BEGIN(test_prof_backtrace_hook_replace) { prof_backtrace_hook_t current_hook; size_t current_hook_sz = sizeof(prof_backtrace_hook_t); + prof_backtrace_hook_t attempted_hook = &mock_bt_augmenting_hook; + + current_hook_sz = sizeof(prof_backtrace_hook_t) - 1; + expect_d_eq(mallctl("experimental.hooks.prof_backtrace", + (void *)¤t_hook, ¤t_hook_sz, + (void *)&attempted_hook, sizeof(attempted_hook)), + EINVAL, "Unexpected mallctl success with malformed oldlen"); + + current_hook_sz = sizeof(prof_backtrace_hook_t); + expect_d_eq(mallctl("experimental.hooks.prof_backtrace", + (void *)¤t_hook, ¤t_hook_sz, NULL, 0), + 0, "Unexpected mallctl failure reading hook"); + expect_ptr_eq(current_hook, hook, + "Malformed oldlen should not update hook"); + + expect_d_eq(mallctl("experimental.hooks.prof_backtrace", NULL, NULL, + (void *)&attempted_hook, sizeof(attempted_hook) - 1), + EINVAL, "Unexpected mallctl success with malformed newlen"); + + current_hook_sz = sizeof(prof_backtrace_hook_t); + expect_d_eq(mallctl("experimental.hooks.prof_backtrace", + (void *)¤t_hook, ¤t_hook_sz, NULL, 0), + 0, "Unexpected mallctl failure reading hook"); + expect_ptr_eq(current_hook, hook, + "Malformed newlen should not update hook"); + expect_d_eq(mallctl("experimental.hooks.prof_backtrace", (void *)¤t_hook, ¤t_hook_sz, (void *)&default_bt_hook, sizeof(default_bt_hook)), @@ -185,6 +211,32 @@ TEST_BEGIN(test_prof_dump_hook) { prof_dump_hook_t current_hook; size_t current_hook_sz = sizeof(prof_dump_hook_t); + prof_dump_hook_t attempted_hook = NULL; + + current_hook_sz = sizeof(prof_dump_hook_t) - 1; + expect_d_eq(mallctl("experimental.hooks.prof_dump", + (void *)¤t_hook, ¤t_hook_sz, + (void *)&attempted_hook, sizeof(attempted_hook)), + EINVAL, "Unexpected mallctl success with malformed oldlen"); + + current_hook_sz = sizeof(prof_dump_hook_t); + expect_d_eq(mallctl("experimental.hooks.prof_dump", + (void *)¤t_hook, ¤t_hook_sz, NULL, 0), + 0, "Unexpected mallctl failure reading hook"); + expect_ptr_eq(current_hook, hook, + "Malformed oldlen should not update hook"); + + expect_d_eq(mallctl("experimental.hooks.prof_dump", NULL, NULL, + (void *)&attempted_hook, sizeof(attempted_hook) - 1), + EINVAL, "Unexpected mallctl success with malformed newlen"); + + current_hook_sz = sizeof(prof_dump_hook_t); + expect_d_eq(mallctl("experimental.hooks.prof_dump", + (void *)¤t_hook, ¤t_hook_sz, NULL, 0), + 0, "Unexpected mallctl failure reading hook"); + expect_ptr_eq(current_hook, hook, + "Malformed newlen should not update hook"); + expect_d_eq(mallctl("experimental.hooks.prof_dump", (void *)¤t_hook, ¤t_hook_sz, (void *)&default_bt_hook, sizeof(default_bt_hook)), @@ -304,6 +356,42 @@ TEST_BEGIN(test_prof_sample_hooks) { write_prof_sample_free_hook(mock_prof_sample_free_hook); check_prof_sample_hooks(true, true); + prof_sample_hook_t attempted_sample_hook = NULL; + prof_sample_hook_t sample_hook; + size_t sample_hook_sz = sizeof(prof_sample_hook_t) - 1; + expect_d_eq(mallctl("experimental.hooks.prof_sample", + (void *)&sample_hook, &sample_hook_sz, + (void *)&attempted_sample_hook, + sizeof(attempted_sample_hook)), + EINVAL, "Unexpected mallctl success with malformed oldlen"); + expect_ptr_eq(read_prof_sample_hook(), mock_prof_sample_hook, + "Malformed oldlen should not update prof_sample hook"); + + expect_d_eq(mallctl("experimental.hooks.prof_sample", NULL, NULL, + (void *)&attempted_sample_hook, + sizeof(attempted_sample_hook) - 1), + EINVAL, "Unexpected mallctl success with malformed newlen"); + expect_ptr_eq(read_prof_sample_hook(), mock_prof_sample_hook, + "Malformed newlen should not update prof_sample hook"); + + prof_sample_free_hook_t attempted_sample_free_hook = NULL; + prof_sample_free_hook_t sample_free_hook; + size_t sample_free_hook_sz = sizeof(prof_sample_free_hook_t) - 1; + expect_d_eq(mallctl("experimental.hooks.prof_sample_free", + (void *)&sample_free_hook, &sample_free_hook_sz, + (void *)&attempted_sample_free_hook, + sizeof(attempted_sample_free_hook)), + EINVAL, "Unexpected mallctl success with malformed oldlen"); + expect_ptr_eq(read_prof_sample_free_hook(), mock_prof_sample_free_hook, + "Malformed oldlen should not update prof_sample_free hook"); + + expect_d_eq(mallctl("experimental.hooks.prof_sample_free", NULL, NULL, + (void *)&attempted_sample_free_hook, + sizeof(attempted_sample_free_hook) - 1), + EINVAL, "Unexpected mallctl success with malformed newlen"); + expect_ptr_eq(read_prof_sample_free_hook(), mock_prof_sample_free_hook, + "Malformed newlen should not update prof_sample_free hook"); + write_prof_sample_hook(NULL); check_prof_sample_hooks(false, true); @@ -311,12 +399,10 @@ TEST_BEGIN(test_prof_sample_hooks) { check_prof_sample_hooks(false, false); /* Test read+write together. */ - prof_sample_hook_t sample_hook; read_write_prof_sample_hook(&sample_hook, true, mock_prof_sample_hook); expect_ptr_null(sample_hook, "Unexpected non NULL default hook"); check_prof_sample_hooks(true, false); - prof_sample_free_hook_t sample_free_hook; read_write_prof_sample_free_hook( &sample_free_hook, true, mock_prof_sample_free_hook); expect_ptr_null(sample_free_hook, "Unexpected non NULL default hook");