//
// Copyright (c) 1999-2003 by Digital Mars, www.digitalmars.com
// All Rights Reserved
// Written by Walter Bright

// Exception handling support

#include        <stdio.h>
#include        <string.h>
#include        <assert.h>
#include        <stdlib.h>

/* ======================== Win32 =============================== */

#if _WIN32

#include        <excpt.h>
#include        <windows.h>

//#include      "\sc\src\include\ehsup.h"

/*** From Digital Mars C runtime library ***/
EXCEPTION_DISPOSITION __cdecl _local_except_handler (EXCEPTION_RECORD *ExceptionRecord,
    void* EstablisherFrame,
        void *ContextRecord,
        void *DispatcherContext
        );
void __cdecl _global_unwind(void *frame,EXCEPTION_RECORD *eRecord);
#define EXCEPTION_UNWIND  6  // Flag to indicate if the system is unwinding

extern DWORD _except_list;
/*** ***/

#include        "mars.h"

extern ClassInfo D9Exception7__ClassZ;

#define _Class_9Exception D9Exception7__ClassZ

typedef int (__pascal *fp_t)();   // function pointer in ambient memory model

struct Interface* rt_createTraceContext(PCONTEXT context);
void* gc_calloc(size_t sz, unsigned ba);

// The layout of DEstablisherFrame is the same for C++

struct DEstablisherFrame
{
    void *prev;                 // pointer to previous exception list
    void *handler;              // pointer to routine for exception handler
    DWORD table_index;          // current index into handler_info[]
    DWORD ebp;                  // this is EBP of routine
};

struct DHandlerInfo
{
    int prev_index;             // previous table index
    unsigned cioffset;          // offset to DCatchInfo data from start of table (!=0 if try-catch)
    void *finally_code;         // pointer to finally code to execute
                                // (!=0 if try-finally)
};

// Address of DHandlerTable is passed in EAX to _d_framehandler()

struct DHandlerTable
{
    void *fptr;                 // pointer to start of function
    unsigned espoffset;         // offset of ESP from EBP
    unsigned retoffset;         // offset from start of function to return code
    struct DHandlerInfo handler_info[1];
};

struct DCatchBlock
{
    ClassInfo *type;            // catch type
    unsigned bpoffset;          // EBP offset of catch var
    void *code;                 // catch handler code
};

// Create one of these for each try-catch
struct DCatchInfo
{
    unsigned ncatches;                  // number of catch blocks
    struct DCatchBlock catch_block[1];  // data for each catch block
};

// Macro to make our own exception code
#define MAKE_EXCEPTION_CODE(severity, facility, exception)      \
        (((severity) << 30) | (1 << 29) | (0 << 28) | ((facility) << 16) | (exception))

#define STATUS_DIGITAL_MARS_D_EXCEPTION         MAKE_EXCEPTION_CODE(3,'D',1)

Object *_d_translate_se_to_d_exception(PCONTEXT context, EXCEPTION_RECORD *exception_record);
void __cdecl _d_local_unwind(struct DHandlerTable *handler_table, struct DEstablisherFrame *frame, int stop_index);


/***********************************
 * The frame handler, this is called for each frame that has been registered
 * in the OS except_list.
 * Input:
 *      EAX     the handler table for the frame
 */

