/*
 * Copyright (C) 2018-2020 Western Digital Corporation or its affiliates
 * Copyright (C) 2017-2018 Wearable, Inc.
 * Copyright (C) 2000-2012 leJOS Contributors
 * Copyright (C) 2000 Jose H. Solorzano and TinyVM Contributors
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 * 
 * This Source Code Form is “Incompatible With Secondary Licenses”,
 * as defined by the Mozilla Public License, v. 2.0.
 */

#include "types.h"
#include "trace.h"
#include "threads.h"
#include "constants.h"
#include "specialsignatures.h"
#include "specialclasses.h"
#include "exceptions.h"
#include "classes.h"
#include "language.h"
#include "configure.h"
#include "interpreter.h"
#include "memory.h"
#include "stack.h"
#include "platform_hooks.h"

void nvm_init_exceptions(nvm_state_t *pState)
{
    pState->out_of_memory_error = (Throwable*) new_object_for_class (pState, JAVA_LANG_OUTOFMEMORYERROR);
    protect_obj(pState->out_of_memory_error);
}

/**
 * Create an exception object. Only used by VM code.
 */
int nvm_throw_new_exception(nvm_state_t *pState, nvm_class_index_t class_index)
{
    Throwable *exception;
    Object *stackTrace;
    exception = (Throwable*) new_object_for_class(pState, class_index);
    if (exception == NULL) {
        // retry allocating the exception after a full gc
        wait_garbage_collect(pState, true, true);
        exception = (Throwable*) new_object_for_class(pState, class_index);
    }
    if (exception != NULL) {
        stackTrace = create_stack_trace(pState, pState->current_thread, NULL);
        if (stackTrace == NULL) {
            // retry allocating the stack trace after a full gc
            wait_garbage_collect(pState, true, true);
            // the gc might have clobbered the exception we allocated above
            exception = (Throwable*) new_object_for_class(pState, class_index);
            stackTrace = create_stack_trace(pState, pState->current_thread, NULL);
        }
    }
    if (exception == NULL || stackTrace == NULL) {
        // We are all of memory, clean things up and abort.
        // Force one more full GC just to clean up and maximize chances of success.
        wait_garbage_collect(pState, true, true);
        oom_hook(pState);
        return nvm_throw_exception(pState, pState->out_of_memory_error);
    }
    exception->stackTrace = obj2ref(stackTrace);
    return nvm_throw_exception(pState, exception);
}


/**
 * Create an exception.
 * Find the handler for this exception if one exists and set things up to
 * handle it. If no handler exists, call the debug interface and if that does
 * not deal with the exception call the default exception handler.
 * @return the exection action state.
 */
int nvm_throw_exception(nvm_state_t *pState, Throwable *exception)
{
    byte exceptionFrame = pState->current_thread->stackFrameIndex;
    uint16_t tempCurrentOffset;
    MethodRecord *tempMethodRecord = NULL;
    StackFrame *tempStackFrame;
    ExceptionRecord *exceptionRecord;
    byte numExceptionHandlers;
    MethodRecord *excepMethodRec = NULL;
    const byte *exceptionPc;
    
#if VERIFY
    assert (exception != NULL, EXCEPTIONS0);
#endif

    if (!exception) {
        return EXEC_CONTINUE;
    }
#if NVM_DEBUG_EXCEPTIONS
    printf("Throw exception %u\n", nvm_object_class_index(&exception->_super));
#endif
    if (pState->current_thread == NULL) {
        // No threads have started probably
        return EXEC_CONTINUE;
    } else if (nvm_object_class_index(&exception->_super) == JAVA_LANG_INTERRUPTEDEXCEPTION)
    {
        // Throwing an interrupted exception clears the flag
        pState->current_thread->interruptState = INTERRUPT_CLEARED;
    }
#if VERIFY
    assert (pState->current_thread->state > DEAD, EXCEPTIONS1);
#endif
    // abort the current instruction so things are in a consistant state
    pState->program_counter = getPc(pState);
    // record current state
    exceptionPc = pState->program_counter;
    tempStackFrame = current_stackframe(pState);
    update_stack_frame(pState, tempStackFrame);
    excepMethodRec = (MethodRecord*) ref2ptr(tempStackFrame->methodRecord);
    
LABEL_PROPAGATE:
    tempMethodRecord = (MethodRecord*) ref2ptr(tempStackFrame->methodRecord);
    
    if (!tempMethodRecord) {
        // FIXME: is this right?
        // We are probably calling the default exception handler already, or the thread has just started.
        return EXEC_CONTINUE;
    }
    
    exceptionRecord = (ExceptionRecord*)(void*)(get_binary_base(pState) + tempMethodRecord->exceptionTable);
    tempCurrentOffset = (uint16_t)(((uintptr_t) pState->program_counter) - (uintptr_t) (get_binary_base(pState) + tempMethodRecord->codeOffset));
    
    numExceptionHandlers = tempMethodRecord->numExceptionHandlers;
#if NVM_DEBUG_EXCEPTIONS
    printf("Num exception handlers=%d\n",numExceptionHandlers);
#endif
    while (numExceptionHandlers--)
    {
        if (exceptionRecord->start <= tempCurrentOffset
            && tempCurrentOffset <= exceptionRecord->end)
        {
            // Check if exception class applies
            if (instance_of (pState, (Object *)exception, exceptionRecord->class_index))
            {
                // Clear operand stack
                pState->program_stack_top = init_sp(pState, tempStackFrame, tempMethodRecord);
                // Push the exception object
                push_ref_cur (pState, ptr2ref(exception));
                // Jump to handler:
                pState->program_counter = get_binary_base(pState) + tempMethodRecord->codeOffset +
                exceptionRecord->handler;
#if NVM_DEBUG_EXCEPTIONS
                printf("Found exception handler\n");
#endif
                return EXEC_EXCEPTION;
            }
        }
        exceptionRecord++;
    }
    // No good handlers in current stack frame - go up.
    do_return (pState, 0);
    // Note: return takes care of synchronized methods.
    if (pState->current_thread->stackFrameIndex == 0)
    {
#if NVM_DEBUG_EXCEPTIONS
        printf("Thread is dead\n");
#endif
        if (nvm_object_class_index(&exception->_super) != JAVA_LANG_THREADDEATH)
        {
#if NVM_DEBUG_EXCEPTIONS
            printf("Handle uncaught exception\n");
#endif
            STACKWORD methodNo = (STACKWORD)(excepMethodRec - get_method_table(pState, get_class_record(pState, 0)));
            // Restore the stack and pc of the exception thread. This prevents
            // corruption of lower frames if we save the current state. The
            // thread is now dead so this should be safe. This also allows any debug
            // code to access the intact stack frames.
            pState->program_counter = exceptionPc;
            pState->current_thread->stackFrameIndex = exceptionFrame;
            tempCurrentOffset = (uint16_t)(((uintptr_t) exceptionPc) - (uintptr_t) (get_binary_base(pState) + excepMethodRec->codeOffset));
            return call_exception_handler(pState, exception, methodNo, tempCurrentOffset);
        }
        return EXEC_CONTINUE;
    }
    // After the return the address will point to the next, instruction, we need
    // to back it off to point to the actual caller...Note that this does not
    // need to point at the start of the instruction since the only use made of
    // PC here is to locate the exception handler, so we can get away with it
    // pointing into the middle...
    pState->program_counter--;
    tempStackFrame = current_stackframe(pState);
    goto LABEL_PROPAGATE; 
}


