#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