EXCEPTION_DISPOSITION _d_framehandler(
            EXCEPTION_RECORD *exception_record,
            struct DEstablisherFrame *frame,
            CONTEXT context,
            void *dispatcher_context)
{
    struct DHandlerTable *handler_table;

    __asm { mov handler_table,EAX }

    if (exception_record->ExceptionFlags & EXCEPTION_UNWIND)
    {
         // Call all the finally blocks in this frame
         _d_local_unwind(handler_table, frame, -1);
    }
    else
    {
        // Jump to catch block if matching one is found

        int ndx,prev_ndx,i;
        struct DHandlerInfo *phi;
        struct DCatchInfo *pci;
        struct DCatchBlock *pcb;
        unsigned ncatches;              // number of catches in the current handler
        Object *pti;
        ClassInfo *ci;

        ci = NULL;                      // only compute it if we need it

        // walk through handler table, checking each handler
        // with an index smaller than the current table_index
        for (ndx = frame->table_index; ndx != -1; ndx = prev_ndx)
        {
            phi = &handler_table->handler_info[ndx];
            prev_ndx = phi->prev_index;
            if (phi->cioffset)
            {
                // this is a catch handler (no finally)
                pci = (struct DCatchInfo *)((char *)handler_table + phi->cioffset);
                ncatches = pci->ncatches;
                for (i = 0; i < ncatches; i++)
                {
                    pcb = pci->catch_block.ptr + i;

                    if (!ci)
                    {
                        // This code must match the translation code
                        if (exception_record->ExceptionCode == STATUS_DIGITAL_MARS_D_EXCEPTION)
                        {
                            //printf("ei[0] = %p\n", exception_record->ExceptionInformation[0]);
                            ci = **(ClassInfo ***)(exception_record->ExceptionInformation[0]);
                        }
                        else {
                            ci = &_Class_9Exception;
                            context.Eip = (DWORD)exception_record->ExceptionAddress;
                        }
                    }

                    if (_d_isbaseof(ci, pcb->type))
                    {   // Matched the catch type, so we've found the handler.
                        int regebp;

                        pti = _d_translate_se_to_d_exception(&context, exception_record);

                        // Initialize catch variable
                        regebp = (int)&frame->ebp;              // EBP for this frame
                        *(void **)(regebp + (pcb->bpoffset)) = pti;

                        // Have system call all finally blocks in intervening frames
                        _global_unwind(frame, exception_record);

                        // Call all the finally blocks skipped in this frame
                        _d_local_unwind(handler_table, frame, ndx);

                        frame->table_index = prev_ndx;  // we are out of this handler

                        // Jump to catch block. Does not return.
                        {
                            unsigned catch_esp;
                            fp_t catch_addr;

                            catch_addr = (fp_t)(pcb->code);
                            catch_esp = regebp - handler_table->espoffset - sizeof(fp_t);
                            _asm
                            {
                                mov     EAX,catch_esp
                                mov     ECX,catch_addr
                                mov     [EAX],ECX
                                mov     EBP,regebp
                                mov     ESP,EAX         // reset stack
                                ret                     // jump to catch block
                            }
                        }
                    }
                }
            }
        }
    }
    return ExceptionContinueSearch;
}

/***********************************
 * Exception filter for use in __try..__except block
 * surrounding call to Dmain()
 */

int _d_exception_filter(struct _EXCEPTION_POINTERS *eptrs,
                        int retval,
                        Object **exception_object)
{
    *exception_object = _d_translate_se_to_d_exception(eptrs->ContextRecord, eptrs->ExceptionRecord);
    return retval;
}

/***********************************
 * Throw a D object.
 */

void __stdcall _d_throw(Object *h)
{
    //printf("_d_throw(h = %p, &h = %p)\n", h, &h);
    //printf("\tvptr = %p\n", *(void **)h);
    RaiseException(STATUS_DIGITAL_MARS_D_EXCEPTION,
                   EXCEPTION_NONCONTINUABLE,
                   1, (DWORD *)&h);
}

/***********************************
 * Create an exception object
 */

Object *_d_create_exception_object(PCONTEXT context, ClassInfo *ci, char *msg)
{
    Exception *exc;

    exc = (Exception *)_d_newclass(ci);
    // BUG: what if _d_newclass() throws an out of memory exception?

    if (msg)
    {
        exc->msglen = strlen(msg);
        exc->msg = msg;
    }

    exc->info = rt_createTraceContext(context);

    return (Object *)exc;
}

/***********************************
 * Converts a Windows Structured Exception code to a D Exception Object.
 */

