#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 <pthread.h>
#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;
}
}
}
syntax highlighted by Code2HTML, v. 0.9.1