// Mac OS X 10.6 or higher only. #include <dispatch/dispatch.h> #include <pthread.h> // for pthread_yield_np() #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #import <CoreFoundation/CFBase.h> #import <Foundation/NSObject.h> #import <Foundation/NSURL.h> // This is a (void*)(void*) function so it can be passed to pthread_create. void *CFAllocatorDefaultDoubleFree(void *unused) { void *mem = CFAllocatorAllocate(kCFAllocatorDefault, 5, 0); CFAllocatorDeallocate(kCFAllocatorDefault, mem); CFAllocatorDeallocate(kCFAllocatorDefault, mem); return 0; } void CFAllocatorSystemDefaultDoubleFree() { void *mem = CFAllocatorAllocate(kCFAllocatorSystemDefault, 5, 0); CFAllocatorDeallocate(kCFAllocatorSystemDefault, mem); CFAllocatorDeallocate(kCFAllocatorSystemDefault, mem); } void CFAllocatorMallocDoubleFree() { void *mem = CFAllocatorAllocate(kCFAllocatorMalloc, 5, 0); CFAllocatorDeallocate(kCFAllocatorMalloc, mem); CFAllocatorDeallocate(kCFAllocatorMalloc, mem); } void CFAllocatorMallocZoneDoubleFree() { void *mem = CFAllocatorAllocate(kCFAllocatorMallocZone, 5, 0); CFAllocatorDeallocate(kCFAllocatorMallocZone, mem); CFAllocatorDeallocate(kCFAllocatorMallocZone, mem); } __attribute__((noinline)) void access_memory(char *a) { *a = 0; } // Test the +load instrumentation. // Because the +load methods are invoked before anything else is initialized, // it makes little sense to wrap the code below into a gTest test case. // If AddressSanitizer doesn't instrument the +load method below correctly, // everything will just crash. char kStartupStr[] = "If your test didn't crash, AddressSanitizer is instrumenting " "the +load methods correctly."; @interface LoadSomething : NSObject { } @end @implementation LoadSomething +(void) load { for (size_t i = 0; i < strlen(kStartupStr); i++) { access_memory(&kStartupStr[i]); // make sure no optimizations occur. } // Don't print anything here not to interfere with the death tests. } @end void worker_do_alloc(int size) { char * volatile mem = (char * volatile)malloc(size); mem[0] = 0; // Ok free(mem); } void worker_do_crash(int size) { char * volatile mem = (char * volatile)malloc(size); access_memory(&mem[size]); // BOOM free(mem); } // Used by the GCD tests to avoid a race between the worker thread reporting a // memory error and the main thread which may exit with exit code 0 before // that. void wait_forever() { volatile bool infinite = true; while (infinite) pthread_yield_np(); } // Tests for the Grand Central Dispatch. See // http://developer.apple.com/library/mac/#documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html // for the reference. void TestGCDDispatchAsync() { dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_block_t block = ^{ worker_do_crash(1024); }; // dispatch_async() runs the task on a worker thread that does not go through // pthread_create(). We need to verify that AddressSanitizer notices that the // thread has started. dispatch_async(queue, block); wait_forever(); } void TestGCDDispatchSync() { dispatch_queue_t queue = dispatch_get_global_queue(2, 0); dispatch_block_t block = ^{ worker_do_crash(1024); }; // dispatch_sync() runs the task on a worker thread that does not go through // pthread_create(). We need to verify that AddressSanitizer notices that the // thread has started. dispatch_sync(queue, block); wait_forever(); } // libdispatch spawns a rather small number of threads and reuses them. We need // to make sure AddressSanitizer handles the reusing correctly. void TestGCDReuseWqthreadsAsync() { dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_block_t block_alloc = ^{ worker_do_alloc(1024); }; dispatch_block_t block_crash = ^{ worker_do_crash(1024); }; for (int i = 0; i < 100; i++) { dispatch_async(queue, block_alloc); } dispatch_async(queue, block_crash); wait_forever(); } // Try to trigger abnormal behaviour of dispatch_sync() being unhandled by us. void TestGCDReuseWqthreadsSync() { dispatch_queue_t queue[4]; queue[0] = dispatch_get_global_queue(2, 0); queue[1] = dispatch_get_global_queue(0, 0); queue[2] = dispatch_get_global_queue(-2, 0); queue[3] = dispatch_queue_create("my_queue", NULL); dispatch_block_t block_alloc = ^{ worker_do_alloc(1024); }; dispatch_block_t block_crash = ^{ worker_do_crash(1024); }; for (int i = 0; i < 1000; i++) { dispatch_sync(queue[i % 4], block_alloc); } dispatch_sync(queue[3], block_crash); wait_forever(); } void TestGCDDispatchAfter() { dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_block_t block_crash = ^{ worker_do_crash(1024); }; // Schedule the event one second from the current time. dispatch_time_t milestone = dispatch_time(DISPATCH_TIME_NOW, 1LL * NSEC_PER_SEC); dispatch_after(milestone, queue, block_crash); wait_forever(); } void worker_do_deallocate(void *ptr) { free(ptr); } void CallFreeOnWorkqueue(void *tsd) { dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_block_t block_dealloc = ^{ worker_do_deallocate(tsd); }; dispatch_async(queue, block_dealloc); // Do not wait for the worker to free the memory -- nobody is going to touch // it. } void TestGCDSourceEvent() { dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); // Schedule the timer one second from the current time. dispatch_time_t milestone = dispatch_time(DISPATCH_TIME_NOW, 1LL * NSEC_PER_SEC); dispatch_source_set_timer(timer, milestone, DISPATCH_TIME_FOREVER, 0); char * volatile mem = (char * volatile)malloc(10); dispatch_source_set_event_handler(timer, ^{ access_memory(&mem[10]); }); dispatch_resume(timer); wait_forever(); } void TestGCDSourceCancel() { dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); // Schedule the timer one second from the current time. dispatch_time_t milestone = dispatch_time(DISPATCH_TIME_NOW, 1LL * NSEC_PER_SEC); dispatch_source_set_timer(timer, milestone, DISPATCH_TIME_FOREVER, 0); char * volatile mem = (char * volatile)malloc(10); // Both dispatch_source_set_cancel_handler() and // dispatch_source_set_event_handler() use dispatch_barrier_async_f(). // It's tricky to test dispatch_source_set_cancel_handler() separately, // so we test both here. dispatch_source_set_event_handler(timer, ^{ dispatch_source_cancel(timer); }); dispatch_source_set_cancel_handler(timer, ^{ access_memory(&mem[10]); }); dispatch_resume(timer); wait_forever(); } void TestGCDGroupAsync() { dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_group_t group = dispatch_group_create(); char * volatile mem = (char * volatile)malloc(10); dispatch_group_async(group, queue, ^{ access_memory(&mem[10]); }); dispatch_group_wait(group, DISPATCH_TIME_FOREVER); wait_forever(); } @interface FixedArray : NSObject { int items[10]; } @end @implementation FixedArray -(int) access: (int)index { return items[index]; } @end void TestOOBNSObjects() { id anObject = [FixedArray new]; [anObject access:1]; [anObject access:11]; [anObject release]; } void TestNSURLDeallocation() { NSURL *base = [[NSURL alloc] initWithString:@"file://localhost/Users/glider/Library/"]; volatile NSURL *u = [[NSURL alloc] initWithString:@"Saved Application State" relativeToURL:base]; [u release]; }