#include "ixxl.h" #ifdef WIN32 HANDLE xxl_heap = NULL; #ifdef XXL_WITHOUT_THREADS static __inline xxl_tsd_t *get_xxl_tsd(void); #endif #endif static void die(char *); #if !defined(XXL_WITHOUT_THREADS) && defined(HAVE_PTHREAD_H) static void xxl_destructor(void *); static void xxl_init(void); #endif #if !defined(XXL_WITHOUT_THREADS) static xxl_tsd_t *get_xxl_tsd(void); #endif static xxl_asset_t *alloc_asset(xxl_tsd_t *); static void free_asset(xxl_tsd_t *, xxl_asset_t *); static xxl_context_t *alloc_context(xxl_tsd_t *); static void free_context(xxl_tsd_t *, xxl_context_t *); static void pop_assets(xxl_tsd_t *, xxl_context_t *); static void pop_asset_blocks(xxl_tsd_t *, xxl_context_t *, xxl_exception_t *); static xxl_context_t *get_try_context(xxl_tsd_t *); static void die(char *why) { #ifndef WIN32 fprintf(stderr, "%s", why); abort(); #else DebugBreak(); ExitProcess(0xFFFFFFFF); #endif } #ifndef XXL_WITHOUT_THREADS #ifdef WIN32 static DWORD xxl_tls_index = 0xFFFFFFFF; static xxl_tsd_t * get_xxl_tsd(void) { DWORD dwTlsIndex; xxl_tsd_t *tsd; if (!xxl_heap) xxl_heap = GetProcessHeap(); if (xxl_tls_index == 0xFFFFFFFF) { dwTlsIndex = TlsAlloc(); xxl_tls_index ^= dwTlsIndex; xxl_tls_index ^= 0xFFFFFFFF; if (xxl_tls_index != dwTlsIndex) die("XXL: Race condition in exception initialization!\n"); } if (!(tsd = (xxl_tsd_t *)TlsGetValue(xxl_tls_index))) { if (!(tsd = (xxl_tsd_t *)XXL_IMALLOC(sizeof(xxl_tsd_t)))) die("XXL: Insufficient memory to allocate thread-specific data!\n"); tsd->contexts = NULL; TlsSetValue(xxl_tls_index, tsd); } return tsd; } #else #ifdef HAVE_PTHREAD_H #include #endif static pthread_key_t xxl_key; static pthread_once_t xxl_once = PTHREAD_ONCE_INIT; static void xxl_destructor(void *arg) { xxl_tsd_t *tsd; xxl_asset_t *asset, *next_asset; xxl_context_t *context, *next_context; /* The thread has been prematurely cancelled or exited. Cleanup saved * assets, but don't run handlers. */ tsd = (xxl_tsd_t *)arg; while (tsd->contexts) { /* XXX: need thread cancelled error code */ tsd->contexts->exception.code = XXL_ERROR_THREAD_CANCELLED; tsd->contexts->exception.data = NULL; tsd->contexts->exception.file = NULL; tsd->contexts->exception.line = 0; xxl_pop_context(); } /* Release free list contents back to the system */ for (asset = tsd->free_assets; asset; asset = next_asset) { next_asset = asset->next; XXL_IFREE(asset); } for (context = tsd->free_contexts; context; context = next_context) { next_context = context->next; XXL_IFREE(context); } XXL_IFREE(arg); } static void xxl_init(void) { pthread_key_create(&xxl_key, xxl_destructor); } static xxl_tsd_t * get_xxl_tsd(void) { xxl_tsd_t *tsd; pthread_once(&xxl_once, xxl_init); if (!(tsd = (xxl_tsd_t *)pthread_getspecific(xxl_key))) { if (!(tsd = (xxl_tsd_t *)XXL_IMALLOC(sizeof(xxl_tsd_t)))) die("XXL: Insufficient memory to allocate thread-specific data!\n"); tsd->contexts = NULL; tsd->free_contexts = NULL; tsd->free_assets = NULL; pthread_setspecific(xxl_key, tsd); } return tsd; } #endif /* WIN32 */ #else /* XXL_WITHOUT_THREADS */ #if !defined(WIN32) || defined(XXL_WITHOUT_THREADS) static xxl_tsd_t xxl_tsd = { 0, NULL, NULL }; #else static xxl_tsd_t xxl_tsd = { 0 }; #endif #ifndef WIN32 #define get_xxl_tsd() (&xxl_tsd) #else static __inline xxl_tsd_t * get_xxl_tsd(void) { if (!xxl_heap) xxl_heap = GetProcessHeap(); return &xxl_tsd; } #endif #endif static xxl_asset_t * alloc_asset(xxl_tsd_t *tsd) { xxl_asset_t *asset; #if !defined(WIN32) || defined(XXL_WITHOUT_THREADS) if (tsd->free_assets) { asset = tsd->free_assets; tsd->free_assets = tsd->free_assets->next; } else #endif { if (!(asset = (xxl_asset_t *)XXL_IMALLOC(sizeof(xxl_asset_t)))) die("XXL: Insufficient memory to allocate a new asset record!\n"); } return asset; } static void free_asset(xxl_tsd_t *tsd, xxl_asset_t *asset) { #if !defined(WIN32) || defined(XXL_WITHOUT_THREADS) asset->next = tsd->free_assets; tsd->free_assets = asset; #else /* Don't use the internal free list on Win32 because we have no way of * cleaning it up later when the thread dies. */ XXL_IFREE(asset); #endif } static xxl_context_t * alloc_context(xxl_tsd_t *tsd) { xxl_context_t *context; #if !defined(WIN32) || defined(XXL_WITHOUT_THREADS) if (tsd->free_contexts) { context = tsd->free_contexts; tsd->free_contexts = tsd->free_contexts->next; } else #endif { if (!(context = (xxl_context_t *)XXL_IMALLOC(sizeof(xxl_context_t)))) die("XXL: Insufficient memory to allocate a new context!\n"); } return context; } static void free_context(xxl_tsd_t *tsd, xxl_context_t *context) { #if !defined(WIN32) || defined(XXL_WITHOUT_THREADS) context->next = tsd->free_contexts; tsd->free_contexts = context; #else /* Don't use the internal free list on Win32 because we have no way of * cleaning it up later when the thread dies. */ XXL_IFREE(context); #endif } static void pop_assets(xxl_tsd_t *tsd, xxl_context_t *context) { xxl_asset_t *asset; while (context->assets) { asset = context->assets; context->assets = asset->next; switch (asset->type) { case XXL_ASSET_PROMOTE: if ((context->state & XXL_STATE_THROWN) && asset->freefn) asset->freefn(asset->ptr, asset->arg); break; case XXL_ASSET_DEMOTE: if (!(context->state & XXL_STATE_THROWN) && asset->freefn) asset->freefn(asset->ptr, asset->arg); break; case XXL_ASSET_TEMPORARY: if (asset->freefn) asset->freefn(asset->ptr, asset->arg); break; case XXL_ASSET_PERMANENT: /* keep the compiler quiet */ case XXL_ASSET_AUTO: /* Shhh! */ break; default: die("XXL: Unknown asset type to pop!\n"); } free_asset(tsd, asset); } } static void pop_asset_blocks(xxl_tsd_t *tsd, xxl_context_t *context, xxl_exception_t *exception) { static xxl_exception_t null_exception = { 0, NULL, NULL, 0 }; if (!exception) exception = (context ? &context->exception : &null_exception); while (tsd->contexts != context) { tsd->contexts->state |= (context->state & XXL_STATE_MASK); tsd->contexts->exception = *exception; xxl_pop_context(); } } static xxl_context_t * get_try_context(xxl_tsd_t *tsd) { xxl_context_t *context; for (context = tsd->contexts; context; context = context->next) if (context->context) return context; return NULL; } xxl_context_t * xxl_push_context(jmp_buf *context) { #if !defined(WIN32) && !defined(XXL_WITHOUT_THREADS) int cancel_type; #endif xxl_tsd_t *tsd; xxl_context_t *new_context; #if !defined(WIN32) && !defined(XXL_WITHOUT_THREADS) pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &cancel_type); #endif tsd = get_xxl_tsd(); new_context = alloc_context(tsd); new_context->context = context; new_context->state = 0; new_context->exception.code = new_context->pending.code = 0; new_context->exception.data = new_context->pending.data = NULL; new_context->exception.file = new_context->pending.file = NULL; new_context->exception.line = new_context->pending.line = 0; #if !defined(WIN32) && !defined(XXL_WITHOUT_THREADS) new_context->cancel_type = cancel_type; #endif new_context->assets = NULL; new_context->next = tsd->contexts; tsd->contexts = new_context; return new_context; } void xxl_pop_context(void) { #if !defined(WIN32) && !defined(XXL_WITHOUT_THREADS) int cancel_type; #endif xxl_tsd_t *tsd; xxl_context_t *context; tsd = get_xxl_tsd(); context = tsd->contexts; if (context->state & XXL_STATE_PENDING) context->exception = context->pending; pop_assets(tsd, context); #if !defined(WIN32) && !defined(XXL_WITHOUT_THREADS) cancel_type = context->cancel_type; #endif tsd->contexts = context->next; free_context(tsd, context); #if !defined(WIN32) && !defined(XXL_WITHOUT_THREADS) pthread_setcanceltype(cancel_type, NULL); #endif } void xxl_pop_contexts(void) { xxl_tsd_t *tsd; tsd = get_xxl_tsd(); pop_asset_blocks(tsd, get_try_context(tsd), NULL); xxl_pop_context(); } void xxl_leave_handler(int how) { xxl_tsd_t *tsd; xxl_context_t *context; xxl_exception_t *exception; static xxl_exception_t null_exception = { 0, NULL, NULL, 0 }; static xxl_exception_t retry_exception = { XXL_ERROR_RETRY_EXCEPTION, NULL, NULL, 0 }; tsd = get_xxl_tsd(); if (!(context = get_try_context(tsd))) die("XXL: Exception thrown with no handler to catch it!\n"); if (how == XXL_SETJMP_PROMOTE && !(context->state & XXL_STATE_THROWN)) die("XXL: Cannot promote a non-existant exception!\n"); exception = (how == XXL_SETJMP_RETRY ? &retry_exception : &context->exception); pop_asset_blocks(tsd, context, exception); switch (how) { case XXL_SETJMP_RETRY: tsd->contexts->exception = *exception; pop_assets(tsd, tsd->contexts); tsd->contexts->exception = null_exception; break; case XXL_SETJMP_ERROR: xxl_pop_context(); if (!tsd->contexts) die("XXL: Exception thrown with no handler to catch it!\n"); tsd->contexts->exception = *exception; xxl_leave_handler(how); return; } longjmp(*(tsd->contexts->context), how); } void xxl_throw_error(int code, void *data, const char *file, unsigned int line) { xxl_tsd_t *tsd; xxl_context_t *context; #ifdef _DEBUG fprintf(stderr, "XXL DEBUG: Exception 0x%08X (%d) thrown from %s line %u.\n", code, code, file, line); #endif tsd = get_xxl_tsd(); if (!(context = get_try_context(tsd))) die("XXL: Exception thrown with no handler to catch it!\n"); /* If we're inside of a catch or except block, set a pending exception and * jump with XXL_SETJMP_PENDING so that the finally block will run if one * is present. We can tell if we're in a catch or except block by the * current state being set to XXL_SETJMP_ERROR. */ if ((context->state & XXL_SETJMP_MASK) == XXL_SETJMP_ERROR) { context->state |= (XXL_STATE_PENDING | XXL_STATE_THROWN); context->pending.code = code; context->pending.data = data; context->pending.file = file; context->pending.line = line; pop_asset_blocks(tsd, context, NULL); longjmp(*(context->context), XXL_SETJMP_PENDING); } /* If the current state is XXL_SETJMP_PENDING, an exception was thrown * from a catch or except block, and we must also be in a finally block * now. In this case, set the pending exception, pop the context stack, * and deliver the exception. We'll lose the exception that was thrown * from the catch or except block. This is the same behavior as Java in * this pathological situation. */ if ((context->state & XXL_SETJMP_MASK) == XXL_SETJMP_PENDING) { if (!(context->state & XXL_STATE_FINALLY)) die("XXL: Inconsistent state in xxl_throw_error()!\n"); context->state |= (XXL_STATE_PENDING | XXL_STATE_THROWN); context->pending.code = code; context->pending.data = data; context->pending.file = file; context->pending.line = line; xxl_pop_contexts(); xxl_throw_error(code, data, file, line); } /* The current state should be either XXL_SETJMP_TRY or XXL_SETJMP_RETRY. * If it's neither, we've got an inconsistent state and we have to abort. */ if ((context->state & XXL_SETJMP_MASK) == XXL_SETJMP_TRY || (context->state & XXL_SETJMP_MASK) == XXL_SETJMP_RETRY) { context->state |= XXL_STATE_THROWN; context->exception.code = code; context->exception.data = data; context->exception.file = file; context->exception.line = line; pop_asset_blocks(tsd, context, NULL); longjmp(*(context->context), XXL_SETJMP_ERROR); } die("XXL: Inconsistent state in xxl_throw_error()!\n"); } int xxl_current_error_code(void) { xxl_context_t *context; context = get_try_context(get_xxl_tsd()); return (context ? context->exception.code : 0); } void * xxl_current_error_data(void) { xxl_context_t *context; context = get_try_context(get_xxl_tsd()); return (context ? context->exception.data : NULL); } const char * xxl_current_error_file(void) { xxl_context_t *context; context = get_try_context(get_xxl_tsd()); return (context ? context->exception.file : NULL); } unsigned int xxl_current_error_line(void) { xxl_context_t *context; context = get_try_context(get_xxl_tsd()); return (context ? context->exception.line : 0); } void xxl_push_asset(void *ptr, xxl_assetfreefn_t freefn, void *arg, xxl_assettype_t type) { xxl_tsd_t *tsd; xxl_asset_t *new_asset; if (type == XXL_ASSET_PERMANENT) return; tsd = get_xxl_tsd(); if (!tsd->contexts) die("XXL_: Attempt to push an asset outside of an asset block!\n"); new_asset = alloc_asset(tsd); new_asset->ptr = ptr; new_asset->freefn = freefn; new_asset->arg = arg; new_asset->type = type; new_asset->next = tsd->contexts->assets; tsd->contexts->assets = new_asset; } void xxl_update_asset(void *old_ptr, void *new_ptr) { xxl_tsd_t *tsd; xxl_asset_t *asset; xxl_context_t *context; tsd = get_xxl_tsd(); for (context = tsd->contexts; context; context = context->next) for (asset = context->assets; asset; asset = asset->next) if (asset->ptr == old_ptr) asset->ptr = new_ptr; } void xxl_release_asset(void *ptr, int mode) { int found = 0; xxl_tsd_t *tsd; xxl_asset_t *asset, *prev; xxl_context_t *context; tsd = get_xxl_tsd(); if (mode == XXL_ASSET_CURRENT) { prev = NULL; for (asset = tsd->contexts->assets; asset; asset = asset->next) { if (asset->ptr == ptr) { if (prev) prev->next = asset->next; else tsd->contexts->assets = asset->next; free_asset(tsd, asset); break; } prev = asset; } return; } for (context = tsd->contexts; !found && context; context = context->next) { prev = NULL; for (asset = context->assets; asset; asset = asset->next) { if (asset->ptr == ptr) { if (prev) prev->next = asset->next; else context->assets = asset->next; free_asset(tsd, asset); found = (mode == XXL_ASSET_FIRST); break; } prev = asset; } } }