Object *_d_translate_se_to_d_exception(PCONTEXT context, EXCEPTION_RECORD *exception_record)
{
    Object *pti;

    switch (exception_record->ExceptionCode) {
        case STATUS_DIGITAL_MARS_D_EXCEPTION:
            // Generated D exception
            pti = (Object *)(exception_record->ExceptionInformation[0]);
            break;

        case STATUS_INTEGER_DIVIDE_BY_ZERO:
            pti = _d_create_exception_object(context, &_Class_9Exception, "Integer Divide by Zero");
            break;

        case STATUS_FLOAT_DIVIDE_BY_ZERO:
            pti = _d_create_exception_object(context, &_Class_9Exception, "Float Divide by Zero");
            break;

        case STATUS_ACCESS_VIOLATION:
            pti = 0;
            if (exception_record->NumberParameters >= 2) {
                unsigned accessType = (unsigned)exception_record->ExceptionInformation[0];
                unsigned accessAddr = exception_record->ExceptionInformation[1];
                char buffer[64];
                int bufferLen = 0;
                switch (accessType) {
                    case 0:
                        bufferLen = sprintf(buffer, "Access Violation - Read at address 0x%x", accessAddr);
                        break;
                    case 1:
                        bufferLen = sprintf(buffer, "Access Violation - Write at address 0x%x", accessAddr);
                        break;
                    case 8:
                        bufferLen = sprintf(buffer, "Access Violation - Data Execution Prevention violation at address 0x%x", accessAddr);
                        break;
                    default:
                        break;
                }
                if (bufferLen > 0) {
                    char* msg = gc_calloc(bufferLen+1, 2 /* NO_SCAN */);
                    if (msg != 0) {
                        memcpy(msg, buffer, bufferLen);
                        msg[bufferLen] = 0;
                        pti = _d_create_exception_object(context, &_Class_9Exception, msg);
                    }
                }
            }
            
            if (0 == pti) {
                pti = _d_create_exception_object(context, &_Class_9Exception, "Access Violation");
            }
            break;

        case STATUS_STACK_OVERFLOW:
            pti = _d_create_exception_object(context, &_Class_9Exception, "Stack Overflow");
            break;

        // convert all other exception codes into a Win32Exception
        default:
            pti = _d_create_exception_object(context, &_Class_9Exception, "Win32 Exception");
            break;
    }

    return pti;
}

/**************************************
 * Call finally blocks in the current stack frame until stop_index.
 * This is roughly equivalent to _local_unwind() for C in \src\win32\ehsup.c
 */

void __cdecl _d_local_unwind(struct DHandlerTable *handler_table,
        struct DEstablisherFrame *frame, int stop_index)
{
    struct DHandlerInfo *phi;
    struct DCatchInfo *pci;
    int i;

    // Set up a special exception handler to catch double-fault exceptions.
    __asm
    {
        push    dword ptr -1
        push    dword ptr 0
        push    offset _local_except_handler    // defined in src\win32\ehsup.c
        push    dword ptr fs:_except_list
        mov     FS:_except_list,ESP
    }

    for (i = frame->table_index; i != -1 && i != stop_index; i = phi->prev_index)
    {
        phi = &handler_table->handler_info[i];
        if (phi->finally_code)
        {
            // Note that it is unnecessary to adjust the ESP, as the finally block
            // accesses all items on the stack as relative to EBP.

            DWORD *catch_ebp = &frame->ebp;
            void *blockaddr = phi->finally_code;

            _asm
            {
                push    EBX
                mov     EBX,blockaddr
                push    EBP
                mov     EBP,catch_ebp
                call    EBX
                pop     EBP
                pop     EBX
            }
        }
    }

    _asm
    {
        pop     FS:_except_list
        add     ESP,12
    }
}

/***********************************
 * external version of the unwinder
 */

__declspec(naked) void __cdecl _d_local_unwind2()
{
    __asm
    {
        jmp     _d_local_unwind
    }
}

