// 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];
}
