use std::any::TypeId;
use std::panic::{catch_unwind, AssertUnwindSafe};
use crate::error::{Error, Result};
use crate::function::Function;
use crate::multi::MultiValue;
use crate::state::Lua;
use crate::sys::*;
#[repr(C)]
pub(crate) struct WrappedErrorHeader {
pub(crate) type_id: TypeId,
}
#[repr(C)]
struct WrappedError {
type_id: TypeId,
error: Box<Error>,
}
struct WrappedErrorTag;
pub(crate) fn wrapped_error_tag() -> TypeId {
TypeId::of::<WrappedErrorTag>()
}
unsafe extern "C" fn wrapped_error_dtor(ptr: *mut c_void) {
if !ptr.is_null() {
unsafe { core::ptr::drop_in_place(ptr as *mut WrappedError) };
}
}
pub(crate) fn is_structured(err: &Error) -> bool {
match err {
Error::CallbackDestructed | Error::UserDataDestructed | Error::RecursiveMutCallback => true,
Error::CallbackError { cause, .. } => is_structured(cause),
_ => false,
}
}
pub(crate) unsafe fn raise_structured_error(state: *mut lua_State, err: Error) -> c_int {
unsafe {
let storage = lua_newuserdatadtor(
state,
core::mem::size_of::<WrappedError>(),
Some(wrapped_error_dtor),
);
if storage.is_null() {
return raise_lua_error(state, &err.to_string());
}
core::ptr::write(
storage as *mut WrappedError,
WrappedError {
type_id: wrapped_error_tag(),
error: Box::new(err),
},
);
lua_error(state) }
}
pub(crate) unsafe fn recover_wrapped_error(state: *mut lua_State, idx: c_int) -> Option<Error> {
unsafe {
if lua_type(state, idx) != ttype::USERDATA {
return None;
}
let ptr = lua_touserdata(state, idx);
if ptr.is_null() {
return None;
}
let header = &*(ptr as *const WrappedErrorHeader);
if header.type_id != wrapped_error_tag() {
return None;
}
let wrapped = &*(ptr as *const WrappedError);
Some((*wrapped.error).clone())
}
}
#[cfg(feature = "send")]
pub(crate) type BoxedCallback = Box<dyn Fn(&Lua, MultiValue) -> Result<MultiValue> + Send>;
#[cfg(not(feature = "send"))]
pub(crate) type BoxedCallback = Box<dyn Fn(&Lua, MultiValue) -> Result<MultiValue>>;
unsafe extern "C" fn callback_dtor(ptr: *mut c_void) {
if !ptr.is_null() {
let bc = ptr as *mut BoxedCallback;
unsafe { core::ptr::drop_in_place(bc) };
}
}
unsafe fn trampoline(state: *mut lua_State) -> c_int {
unsafe {
let ud = lua_touserdata(state, lua_upvalueindex(1));
if ud.is_null() {
return raise_lua_error(state, "luaur-rt: missing callback upvalue");
}
let callback = &*(ud as *const BoxedCallback);
let lua = Lua::from_borrowed(state);
let nargs = lua_gettop(state);
let args = match collect_args(&lua, nargs) {
Ok(a) => a,
Err(e) => return raise_lua_error(state, &e.to_string()),
};
let outcome: std::thread::Result<Result<MultiValue>> =
catch_unwind(AssertUnwindSafe(|| callback(&lua, args)));
match outcome {
Ok(Ok(results)) => {
let n = results.len() as c_int;
if lua_checkstack(state, n.max(1)) == 0 {
return raise_lua_error(state, "too many results to return to Lua");
}
for v in results.iter() {
if let Err(e) = lua.push_value(v) {
return raise_lua_error(state, &e.to_string());
}
}
n
}
Ok(Err(err)) => {
if is_structured(&err) {
raise_structured_error(state, err)
} else {
raise_lua_error(state, &err.to_string())
}
}
Err(panic_payload) => {
let msg = panic_message(&panic_payload);
raise_lua_error(state, &format!("rust panic: {msg}"))
}
}
}
}
fn trampoline_ptr() -> lua_CFunction {
Some(trampoline)
}
unsafe fn collect_args(lua: &Lua, nargs: c_int) -> Result<MultiValue> {
let mut m = MultiValue::with_capacity(nargs.max(0) as usize);
for i in 1..=nargs {
m.push_back(lua.value_from_stack(i)?);
}
Ok(m)
}
unsafe fn raise_lua_error(state: *mut lua_State, msg: &str) -> c_int {
unsafe {
lua_pushlstring(state, msg.as_ptr() as *const c_char, msg.len());
lua_error(state) }
}
fn panic_message(payload: &Box<dyn std::any::Any + Send>) -> String {
if let Some(s) = payload.downcast_ref::<&str>() {
(*s).to_string()
} else if let Some(s) = payload.downcast_ref::<String>() {
s.clone()
} else {
"unknown panic".to_string()
}
}
pub(crate) fn create_callback_function(lua: &Lua, callback: BoxedCallback) -> Result<Function> {
let state = lua.state();
unsafe {
let storage = lua_newuserdatadtor(
state,
core::mem::size_of::<BoxedCallback>(),
Some(callback_dtor),
);
if storage.is_null() {
return Err(Error::runtime(
"luaur-rt: failed to allocate callback userdata",
));
}
core::ptr::write(storage as *mut BoxedCallback, callback);
lua_pushcclosurek(
state,
trampoline_ptr(),
c"luaur-rt-callback".as_ptr(),
1, None,
);
Ok(Function::from_ref(lua.pop_ref()))
}
}
pub(crate) fn destruct_callback(func: &Function) {
let lua = func.lua();
let state = lua.state();
unsafe {
func.push_to_stack();
let name = lua_getupvalue(state, -1, 1);
if name.is_null() {
lua_pop(state, 1);
return;
}
let ud = lua_touserdata(state, -1);
if !ud.is_null() {
let slot = ud as *mut BoxedCallback;
let sentinel: BoxedCallback = Box::new(|_lua, _args| Err(Error::CallbackDestructed));
let old = core::ptr::replace(slot, sentinel);
drop(old);
}
lua_pop(state, 2);
}
}