static dbus_bool_t run_failing_each_malloc (int n_mallocs, const char *description, DBusTestMemoryFunction func, void *data) { n_mallocs += 10; /* fudge factor to ensure reallocs etc. are covered */ while (n_mallocs >= 0) { _dbus_set_fail_alloc_counter (n_mallocs); _dbus_verbose ("\n===\n%s: (will fail malloc %d with %d failures)\n===\n", description, n_mallocs, _dbus_get_fail_alloc_failures ()); if (!(* func) (data)) return FALSE; n_mallocs -= 1; } _dbus_set_fail_alloc_counter (_DBUS_INT_MAX); return TRUE; }
/** * Tests how well the given function responds to out-of-memory * situations. Calls the function repeatedly, failing a different * call to malloc() each time. If the function ever returns #FALSE, * the test fails. The function should return #TRUE whenever something * valid (such as returning an error, or succeeding) occurs, and #FALSE * if it gets confused in some way. * * @param description description of the test used in verbose output * @param func function to call * @param data data to pass to function * @returns #TRUE if the function never returns FALSE */ dbus_bool_t _dbus_test_oom_handling (const char *description, DBusTestMemoryFunction func, void *data) { int approx_mallocs; const char *setting; int max_failures_to_try; int i; /* Run once to see about how many mallocs are involved */ _dbus_set_fail_alloc_counter (_DBUS_INT_MAX); _dbus_verbose ("Running once to count mallocs\n"); if (!(* func) (data)) return FALSE; approx_mallocs = _DBUS_INT_MAX - _dbus_get_fail_alloc_counter (); _dbus_verbose ("\n=================\n%s: about %d mallocs total\n=================\n", description, approx_mallocs); setting = _dbus_getenv ("DBUS_TEST_MALLOC_FAILURES"); if (setting != NULL) { DBusString str; long v; _dbus_string_init_const (&str, setting); v = 4; if (!_dbus_string_parse_int (&str, 0, &v, NULL)) _dbus_warn ("couldn't parse '%s' as integer\n", setting); max_failures_to_try = v; } else { max_failures_to_try = 4; } i = setting ? max_failures_to_try - 1 : 1; while (i < max_failures_to_try) { _dbus_set_fail_alloc_failures (i); if (!run_failing_each_malloc (approx_mallocs, description, func, data)) return FALSE; ++i; } _dbus_verbose ("\n=================\n%s: all iterations passed\n=================\n", description); return TRUE; }
/** * Allocates an object from the memory pool. * The object must be freed with _dbus_mem_pool_dealloc(). * * @param pool the memory pool * @returns the allocated object or #NULL if no memory. */ void* _dbus_mem_pool_alloc (DBusMemPool *pool) { #ifdef DBUS_BUILD_TESTS if (_dbus_disable_mem_pools ()) { DBusMemBlock *block; int alloc_size; /* This is obviously really silly, but it's * debug-mode-only code that is compiled out * when tests are disabled (_dbus_disable_mem_pools() * is a constant expression FALSE so this block * should vanish) */ alloc_size = sizeof (DBusMemBlock) - ELEMENT_PADDING + pool->element_size; if (pool->zero_elements) block = dbus_malloc0 (alloc_size); else block = dbus_malloc (alloc_size); if (block != NULL) { block->next = pool->blocks; pool->blocks = block; pool->allocated_elements += 1; return (void*) &block->elements[0]; } else return NULL; } else #endif { if (_dbus_decrement_fail_alloc_counter ()) { _dbus_verbose (" FAILING mempool alloc\n"); return NULL; } else if (pool->free_elements) { DBusFreedElement *element = pool->free_elements; pool->free_elements = pool->free_elements->next; if (pool->zero_elements) memset (element, '\0', pool->element_size); pool->allocated_elements += 1; return element; } else { void *element; if (pool->blocks == NULL || pool->blocks->used_so_far == pool->block_size) { /* Need a new block */ DBusMemBlock *block; int alloc_size; #ifdef DBUS_BUILD_TESTS int saved_counter; #endif if (pool->block_size <= _DBUS_INT_MAX / 4) /* avoid overflow */ { /* use a larger block size for our next block */ pool->block_size *= 2; _dbus_assert ((pool->block_size % pool->element_size) == 0); } alloc_size = sizeof (DBusMemBlock) - ELEMENT_PADDING + pool->block_size; #ifdef DBUS_BUILD_TESTS /* We save/restore the counter, so that memory pools won't * cause a given function to have different number of * allocations on different invocations. i.e. when testing * we want consistent alloc patterns. So we skip our * malloc here for purposes of failed alloc simulation. */ saved_counter = _dbus_get_fail_alloc_counter (); _dbus_set_fail_alloc_counter (_DBUS_INT_MAX); #endif if (pool->zero_elements) block = dbus_malloc0 (alloc_size); else block = dbus_malloc (alloc_size); #ifdef DBUS_BUILD_TESTS _dbus_set_fail_alloc_counter (saved_counter); _dbus_assert (saved_counter == _dbus_get_fail_alloc_counter ()); #endif if (block == NULL) return NULL; block->used_so_far = 0; block->next = pool->blocks; pool->blocks = block; } element = &pool->blocks->elements[pool->blocks->used_so_far]; pool->blocks->used_so_far += pool->element_size; pool->allocated_elements += 1; return element; } } }