use cesu8::{from_cesu8, to_cesu8};
use ducc::ExecSettings;
use error::{Error, ErrorKind, Result, RuntimeErrorCode};
use ffi;
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_void};
use std::{process, ptr, slice};
use std::sync::Once;
use types::AnyMap;
macro_rules! assert_stack {
($ctx:expr, $diff:expr, $body:block) => {
{
let initial_stack_height = $crate::ffi::duk_get_top($ctx);
let result = $body;
assert_eq!(initial_stack_height + $diff, $crate::ffi::duk_get_top($ctx));
result
}
}
}
macro_rules! cstr {
($s:expr) => (
concat!($s, "\0")
as *const str
as *const [::std::os::raw::c_char]
as *const ::std::os::raw::c_char
)
}
macro_rules! i8str {
($($b:expr),*) => ([$($b as i8),*, 0])
}
macro_rules! hidden_i8str {
($($b:expr),*) => (i8str!(-1, $($b as i8),*))
}
pub(crate) unsafe fn protect_duktape_closure<F, R>(
ctx: *mut ffi::duk_context,
num_args: ffi::duk_idx_t,
num_returns: ffi::duk_idx_t,
function: F,
) -> Result<R>
where
F: Fn(*mut ffi::duk_context) -> R,
{
struct Params<F, R> {
function: F,
result: Option<R>,
num_returns: ffi::duk_idx_t,
}
unsafe extern "C" fn do_call<F, R>(
ctx: *mut ffi::duk_context,
udata: *mut c_void,
) -> ffi::duk_ret_t
where
F: Fn(*mut ffi::duk_context) -> R,
{
let params = udata as *mut Params<F, R>;
(*params).result = Some(((*params).function)(ctx));
(*params).num_returns
}
let mut params = Params { function, result: None, num_returns };
let result = ffi::duk_safe_call(
ctx,
Some(do_call::<F, R>),
&mut params as *mut Params<F, R> as *mut c_void,
num_args,
num_returns + 1
);
if result == 0 {
ffi::duk_pop(ctx);
Ok(params.result.unwrap())
} else {
ffi::duk_pop_n(ctx, num_returns);
Err(pop_error(ctx))
}
}
const ERROR_KEY: [i8; 7] = hidden_i8str!('e', 'r', 'r', 'o', 'r');
unsafe extern "C" fn error_finalizer(ctx: *mut ffi::duk_context) -> ffi::duk_ret_t {
ffi::duk_require_stack(ctx, 1);
ffi::duk_get_prop_string(ctx, 0, ERROR_KEY.as_ptr() as *const _);
Box::from_raw(ffi::duk_get_pointer(ctx, -1) as *mut Error);
ffi::duk_pop(ctx);
ffi::duk_push_undefined(ctx);
ffi::duk_put_prop_string(ctx, 0, ERROR_KEY.as_ptr() as *const _);
0
}
pub(crate) unsafe fn push_error(ctx: *mut ffi::duk_context, error: Error) {
assert_stack!(ctx, 1, {
let desc = error.into_runtime_error_desc();
let cstr_msg = match desc.message {
Some(ref msg) => match CString::new(to_cesu8(msg)) {
Ok(msg) => Some(msg),
Err(_) => None,
},
None => None,
};
let cstr_name = match CString::new(to_cesu8(&desc.name)) {
Ok(name) => name,
Err(_) => CString::new("Error").unwrap(),
};
ffi::duk_require_stack(ctx, 2);
ffi::duk_push_error_object_raw(
ctx,
desc.code.to_duk_errcode(),
ptr::null_mut(),
0,
ptr::null_mut(),
);
ffi::duk_push_lstring(ctx, cstr_name.as_ptr(), cstr_name.as_bytes().len());
ffi::duk_put_prop_string(ctx, -2, cstr!("name"));
if let Some(cstr_msg) = cstr_msg {
ffi::duk_push_lstring(ctx, cstr_msg.as_ptr(), cstr_msg.as_bytes().len());
ffi::duk_put_prop_string(ctx, -2, cstr!("message"));
}
ffi::duk_push_pointer(ctx, Box::into_raw(desc.cause) as *mut _);
ffi::duk_put_prop_string(ctx, -2, ERROR_KEY.as_ptr() as *const _);
ffi::duk_push_c_function(ctx, Some(error_finalizer), 1);
ffi::duk_set_finalizer(ctx, -2);
})
}
pub(crate) unsafe fn pop_error(ctx: *mut ffi::duk_context) -> Error {
assert_stack!(ctx, -1, {
ffi::duk_require_stack(ctx, 1);
if ffi::duk_is_object_coercible(ctx, -1) == 0 {
ffi::duk_pop(ctx);
return Error {
kind: ErrorKind::RuntimeError {
code: RuntimeErrorCode::Error,
name: "Error".to_string(),
},
context: vec![],
};
}
ffi::duk_get_prop_string(ctx, -1, ERROR_KEY.as_ptr() as *const _);
let error_ptr = ffi::duk_get_pointer(ctx, -1) as *mut Error;
ffi::duk_pop(ctx);
ffi::duk_get_finalizer(ctx, -1);
let has_finalizer = ffi::duk_is_undefined(ctx, -1) == 0;
ffi::duk_pop(ctx);
if has_finalizer {
ffi::duk_push_undefined(ctx);
ffi::duk_set_finalizer(ctx, -2);
}
if !error_ptr.is_null() {
ffi::duk_push_undefined(ctx);
ffi::duk_put_prop_string(ctx, -2, ERROR_KEY.as_ptr() as *const _);
ffi::duk_pop(ctx);
return *Box::from_raw(error_ptr);
}
let code = RuntimeErrorCode::from_duk_errcode(ffi::duk_get_error_code(ctx, -1));
ffi::duk_get_prop_string(ctx, -1, cstr!("name"));
let name = get_string(ctx, -1);
ffi::duk_pop(ctx);
ffi::duk_get_prop_string(ctx, -1, cstr!("message"));
let message = get_string(ctx, -1);
ffi::duk_pop(ctx);
let name = match name.is_empty() {
false => name,
true => "Error".to_string(),
};
let message = match message.is_empty() {
false => vec![message],
true => vec![],
};
ffi::duk_pop(ctx);
Error {
kind: ErrorKind::RuntimeError { code, name },
context: message,
}
})
}
pub(crate) unsafe fn push_bytes(ctx: *mut ffi::duk_context, value: &[u8]) -> Result<()> {
assert_stack!(ctx, 1, {
protect_duktape_closure(ctx, 0, 1, |ctx| {
ffi::duk_require_stack(ctx, 1);
let len = value.len();
let data = ffi::duk_push_fixed_buffer(ctx, len);
ptr::copy(value.as_ptr(), data as *mut u8, len);
})
})
}
pub(crate) unsafe fn push_str(ctx: *mut ffi::duk_context, value: &str) -> Result<()> {
let string = CString::new(to_cesu8(value))
.map_err(|_| Error::to_js_conversion("&str", "string"))?;
assert_stack!(ctx, 1, {
protect_duktape_closure(ctx, 0, 1, |ctx| {
ffi::duk_require_stack(ctx, 1);
ffi::duk_push_lstring(ctx, string.as_ptr(), string.as_bytes().len());
})
})
}
unsafe fn get_string(ctx: *mut ffi::duk_context, idx: ffi::duk_idx_t) -> String {
let mut len = 0;
let string = ffi::duk_get_lstring_default(ctx, idx, &mut len, cstr!(""), 0);
if string.is_null() {
return String::new();
}
let bytes = slice::from_raw_parts(string as *const u8, len as usize);
match from_cesu8(bytes) {
Ok(string) => string.into_owned(),
Err(_) => String::new(),
}
}
const UDATA: [i8; 7] = hidden_i8str!('u', 'd', 'a', 't', 'a');
const ANYMAP: [i8; 8] = hidden_i8str!('a', 'n', 'y', 'm', 'a', 'p');
pub(crate) unsafe fn create_heap() -> *mut ffi::duk_context {
ensure_exec_timeout_check_exists();
let udata = Box::into_raw(Box::new(Udata { exec_settings: None }));
let ctx = ffi::duk_create_heap(None, None, None, udata as *mut _, Some(fatal_handler));
assert!(!ctx.is_null());
ffi::duk_require_stack(ctx, 1);
ffi::duk_push_pointer(ctx, udata as *mut _);
ffi::duk_put_global_string(ctx, UDATA.as_ptr() as *const _);
let any_map = Box::into_raw(Box::new(AnyMap::new()));
ffi::duk_push_pointer(ctx, any_map as *mut _);
ffi::duk_put_global_string(ctx, ANYMAP.as_ptr() as *const _);
ffi::duk_push_global_object(ctx);
ffi::duk_del_prop_string(ctx, -1, cstr!("Duktape"));
ffi::duk_pop(ctx);
ctx
}
pub(crate) unsafe fn get_udata(ctx: *mut ffi::duk_context) -> *mut Udata {
let _sg = StackGuard::new(ctx);
ffi::duk_require_stack(ctx, 1);
ffi::duk_get_global_string(ctx, UDATA.as_ptr() as *const _);
ffi::duk_get_pointer(ctx, -1) as *mut Udata
}
pub(crate) unsafe fn get_any_map(ctx: *mut ffi::duk_context) -> *mut AnyMap {
let _sg = StackGuard::new(ctx);
ffi::duk_require_stack(ctx, 1);
ffi::duk_get_global_string(ctx, ANYMAP.as_ptr() as *const _);
ffi::duk_get_pointer(ctx, -1) as *mut AnyMap
}
unsafe extern "C" fn fatal_handler(_udata: *mut c_void, msg: *const c_char) {
let msg = from_cesu8(CStr::from_ptr(msg).to_bytes())
.map(|c| c.into_owned())
.unwrap_or_else(|_| "failed to decode message".to_string());
eprintln!("fatal error from duktape: {}", msg);
process::abort();
}
pub(crate) struct Udata {
exec_settings: Option<ExecSettings>,
}
impl Udata {
pub fn set_exec_settings(&mut self, exec_settings: ExecSettings) {
self.exec_settings = Some(exec_settings);
}
pub fn clear_exec_settings(&mut self) {
self.exec_settings = None;
}
}
fn ensure_exec_timeout_check_exists() {
static INIT: Once = Once::new();
INIT.call_once(|| {
unsafe { ffi::ducc_set_exec_timeout_function(Some(timeout_func)); }
});
}
unsafe extern "C" fn timeout_func(udata: *mut c_void) -> ffi::duk_bool_t {
let udata = udata as *mut Udata;
assert!(!udata.is_null());
if let Some(ref settings) = (*udata).exec_settings {
if let Some(ref cancel_fn) = settings.cancel_fn {
return if cancel_fn() { 1 } else { 0 };
}
}
return 0;
}
pub(crate) struct StackGuard {
ctx: *mut ffi::duk_context,
top: ffi::duk_idx_t,
}
impl StackGuard {
pub unsafe fn new(ctx: *mut ffi::duk_context) -> StackGuard {
let top = ffi::duk_get_top(ctx);
StackGuard { ctx, top }
}
}
impl Drop for StackGuard {
fn drop(&mut self) {
let top = unsafe { ffi::duk_get_top(self.ctx) };
if top > self.top {
unsafe { ffi::duk_set_top(self.ctx, self.top); }
} else if top < self.top {
panic!("{} too many stack values popped", self.top - top);
}
}
}