Different strategy..
This commit is contained in:
parent
1fda34225e
commit
b59b8cc23b
243
src/util.rs
243
src/util.rs
|
@ -8,7 +8,7 @@ use std::os::raw::{c_char, c_int, c_void};
|
|||
use std::panic::{catch_unwind, resume_unwind, UnwindSafe};
|
||||
|
||||
use ffi;
|
||||
use error::{LuaResult, LuaSyntaxError, LuaError};
|
||||
use error::{LuaResult, LuaSyntaxError, LuaUserDataError, LuaError};
|
||||
|
||||
macro_rules! cstr {
|
||||
($s:expr) => (
|
||||
|
@ -121,10 +121,16 @@ where
|
|||
pub unsafe fn handle_error(state: *mut ffi::lua_State, err: c_int) -> LuaResult<()> {
|
||||
if err == ffi::LUA_OK || err == ffi::LUA_YIELD {
|
||||
Ok(())
|
||||
} else if let Some(error) = pop_wrapped_error(state) {
|
||||
Err(error)
|
||||
} else {
|
||||
let err_string = if let Some(s) = ffi::lua_tolstring(state, -1, ptr::null_mut()).as_ref() {
|
||||
if is_wrapped_error(state, -1) {
|
||||
Err(pop_wrapped_error(state).unwrap())
|
||||
|
||||
} else if is_wrapped_panic(state, -1) {
|
||||
resume_unwind(pop_wrapped_panic(state).unwrap())
|
||||
|
||||
} else {
|
||||
let err_string =
|
||||
if let Some(s) = ffi::lua_tolstring(state, -1, ptr::null_mut()).as_ref() {
|
||||
CStr::from_ptr(s).to_str().unwrap().to_owned()
|
||||
} else {
|
||||
"<unprintable error>".to_owned()
|
||||
|
@ -165,6 +171,7 @@ pub unsafe fn handle_error(state: *mut ffi::lua_State, err: c_int) -> LuaResult<
|
|||
_ => panic!("unrecognized lua error code"),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn push_string(state: *mut ffi::lua_State, s: &str) {
|
||||
|
@ -185,11 +192,10 @@ pub unsafe extern "C" fn destructor<T>(state: *mut ffi::lua_State) -> c_int {
|
|||
}
|
||||
}
|
||||
|
||||
// In the context of a lua callback, this will call the given function and if
|
||||
// the given function returns an error, *or if the given function panics*, this
|
||||
// will result in a call to lua_error (a longjmp). Panics are wrapped in such a
|
||||
// way that when calling handle_error back on the rust side, it will resume the
|
||||
// panic.
|
||||
// In the context of a lua callback, this will call the given function and if the given function
|
||||
// returns an error, *or if the given function panics*, this will result in a call to lua_error (a
|
||||
// longjmp). The error or panic is wrapped in such a way that when calling pop_error back on
|
||||
// the rust side, it will resume the panic.
|
||||
pub unsafe fn callback_error<R, F>(state: *mut ffi::lua_State, f: F) -> R
|
||||
where
|
||||
F: FnOnce() -> LuaResult<R> + UnwindSafe,
|
||||
|
@ -207,47 +213,6 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
// Pushes a WrappedError::Error to the top of the stack
|
||||
pub unsafe fn push_wrapped_error(state: *mut ffi::lua_State, err: LuaError) {
|
||||
do_push_wrapped_error(state, WrappedError::Error(err));
|
||||
}
|
||||
|
||||
// Pushes a WrappedError::Panic to the top of the stack
|
||||
pub unsafe fn push_wrapped_panic(state: *mut ffi::lua_State, panic: Box<Any + Send>) {
|
||||
do_push_wrapped_error(state, WrappedError::Panic(Some(panic)));
|
||||
}
|
||||
|
||||
// If a WrappedError is at the top of the lua stack, pops it off. If the
|
||||
// WrappedError was a WrappedError::Panic, clears the lua stack and resumes the
|
||||
// panic, otherwise returns the WrappedError::Error. If the value at the top of
|
||||
// the stack was not a WrappedError, returns None and does not pop the value.
|
||||
pub unsafe fn pop_wrapped_error(state: *mut ffi::lua_State) -> Option<LuaError> {
|
||||
assert!(
|
||||
ffi::lua_gettop(state) > 0,
|
||||
"pop_wrapped_error called with nothing on the stack"
|
||||
);
|
||||
|
||||
if wrapped_error_type(state, -1) == WrappedErrorType::NotWrappedError {
|
||||
None
|
||||
} else {
|
||||
let userdata = ffi::lua_touserdata(state, -1);
|
||||
match &mut *(userdata as *mut WrappedError) {
|
||||
&mut WrappedError::Error(ref err) => {
|
||||
let err = err.clone();
|
||||
ffi::lua_pop(state, 1);
|
||||
Some(err)
|
||||
}
|
||||
&mut WrappedError::Panic(ref mut p) => {
|
||||
let p = p.take().unwrap_or_else(|| {
|
||||
Box::new("internal error: panic error used twice")
|
||||
});
|
||||
ffi::lua_settop(state, 0);
|
||||
resume_unwind(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ffi::lua_pcall with a message handler that gives a nice traceback. If the
|
||||
// caught error is actually a LuaError, will simply pass the error along. Does
|
||||
// not call checkstack, and uses 2 extra stack spaces.
|
||||
|
@ -257,9 +222,7 @@ pub unsafe fn pcall_with_traceback(
|
|||
nresults: c_int,
|
||||
) -> c_int {
|
||||
unsafe extern "C" fn message_handler(state: *mut ffi::lua_State) -> c_int {
|
||||
match wrapped_error_type(state, 1) {
|
||||
WrappedErrorType::Panic => {}
|
||||
WrappedErrorType::Error => {
|
||||
if is_wrapped_error(state, 1) {
|
||||
let error = pop_wrapped_error(state).unwrap();
|
||||
ffi::luaL_traceback(state, state, ptr::null(), 0);
|
||||
let traceback = CStr::from_ptr(ffi::lua_tolstring(state, -1, ptr::null_mut()))
|
||||
|
@ -267,8 +230,7 @@ pub unsafe fn pcall_with_traceback(
|
|||
.unwrap()
|
||||
.to_owned();
|
||||
push_wrapped_error(state, LuaError::CallbackError(traceback, Arc::new(error)));
|
||||
}
|
||||
WrappedErrorType::NotWrappedError => {
|
||||
} else if !is_wrapped_panic(state, 1) {
|
||||
let s = ffi::lua_tolstring(state, 1, ptr::null_mut());
|
||||
if !s.is_null() {
|
||||
ffi::luaL_traceback(state, state, s, 0);
|
||||
|
@ -276,7 +238,6 @@ pub unsafe fn pcall_with_traceback(
|
|||
ffi::luaL_traceback(state, state, cstr!("<unprintable lua error>"), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
1
|
||||
}
|
||||
|
||||
|
@ -295,9 +256,7 @@ pub unsafe fn resume_with_traceback(
|
|||
) -> c_int {
|
||||
let res = ffi::lua_resume(state, from, nargs);
|
||||
if res != ffi::LUA_OK && res != ffi::LUA_YIELD {
|
||||
match wrapped_error_type(state, 1) {
|
||||
WrappedErrorType::Panic => {}
|
||||
WrappedErrorType::Error => {
|
||||
if is_wrapped_error(state, 1) {
|
||||
let error = pop_wrapped_error(state).unwrap();
|
||||
ffi::luaL_traceback(from, state, ptr::null(), 0);
|
||||
let traceback = CStr::from_ptr(ffi::lua_tolstring(from, -1, ptr::null_mut()))
|
||||
|
@ -305,8 +264,7 @@ pub unsafe fn resume_with_traceback(
|
|||
.unwrap()
|
||||
.to_owned();
|
||||
push_wrapped_error(from, LuaError::CallbackError(traceback, Arc::new(error)));
|
||||
}
|
||||
WrappedErrorType::NotWrappedError => {
|
||||
} else if !is_wrapped_panic(state, 1) {
|
||||
let s = ffi::lua_tolstring(state, 1, ptr::null_mut());
|
||||
if !s.is_null() {
|
||||
ffi::luaL_traceback(from, state, s, 0);
|
||||
|
@ -315,14 +273,13 @@ pub unsafe fn resume_with_traceback(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
// A variant of pcall that does not allow lua to catch panic errors from callback_error
|
||||
pub unsafe extern "C" fn safe_pcall(state: *mut ffi::lua_State) -> c_int {
|
||||
if ffi::lua_pcall(state, ffi::lua_gettop(state) - 1, ffi::LUA_MULTRET, 0) != ffi::LUA_OK {
|
||||
if wrapped_error_type(state, -1) == WrappedErrorType::Panic {
|
||||
if is_wrapped_panic(state, -1) {
|
||||
ffi::lua_error(state);
|
||||
}
|
||||
}
|
||||
|
@ -332,7 +289,7 @@ pub unsafe extern "C" fn safe_pcall(state: *mut ffi::lua_State) -> c_int {
|
|||
// A variant of xpcall that does not allow lua to catch panic errors from callback_error
|
||||
pub unsafe extern "C" fn safe_xpcall(state: *mut ffi::lua_State) -> c_int {
|
||||
unsafe extern "C" fn xpcall_msgh(state: *mut ffi::lua_State) -> c_int {
|
||||
if wrapped_error_type(state, -1) == WrappedErrorType::Panic {
|
||||
if is_wrapped_panic(state, -1) {
|
||||
1
|
||||
} else {
|
||||
ffi::lua_pushvalue(state, ffi::lua_upvalueindex(1));
|
||||
|
@ -349,7 +306,7 @@ pub unsafe extern "C" fn safe_xpcall(state: *mut ffi::lua_State) -> c_int {
|
|||
|
||||
let res = ffi::lua_pcall(state, ffi::lua_gettop(state) - 2, ffi::LUA_MULTRET, 1);
|
||||
if res != ffi::LUA_OK {
|
||||
if wrapped_error_type(state, -1) == WrappedErrorType::Panic {
|
||||
if is_wrapped_panic(state, -1) {
|
||||
ffi::lua_error(state);
|
||||
}
|
||||
}
|
||||
|
@ -364,37 +321,21 @@ pub unsafe fn main_state(state: *mut ffi::lua_State) -> *mut ffi::lua_State {
|
|||
state
|
||||
}
|
||||
|
||||
static ERROR_METATABLE_REGISTRY_KEY: u8 = 0;
|
||||
|
||||
enum WrappedError {
|
||||
Error(LuaError),
|
||||
Panic(Option<Box<Any + Send>>),
|
||||
}
|
||||
|
||||
// Pushes the given error or panic as a wrapped error onto the stack
|
||||
unsafe fn do_push_wrapped_error(state: *mut ffi::lua_State, err: WrappedError) {
|
||||
// Wrapped errors have a __tostring metamethod and a 'backtrace' normal
|
||||
// method.
|
||||
pub struct WrappedError(LuaError);
|
||||
pub struct WrappedPanic(Option<Box<Any + Send>>);
|
||||
|
||||
// Pushes a WrappedError::Error to the top of the stack
|
||||
pub unsafe fn push_wrapped_error(state: *mut ffi::lua_State, err: LuaError) {
|
||||
unsafe extern "C" fn error_tostring(state: *mut ffi::lua_State) -> c_int {
|
||||
callback_error(state, || {
|
||||
if wrapped_error_type(state, -1) == WrappedErrorType::NotWrappedError {
|
||||
panic!("error metatable on wrong userdata, impossible")
|
||||
if !is_wrapped_error(state, -1) {
|
||||
return Err(LuaUserDataError::TypeMismatch.into());
|
||||
}
|
||||
|
||||
let userdata = ffi::lua_touserdata(state, -1);
|
||||
match &*(userdata as *const WrappedError) {
|
||||
&WrappedError::Error(ref error) => {
|
||||
push_string(state, &error.to_string());
|
||||
let error = &*(userdata as *const WrappedError);
|
||||
push_string(state, &error.0.to_string());
|
||||
ffi::lua_remove(state, -2);
|
||||
}
|
||||
&WrappedError::Panic(_) => {
|
||||
// This should be impossible, there should be no way for lua
|
||||
// to catch a panic error.
|
||||
push_string(state, "panic error");
|
||||
ffi::lua_remove(state, -2);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(1)
|
||||
})
|
||||
|
@ -405,7 +346,7 @@ unsafe fn do_push_wrapped_error(state: *mut ffi::lua_State, err: WrappedError) {
|
|||
let err_userdata = ffi::lua_newuserdata(state, mem::size_of::<WrappedError>()) as
|
||||
*mut WrappedError;
|
||||
|
||||
ptr::write(err_userdata, err);
|
||||
ptr::write(err_userdata, WrappedError(err));
|
||||
|
||||
get_error_metatable(state);
|
||||
if ffi::lua_isnil(state, -1) != 0 {
|
||||
|
@ -438,49 +379,131 @@ unsafe fn do_push_wrapped_error(state: *mut ffi::lua_State, err: WrappedError) {
|
|||
ffi::lua_setmetatable(state, -2);
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq)]
|
||||
enum WrappedErrorType {
|
||||
NotWrappedError,
|
||||
Error,
|
||||
Panic,
|
||||
// Pushes a WrappedError::Panic to the top of the stack
|
||||
pub unsafe fn push_wrapped_panic(state: *mut ffi::lua_State, panic: Box<Any + Send>) {
|
||||
ffi::luaL_checkstack(state, 2, ptr::null());
|
||||
|
||||
let panic_userdata = ffi::lua_newuserdata(state, mem::size_of::<WrappedPanic>()) as
|
||||
*mut WrappedPanic;
|
||||
|
||||
ptr::write(panic_userdata, WrappedPanic(Some(panic)));
|
||||
|
||||
get_panic_metatable(state);
|
||||
if ffi::lua_isnil(state, -1) != 0 {
|
||||
ffi::lua_pop(state, 1);
|
||||
|
||||
ffi::luaL_checkstack(state, 7, ptr::null());
|
||||
|
||||
ffi::lua_newtable(state);
|
||||
ffi::lua_pushlightuserdata(
|
||||
state,
|
||||
&PANIC_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
|
||||
);
|
||||
ffi::lua_pushvalue(state, -2);
|
||||
|
||||
push_string(state, "__gc");
|
||||
ffi::lua_pushcfunction(state, destructor::<WrappedPanic>);
|
||||
ffi::lua_settable(state, -3);
|
||||
|
||||
push_string(state, "__metatable");
|
||||
ffi::lua_pushboolean(state, 0);
|
||||
ffi::lua_settable(state, -3);
|
||||
|
||||
ffi::lua_settable(state, ffi::LUA_REGISTRYINDEX);
|
||||
}
|
||||
|
||||
ffi::lua_setmetatable(state, -2);
|
||||
}
|
||||
|
||||
unsafe fn wrapped_error_type(state: *mut ffi::lua_State, index: c_int) -> WrappedErrorType {
|
||||
// Pops a WrappedError off of the top of the stack, if it is a WrappedError. If
|
||||
// it is not a WrappedError, returns None and does not pop anything.
|
||||
pub unsafe fn pop_wrapped_error(state: *mut ffi::lua_State) -> Option<LuaError> {
|
||||
if ffi::lua_gettop(state) == 0 || !is_wrapped_error(state, -1) {
|
||||
None
|
||||
} else {
|
||||
let userdata = ffi::lua_touserdata(state, -1);
|
||||
let err = &*(userdata as *const WrappedError);
|
||||
Some(err.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
// Pops a WrappedError off of the top of the stack, if it is a WrappedError. If
|
||||
// it is not a WrappedError, returns None and does not pop anything.
|
||||
pub unsafe fn pop_wrapped_panic(state: *mut ffi::lua_State) -> Option<Box<Any + Send>> {
|
||||
if ffi::lua_gettop(state) == 0 || !is_wrapped_panic(state, -1) {
|
||||
None
|
||||
} else {
|
||||
let userdata = ffi::lua_touserdata(state, -1);
|
||||
let panic = &mut *(userdata as *mut WrappedPanic);
|
||||
panic.0.take()
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if the value at the given index is a WrappedError
|
||||
pub unsafe fn is_wrapped_error(state: *mut ffi::lua_State, index: c_int) -> bool {
|
||||
assert_ne!(
|
||||
ffi::lua_checkstack(state, 2),
|
||||
0,
|
||||
"somehow not enough stack space to check wrapped error type"
|
||||
"somehow not enough stack space to check if a value is a WrappedError"
|
||||
);
|
||||
|
||||
let index = ffi::lua_absindex(state, index);
|
||||
|
||||
let userdata = ffi::lua_touserdata(state, index);
|
||||
if userdata.is_null() {
|
||||
return WrappedErrorType::NotWrappedError;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ffi::lua_getmetatable(state, index) == 0 {
|
||||
return WrappedErrorType::NotWrappedError;
|
||||
return false;
|
||||
}
|
||||
|
||||
get_error_metatable(state);
|
||||
if ffi::lua_rawequal(state, -1, -2) == 0 {
|
||||
let res = ffi::lua_rawequal(state, -1, -2) != 0;
|
||||
ffi::lua_pop(state, 2);
|
||||
return WrappedErrorType::NotWrappedError;
|
||||
}
|
||||
|
||||
ffi::lua_pop(state, 2);
|
||||
|
||||
match &*(userdata as *const WrappedError) {
|
||||
&WrappedError::Error(_) => WrappedErrorType::Error,
|
||||
&WrappedError::Panic(_) => WrappedErrorType::Panic,
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
unsafe fn get_error_metatable(state: *mut ffi::lua_State) -> c_int {
|
||||
// Checks if the value at the given index is a WrappedPanic
|
||||
pub unsafe fn is_wrapped_panic(state: *mut ffi::lua_State, index: c_int) -> bool {
|
||||
assert_ne!(
|
||||
ffi::lua_checkstack(state, 2),
|
||||
0,
|
||||
"somehow not enough stack space to check if a value is a wrapped panic"
|
||||
);
|
||||
|
||||
let index = ffi::lua_absindex(state, index);
|
||||
|
||||
let userdata = ffi::lua_touserdata(state, index);
|
||||
if userdata.is_null() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ffi::lua_getmetatable(state, index) == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
get_panic_metatable(state);
|
||||
let res = ffi::lua_rawequal(state, -1, -2) != 0;
|
||||
ffi::lua_pop(state, 2);
|
||||
return res;
|
||||
}
|
||||
|
||||
pub unsafe fn get_error_metatable(state: *mut ffi::lua_State) -> c_int {
|
||||
ffi::lua_pushlightuserdata(
|
||||
state,
|
||||
&ERROR_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
|
||||
);
|
||||
ffi::lua_gettable(state, ffi::LUA_REGISTRYINDEX)
|
||||
}
|
||||
|
||||
pub unsafe fn get_panic_metatable(state: *mut ffi::lua_State) -> c_int {
|
||||
ffi::lua_pushlightuserdata(
|
||||
state,
|
||||
&PANIC_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
|
||||
);
|
||||
ffi::lua_gettable(state, ffi::LUA_REGISTRYINDEX)
|
||||
}
|
||||
|
||||
static ERROR_METATABLE_REGISTRY_KEY: u8 = 0;
|
||||
static PANIC_METATABLE_REGISTRY_KEY: u8 = 0;
|
||||
|
|
Loading…
Reference in New Issue