/***********************************
 * The frame handler, this is called for each frame that has been registered
 * in the OS except_list.
 * Input:
 *      EAX     the handler table for the frame
 */

EXCEPTION_DISPOSITION _d_monitor_handler(
            EXCEPTION_RECORD *exception_record,
            struct DEstablisherFrame *frame,
            CONTEXT context,
            void *dispatcher_context)
{
    if (exception_record->ExceptionFlags & EXCEPTION_UNWIND)
    {
        _d_monitorexit((Object *)frame->table_index);
    }
    else
    {
    }
    return ExceptionContinueSearch;
}

/***********************************
 */

void _d_monitor_prolog(void *x, void *y, Object *h)
{
    __asm
    {
        push    EAX
    }
    //printf("_d_monitor_prolog(x=%p, y=%p, h=%p)\n", x, y, h);
    _d_monitorenter(h);
    __asm
    {
        pop     EAX
    }
}

/***********************************
 */

void _d_monitor_epilog(void *x, void *y, Object *h)
{
    //printf("_d_monitor_epilog(x=%p, y=%p, h=%p)\n", x, y, h);
    __asm
    {
        push    EAX
        push    EDX
    }
    _d_monitorexit(h);
    __asm
    {
        pop     EDX
        pop     EAX
    }
}

#endif

/* ======================== linux =============================== */

#if linux

#include        "mars.h"

extern ClassInfo D9Exception7__ClassZ;

#define _Class_9Exception D9Exception7__ClassZ

typedef int (*fp_t)();   // function pointer in ambient memory model

struct DHandlerInfo
{
    unsigned offset;            // offset from function address to start of guarded section
    int prev_index;             // previous table index
    unsigned cioffset;          // offset to DCatchInfo data from start of table (!=0 if try-catch)
    void *finally_code;         // pointer to finally code to execute
                                // (!=0 if try-finally)
};

// Address of DHandlerTable, searched for by eh_finddata()

struct DHandlerTable
{
    void *fptr;                 // pointer to start of function
    unsigned espoffset;         // offset of ESP from EBP
    unsigned retoffset;         // offset from start of function to return code
    unsigned nhandlers;         // dimension of handler_info[]
    struct DHandlerInfo handler_info[1];
};

struct DCatchBlock
{
    ClassInfo *type;            // catch type
    unsigned bpoffset;          // EBP offset of catch var
    void *code;                 // catch handler code
};

// Create one of these for each try-catch
struct DCatchInfo
{
    unsigned ncatches;                  // number of catch blocks
    struct DCatchBlock catch_block[1];  // data for each catch block
};

// One of these is generated for each function with try-catch or try-finally

struct FuncTable
{
    void *fptr;                 // pointer to start of function
    struct DHandlerTable *handlertable; // eh data for this function
    unsigned size;              // size of function in bytes
};

extern struct FuncTable *table_start;
extern struct FuncTable *table_end;

void terminate()
{
//    _asm
//    {
//      hlt
//    }
}

/*******************************************
 * Given address that is inside a function,
 * figure out which function it is in.
 * Return DHandlerTable if there is one, NULL if not.
 */

struct DHandlerTable *__eh_finddata(void *address)
{
    struct FuncTable *ft;

    for (ft = (struct FuncTable *)table_start;
         ft < (struct FuncTable *)table_end;
         ft++)
    {
        if (ft->fptr <= address &&
            address < (void *)((char *)ft->fptr + ft->size))
        {
            return ft->handlertable;
        }
    }
    return NULL;
}


/******************************
 * Given EBP, find return address to caller, and caller's EBP.
 * Input:
 *   regbp       Value of EBP for current function
 *   *pretaddr   Return address
 * Output:
 *   *pretaddr   return address to caller
 * Returns:
 *   caller's EBP
 */

