#include <TargetConditionals.h>
#include <darwintest.h>
#include <darwintest_utils.h>
#include <spawn.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <malloc/malloc.h>
#include <../private/malloc_private.h>
#include <../src/internal.h>
#if CONFIG_NANOZONE
#pragma mark -
#pragma mark Enumerator access
static int range_count; static int ptr_count; static size_t total_ranges_size; static size_t total_in_use_ptr_size;
static void
range_recorder(task_t task, void *context, unsigned type, vm_range_t *ranges,
unsigned count)
{
for (int i = 0; i < count; i++) {
total_ranges_size += ranges[i].size;
}
range_count += count;
}
static void
pointer_recorder(task_t task, void *context, unsigned type, vm_range_t *ranges,
unsigned count)
{
for (int i = 0; i < count; i++) {
total_in_use_ptr_size += ranges[i].size;
}
ptr_count += count;
}
static kern_return_t
memory_reader(task_t remote_task, vm_address_t remote_address, vm_size_t size,
void **local_memory)
{
if (local_memory) {
*local_memory = (void*)remote_address;
return KERN_SUCCESS;
}
return KERN_FAILURE;
}
static void
run_enumerator()
{
total_ranges_size = 0;
total_in_use_ptr_size = 0;
range_count = 0;
ptr_count = 0;
malloc_zone_t *zone = malloc_default_zone();
zone->introspect->enumerator(mach_task_self(), NULL,
MALLOC_PTR_REGION_RANGE_TYPE, (vm_address_t)zone, memory_reader,
range_recorder);
zone->introspect->enumerator(mach_task_self(), NULL,
MALLOC_PTR_IN_USE_RANGE_TYPE, (vm_address_t)zone, memory_reader,
pointer_recorder);
}
#endif // CONFIG_NANOZONE
#pragma mark -
#pragma mark Enumerator tests
#if TARGET_OS_WATCH
#define ALLOCATION_COUNT 10000
#else // TARGET_OS_WATCH
#define ALLOCATION_COUNT 100000
#endif // TARGET_OS_WATCH
static void *allocations[ALLOCATION_COUNT];
T_DECL(nano_active_test, "Test that Nano is activated",
T_META_ENVVAR("MallocNanoZone=1"))
{
#if CONFIG_NANOZONE
void *ptr = malloc(16);
T_LOG("Nano ptr is %p\n", ptr);
T_ASSERT_EQ(NANOZONE_SIGNATURE, (uint64_t)((uintptr_t)ptr) >> SHIFT_NANO_SIGNATURE,
"Nanozone is active");
T_ASSERT_NE(malloc_engaged_nano(), 0, "Nanozone engaged");
free(ptr);
#else // CONFIG_NANOZONE
T_SKIP("Nano allocator not configured");
#endif // CONFIG_NANOZONE
}
T_DECL(nano_enumerator_test, "Test the Nanov2 enumerator",
T_META_ENVVAR("MallocNanoZone=V2"))
{
#if CONFIG_NANOZONE
T_ASSERT_EQ(malloc_engaged_nano(), 2, "Nanozone V2 engaged");
malloc_statistics_t stats;
malloc_zone_statistics(malloc_default_zone(), &stats);
const unsigned int initial_blocks_in_use = stats.blocks_in_use;
const size_t initial_size_in_use = stats.size_in_use;
const size_t initial_size_allocated = stats.size_allocated;
run_enumerator();
const int initial_ptrs = ptr_count;
const size_t initial_ranges_size = total_ranges_size;
const size_t initial_in_use_ptr_size = total_in_use_ptr_size;
size_t total_requested_size = 0;
for (int i = 0; i < ALLOCATION_COUNT; i++) {
size_t sz = malloc_good_size(arc4random_uniform(257));
allocations[i] = malloc(sz);
total_requested_size += sz;
}
malloc_zone_statistics(malloc_default_zone(), &stats);
run_enumerator();
T_ASSERT_EQ(stats.blocks_in_use, initial_blocks_in_use + ALLOCATION_COUNT,
"Incorrect blocks_in_use");
T_ASSERT_EQ(stats.size_in_use, initial_size_in_use + total_requested_size,
"Incorrect size_in_use");
T_ASSERT_TRUE(stats.size_allocated - initial_size_allocated >= total_requested_size,
"Size allocated must be >= size requested");
T_ASSERT_EQ(ptr_count, initial_ptrs + ALLOCATION_COUNT,
"Incorrect number of pointers");
T_ASSERT_EQ(total_in_use_ptr_size, initial_in_use_ptr_size + total_requested_size,
"Incorrect in-use pointer size");
size_t size_freed = 0;
for (int i = 0; i < ALLOCATION_COUNT / 2; i++) {
size_freed += malloc_size(allocations[i]);
free(allocations[i]);
}
malloc_zone_statistics(malloc_default_zone(), &stats);
run_enumerator();
T_ASSERT_EQ(stats.blocks_in_use, initial_blocks_in_use + ALLOCATION_COUNT/2,
"Incorrect blocks_in_use after half free");
T_ASSERT_EQ(stats.size_in_use,
initial_size_in_use + total_requested_size - size_freed,
"Incorrect size_in_use after half free");
T_ASSERT_TRUE(stats.size_allocated >= initial_size_allocated ,
"Size allocated must be >= size requested");
T_ASSERT_EQ(ptr_count, initial_ptrs + ALLOCATION_COUNT / 2,
"Incorrect number of pointers after half free");
T_ASSERT_EQ(total_in_use_ptr_size,
initial_in_use_ptr_size + total_requested_size - size_freed,
"Incorrect in-use pointer size after half free");
for (int i = ALLOCATION_COUNT / 2; i < ALLOCATION_COUNT; i++) {
free(allocations[i]);
}
malloc_zone_statistics(malloc_default_zone(), &stats);
run_enumerator();
T_ASSERT_EQ(stats.blocks_in_use, initial_blocks_in_use,
"Incorrect blocks_in_use after full free");
T_ASSERT_EQ(stats.size_in_use, initial_size_in_use,
"Incorrect size_in_use after full free");
T_ASSERT_TRUE(stats.size_allocated >= initial_size_allocated ,
"Size allocated must be >= size requested");
T_ASSERT_EQ(ptr_count, initial_ptrs, "Incorrect number of pointers after free");
T_ASSERT_EQ(total_in_use_ptr_size, initial_in_use_ptr_size,
"Incorrect in-use pointer size after free");
#else // CONFIG_NANOZONE
T_SKIP("Nano allocator not configured");
#endif // CONFIG_NANOZONE
}
#pragma mark -
#pragma mark Nano realloc tests
const char * const data = "abcdefghijklm";
T_DECL(realloc_nano_size_class_change, "realloc with size class change",
T_META_ENVVAR("MallocNanoZone=1"))
{
#if CONFIG_NANOZONE
void *ptr = malloc(16);
strcpy(ptr, data);
void *new_ptr;
for (int i = 32; i <= 256; i += 16) {
new_ptr = realloc(ptr, i);
T_QUIET; T_ASSERT_TRUE(ptr != new_ptr, "realloc pointer should change");
T_QUIET; T_ASSERT_EQ(i, (int)malloc_size(new_ptr), "Check size for new allocation");
T_QUIET; T_ASSERT_TRUE(!strncmp(new_ptr, data, strlen(data)), "Content must be copied");
T_QUIET; T_ASSERT_EQ(0, (int)malloc_size(ptr), "Old allocation not freed");
ptr = new_ptr;
}
free(new_ptr);
#else // CONFIG_NANOZONE
T_SKIP("Nano allocator not configured");
#endif // CONFIG_NANOZONE
}
T_DECL(realloc_nano_ptr_change, "realloc with pointer change",
T_META_ENVVAR("MallocNanoZone=1"))
{
#if CONFIG_NANOZONE
void *ptr = malloc(32);
strcpy(ptr, data);
void *new_ptr = realloc(ptr, 128);
T_ASSERT_TRUE(ptr != new_ptr, "realloc pointer should change");
T_ASSERT_EQ(128, (int)malloc_size(new_ptr), "Wrong size for new allocation");
T_ASSERT_TRUE(!strncmp(new_ptr, data, strlen(data)), "Content must be copied");
T_ASSERT_EQ(0, (int)malloc_size(ptr), "Old allocation not freed");
free(new_ptr);
#else // CONFIG_NANOZONE
T_SKIP("Nano allocator not configured");
#endif // CONFIG_NANOZONE
}
T_DECL(realloc_nano_to_other, "realloc with allocator change (nano)",
T_META_ENVVAR("MallocNanoZone=1"))
{
#if CONFIG_NANOZONE
void *ptr = malloc(32); strcpy(ptr, data);
void *new_ptr = realloc(ptr, 1024);
T_ASSERT_TRUE(ptr != new_ptr, "realloc pointer should change");
T_ASSERT_EQ(1024, (int)malloc_size(new_ptr), "Wrong size for new allocation");
T_ASSERT_TRUE(!strncmp(new_ptr, data, strlen(data)), "Content must be copied");
T_ASSERT_EQ(0, (int)malloc_size(ptr), "Old allocation not freed");
free(new_ptr);
#else // CONFIG_NANOZONE
T_SKIP("Nano allocator not configured");
#endif // CONFIG_NANOZONE
}
T_DECL(realloc_nano_to_zero_size, "realloc with target size zero",
T_META_ENVVAR("MallocNanoZone=1"))
{
#if CONFIG_NANOZONE
void *ptr = malloc(16);
void *new_ptr = realloc(ptr, 0);
T_ASSERT_EQ(0, (int)malloc_size(ptr), "Old allocation not freed");
T_ASSERT_NOTNULL(new_ptr, "New allocation must be non-NULL");
T_ASSERT_TRUE(malloc_size(new_ptr) > 0, "New allocation not known");
free(new_ptr);
#else // CONFIG_NANOZONE
T_SKIP("Nano allocator not configured");
#endif // CONFIG_NANOZONE
}
T_DECL(realloc_nano_shrink, "realloc to smaller size",
T_META_ENVVAR("MallocNanoZone=1"))
{
#if CONFIG_NANOZONE
void *ptr = malloc(64);
strcpy(ptr, data);
void *new_ptr = realloc(ptr, 40);
T_ASSERT_TRUE(ptr == new_ptr, "realloc pointer should not change");
T_ASSERT_TRUE(!strncmp(new_ptr, data, strlen(data)), "Content changed");
ptr = new_ptr;
new_ptr = realloc(ptr, 16);
T_ASSERT_TRUE(ptr != new_ptr, "realloc pointer should change");
T_ASSERT_TRUE(!strncmp(new_ptr, data, strlen(data)), "Content must be copied");
free(new_ptr);
#else // CONFIG_NANOZONE
T_SKIP("Nano allocator not configured");
#endif // CONFIG_NANOZONE
}
#pragma mark -
#pragma mark Nanov2 tests
#define ALLOCS_PER_ARENA ((NANOV2_ARENA_SIZE)/256)
T_DECL(overspill_arena, "force overspill of an arena",
T_META_ENVVAR("MallocNanoZone=V2"))
{
#if CONFIG_NANOZONE
void **ptrs = calloc(ALLOCS_PER_ARENA, sizeof(void *));
T_QUIET; T_ASSERT_NOTNULL(ptrs, "Unable to allocate pointers");
int index;
nanov2_addr_t first_ptr;
ptrs[0] = malloc(256);
T_QUIET; T_ASSERT_NOTNULL(ptrs[index], "Failed to allocate");
first_ptr.addr = ptrs[0];
for (index = 1; index < ALLOCS_PER_ARENA; index++) {
ptrs[index] = malloc(256);
T_QUIET; T_ASSERT_NOTNULL(ptrs[index], "Failed to allocate");
nanov2_addr_t current_ptr;
current_ptr.addr = ptrs[index];
if (current_ptr.fields.nano_arena != first_ptr.fields.nano_arena) {
break;
}
}
for (int i = 0; i <= index; i++) {
free(ptrs[i]);
}
free(ptrs);
#else // CONFIG_NANOZONE
T_SKIP("Nano allocator not configured");
#endif // CONFIG_NANOZONE
}
#if TARGET_OS_OSX
#define ALLOCS_PER_REGION ((NANOV2_REGION_SIZE)/256)
T_DECL(overspill_region, "force overspill of a region",
T_META_ENVVAR("MallocNanoZone=V2"))
{
#if CONFIG_NANOZONE
void **ptrs = calloc(ALLOCS_PER_REGION, sizeof(void *));
T_QUIET; T_ASSERT_NOTNULL(ptrs, "Unable to allocate pointers");
int index;
nanov2_addr_t first_ptr;
ptrs[0] = malloc(256);
T_QUIET; T_ASSERT_NOTNULL(ptrs[index], "Failed to allocate");
first_ptr.addr = ptrs[0];
for (index = 1; index < ALLOCS_PER_REGION; index++) {
ptrs[index] = malloc(256);
T_QUIET; T_ASSERT_NOTNULL(ptrs[index], "Failed to allocate");
nanov2_addr_t current_ptr;
current_ptr.addr = ptrs[index];
if (current_ptr.fields.nano_region != first_ptr.fields.nano_region) {
break;
}
}
for (int i = 0; i <= index; i++) {
free(ptrs[i]);
}
free(ptrs);
#else // CONFIG_NANOZONE
T_SKIP("Nano allocator not configured");
#endif // CONFIG_NANOZONE
}
#endif // TARGET_OS_OSX