//! Unwinding implementation of top of native Win64 SEH,
//! however the unwind handler data (aka LSDA) uses GCC-compatible encoding.

#![allow(nonstandard_style)]
#![allow(private_no_mangle_fns)]

use alloc::boxed::Box;

use core::any::Any;
use core::intrinsics;
use core::ptr;
use crate::dwarf::eh::{EHContext, EHAction, find_eh_action};
use crate::windows as c;

// Define our exception codes:
// according to http://msdn.microsoft.com/en-us/library/het71c37(v=VS.80).aspx,
//    [31:30] = 3 (error), 2 (warning), 1 (info), 0 (success)
//    [29]    = 1 (user-defined)
//    [28]    = 0 (reserved)
// we define bits:
//    [24:27] = type
//    [0:23]  = magic
const ETYPE: c::DWORD = 0b1110_u32 << 28;
const MAGIC: c::DWORD = 0x525354; // "RST"

const RUST_PANIC: c::DWORD = ETYPE | (1 << 24) | MAGIC;

#[repr(C)]
struct PanicData {
    data: Box<dyn Any + Send>,
}

pub unsafe fn panic(data: Box<dyn Any + Send>) -> u32 {
    let panic_ctx = Box::new(PanicData { data });
    let params = [Box::into_raw(panic_ctx) as c::ULONG_PTR];
    c::RaiseException(RUST_PANIC,
                      c::EXCEPTION_NONCONTINUABLE,
                      params.len() as c::DWORD,
                      &params as *const c::ULONG_PTR);
    u32::max_value()
}

pub fn payload() -> *mut u8 {
    ptr::null_mut()
}

pub unsafe fn cleanup(ptr: *mut u8) -> Box<dyn Any + Send> {
    let panic_ctx = Box::from_raw(ptr as *mut PanicData);
    return panic_ctx.data;
}

// SEH doesn't support resuming unwinds after calling a landing pad like
// libunwind does. For this reason, MSVC compiler outlines landing pads into
// separate functions that can be called directly from the personality function
// but are nevertheless able to find and modify stack frame of the "parent"
// function.
//
// Since this cannot be done with libdwarf-style landing pads,
// rust_eh_personality instead catches RUST_PANICs, runs the landing pad, then
// reraises the exception.
//
// Note that it makes certain assumptions about the exception:
//
// 1. That RUST_PANIC is non-continuable, so no lower stack frame may choose to
//    resume execution.
// 2. That the first parameter of the exception is a pointer to an extra data
//    area (PanicData).
// Since these assumptions do not generally hold true for foreign exceptions
// (system faults, C++ exceptions, etc), we make no attempt to invoke our
// landing pads (and, thus, destructors!) for anything other than RUST_PANICs.
// This is considered acceptable, because the behavior of throwing exceptions
// through a C ABI boundary is undefined.

#[lang = "eh_personality"]
#[cfg(not(test))]
unsafe extern "C" fn rust_eh_personality(exceptionRecord: *mut c::EXCEPTION_RECORD,
                                         establisherFrame: c::LPVOID,
                                         contextRecord: *mut c::CONTEXT,
                                         dispatcherContext: *mut c::DISPATCHER_CONTEXT)
                                         -> c::EXCEPTION_DISPOSITION {
    let er = &*exceptionRecord;
    let dc = &*dispatcherContext;

    if er.ExceptionFlags & c::EXCEPTION_UNWIND == 0 {
        // we are in the dispatch phase
        if er.ExceptionCode == RUST_PANIC {
            if let Some(lpad) = find_landing_pad(dc) {
                c::RtlUnwindEx(establisherFrame,
                               lpad as c::LPVOID,
                               exceptionRecord,
                               er.ExceptionInformation[0] as c::LPVOID, // pointer to PanicData
                               contextRecord,
                               dc.HistoryTable);
            }
        }
    }
    c::ExceptionContinueSearch
}

#[lang = "eh_unwind_resume"]
#[unwind(allowed)]
unsafe extern "C" fn rust_eh_unwind_resume(panic_ctx: c::LPVOID) -> ! {
    let params = [panic_ctx as c::ULONG_PTR];
    c::RaiseException(RUST_PANIC,
                      c::EXCEPTION_NONCONTINUABLE,
                      params.len() as c::DWORD,
                      &params as *const c::ULONG_PTR);
    intrinsics::abort();
}

unsafe fn find_landing_pad(dc: &c::DISPATCHER_CONTEXT) -> Option<usize> {
    let eh_ctx = EHContext {
        // The return address points 1 byte past the call instruction,
        // which could be in the next IP range in LSDA range table.
        ip: dc.ControlPc as usize - 1,
        func_start: dc.ImageBase as usize + (*dc.FunctionEntry).BeginAddress as usize,
        get_text_start: &|| dc.ImageBase as usize,
        get_data_start: &|| unimplemented!(),
    };
    match find_eh_action(dc.HandlerData, &eh_ctx) {
        Err(_) |
        Ok(EHAction::None) => None,
        Ok(EHAction::Cleanup(lpad)) |
        Ok(EHAction::Catch(lpad)) => Some(lpad),
        Ok(EHAction::Terminate) => intrinsics::abort(),
    }
}