unsigned __eh_find_caller(unsigned regbp, unsigned *pretaddr)
{
    unsigned bp = *(unsigned *)regbp;

    if (bp)         // if not end of call chain
    {
        // Perform sanity checks on new EBP.
        // If it is screwed up, terminate() hopefully before we do more damage.
        if (bp <= regbp)
            // stack should grow to smaller values
            terminate();

        *pretaddr = *(unsigned *)(regbp + sizeof(int));
    }
    return bp;
}

/***********************************
 * Throw a D object.
 */

void __stdcall _d_throw(Object *h)
{
    unsigned regebp;

    //printf("_d_throw(h = %p, &h = %p)\n", h, &h);
    //printf("\tvptr = %p\n", *(void **)h);

    regebp = _EBP;

    while (1)           // for each function on the stack
    {
        struct DHandlerTable *handler_table;
        struct FuncTable *pfunc;
        struct DHandlerInfo *phi;
        unsigned retaddr;
        unsigned funcoffset;
        unsigned spoff;
        unsigned retoffset;
        int index;
        int dim;
        int ndx;
        int prev_ndx;

        regebp = __eh_find_caller(regebp,&retaddr);
        if (!regebp)
            // if end of call chain
            break;

        handler_table = __eh_finddata((void *)retaddr);   // find static data associated with function
        if (!handler_table)         // if no static data
        {
            continue;
        }
        funcoffset = (unsigned)handler_table->fptr;
        spoff = handler_table->espoffset;
        retoffset = handler_table->retoffset;

#ifdef DEBUG
        printf("retaddr = x%x\n",(unsigned)retaddr);
        printf("regebp=x%04x, funcoffset=x%04x, spoff=x%x, retoffset=x%x\n",
        regebp,funcoffset,spoff,retoffset);
#endif

        // Find start index for retaddr in static data
        dim = handler_table->nhandlers;
        index = -1;
        for (int i = 0; i < dim; i++)
        {
            phi = handler_table->handler_info.ptr + i;

            if ((unsigned)retaddr >= funcoffset + phi->offset)
                index = i;
        }

        // walk through handler table, checking each handler
        // with an index smaller than the current table_index
        for (ndx = index; ndx != -1; ndx = prev_ndx)
        {
            phi = handler_table->handler_info.ptr + ndx;
            prev_ndx = phi->prev_index;
            if (phi->cioffset)
            {
                // this is a catch handler (no finally)
                struct DCatchInfo *pci;
                int ncatches;
                int i;

                pci = (struct DCatchInfo *)((char *)handler_table + phi->cioffset);
                ncatches = pci->ncatches;
                for (i = 0; i < ncatches; i++)
                {
                    struct DCatchBlock *pcb;
                    ClassInfo *ci = **(ClassInfo ***)h;

                    pcb = pci->catch_block.ptr + i;

                    if (_d_isbaseof(ci, pcb->type))
                    {   // Matched the catch type, so we've found the handler.

                        // Initialize catch variable
                        *(void **)(regebp + (pcb->bpoffset)) = h;

                        // Jump to catch block. Does not return.
                        {
                            unsigned catch_esp;
                            fp_t catch_addr;

                            catch_addr = (fp_t)(pcb->code);
                            catch_esp = regebp - handler_table->espoffset - sizeof(fp_t);
                            _asm
                            {
                                mov     EAX,catch_esp
                                mov     ECX,catch_addr
                                mov     [EAX],ECX
                                mov     EBP,regebp
                                mov     ESP,EAX         // reset stack
                                ret                     // jump to catch block
                            }
                        }
                    }
                }
            }
            else if (phi->finally_code)
            {   // Call finally block
                // Note that it is unnecessary to adjust the ESP, as the finally block
                // accesses all items on the stack as relative to EBP.

                void *blockaddr = phi->finally_code;

                _asm
                {
                    push        EBX
                    mov         EBX,blockaddr
                    push        EBP
                    mov         EBP,regebp
                    call        EBX
                    pop         EBP
                    pop         EBX
                }
            }
        }
    }
}


#endif
