Different strategy..

This commit is contained in:
kyren 2017-06-24 22:26:35 -04:00
parent 1fda34225e
commit b59b8cc23b
1 changed files with 194 additions and 171 deletions

View File

@ -8,7 +8,7 @@ use std::os::raw::{c_char, c_int, c_void};
use std::panic::{catch_unwind, resume_unwind, UnwindSafe}; use std::panic::{catch_unwind, resume_unwind, UnwindSafe};
use ffi; use ffi;
use error::{LuaResult, LuaSyntaxError, LuaError}; use error::{LuaResult, LuaSyntaxError, LuaUserDataError, LuaError};
macro_rules! cstr { macro_rules! cstr {
($s:expr) => ( ($s:expr) => (
@ -121,49 +121,56 @@ where
pub unsafe fn handle_error(state: *mut ffi::lua_State, err: c_int) -> LuaResult<()> { pub unsafe fn handle_error(state: *mut ffi::lua_State, err: c_int) -> LuaResult<()> {
if err == ffi::LUA_OK || err == ffi::LUA_YIELD { if err == ffi::LUA_OK || err == ffi::LUA_YIELD {
Ok(()) Ok(())
} else if let Some(error) = pop_wrapped_error(state) {
Err(error)
} else { } else {
let err_string = if let Some(s) = ffi::lua_tolstring(state, -1, ptr::null_mut()).as_ref() { if is_wrapped_error(state, -1) {
CStr::from_ptr(s).to_str().unwrap().to_owned() Err(pop_wrapped_error(state).unwrap())
} else {
"<unprintable error>".to_owned()
};
ffi::lua_pop(state, 1);
Err(match err { } else if is_wrapped_panic(state, -1) {
ffi::LUA_ERRRUN => LuaError::RuntimeError(err_string), resume_unwind(pop_wrapped_panic(state).unwrap())
ffi::LUA_ERRSYNTAX => {
// This seems terrible, but as far as I can tell, this is exactly what the stock lua } else {
// repl does. let err_string =
if err_string.ends_with("<eof>") { if let Some(s) = ffi::lua_tolstring(state, -1, ptr::null_mut()).as_ref() {
LuaSyntaxError::IncompleteStatement(err_string).into() CStr::from_ptr(s).to_str().unwrap().to_owned()
} else { } else {
LuaSyntaxError::Syntax(err_string).into() "<unprintable error>".to_owned()
};
ffi::lua_pop(state, 1);
Err(match err {
ffi::LUA_ERRRUN => LuaError::RuntimeError(err_string),
ffi::LUA_ERRSYNTAX => {
// This seems terrible, but as far as I can tell, this is exactly what the stock lua
// repl does.
if err_string.ends_with("<eof>") {
LuaSyntaxError::IncompleteStatement(err_string).into()
} else {
LuaSyntaxError::Syntax(err_string).into()
}
} }
} ffi::LUA_ERRERR => LuaError::ErrorError(err_string),
ffi::LUA_ERRERR => LuaError::ErrorError(err_string), ffi::LUA_ERRMEM => {
ffi::LUA_ERRMEM => { // This is not impossible to hit, but this library is not set up
// This is not impossible to hit, but this library is not set up // to handle this properly. Lua does a longjmp on out of memory
// to handle this properly. Lua does a longjmp on out of memory // (like all lua errors), but it can do this from a huge number
// (like all lua errors), but it can do this from a huge number // of lua functions, and it is extremely difficult to set up the
// of lua functions, and it is extremely difficult to set up the // pcall protection for every lua function that might allocate.
// pcall protection for every lua function that might allocate. // If lua does this in an unprotected context, it will abort
// If lua does this in an unprotected context, it will abort // anyway, so the best we can do right now is guarantee an abort
// anyway, so the best we can do right now is guarantee an abort // even in a protected context.
// even in a protected context. println!("Lua memory error, aborting!");
println!("Lua memory error, aborting!"); process::abort()
process::abort() }
} ffi::LUA_ERRGCMM => {
ffi::LUA_ERRGCMM => { // This should be impossible, or at least is indicative of an
// This should be impossible, or at least is indicative of an // internal bug. Similarly to LUA_ERRMEM, this could indicate a
// internal bug. Similarly to LUA_ERRMEM, this could indicate a // longjmp out of rust code, so we just abort.
// longjmp out of rust code, so we just abort. println!("Lua error during __gc, aborting!");
println!("Lua error during __gc, aborting!"); process::abort()
process::abort() }
} _ => panic!("unrecognized lua error code"),
_ => panic!("unrecognized lua error code"), })
}) }
} }
} }
@ -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 // In the context of a lua callback, this will call the given function and if the given function
// the given function returns an error, *or if the given function panics*, this // returns an error, *or if the given function panics*, this will result in a call to lua_error (a
// will result in a call to lua_error (a longjmp). Panics are wrapped in such a // longjmp). The error or panic is wrapped in such a way that when calling pop_error back on
// way that when calling handle_error back on the rust side, it will resume the // the rust side, it will resume the panic.
// panic.
pub unsafe fn callback_error<R, F>(state: *mut ffi::lua_State, f: F) -> R pub unsafe fn callback_error<R, F>(state: *mut ffi::lua_State, f: F) -> R
where where
F: FnOnce() -> LuaResult<R> + UnwindSafe, 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 // 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 // caught error is actually a LuaError, will simply pass the error along. Does
// not call checkstack, and uses 2 extra stack spaces. // not call checkstack, and uses 2 extra stack spaces.
@ -257,24 +222,20 @@ pub unsafe fn pcall_with_traceback(
nresults: c_int, nresults: c_int,
) -> c_int { ) -> c_int {
unsafe extern "C" fn message_handler(state: *mut ffi::lua_State) -> c_int { unsafe extern "C" fn message_handler(state: *mut ffi::lua_State) -> c_int {
match wrapped_error_type(state, 1) { if is_wrapped_error(state, 1) {
WrappedErrorType::Panic => {} let error = pop_wrapped_error(state).unwrap();
WrappedErrorType::Error => { ffi::luaL_traceback(state, state, ptr::null(), 0);
let error = pop_wrapped_error(state).unwrap(); let traceback = CStr::from_ptr(ffi::lua_tolstring(state, -1, ptr::null_mut()))
ffi::luaL_traceback(state, state, ptr::null(), 0); .to_str()
let traceback = CStr::from_ptr(ffi::lua_tolstring(state, -1, ptr::null_mut())) .unwrap()
.to_str() .to_owned();
.unwrap() push_wrapped_error(state, LuaError::CallbackError(traceback, Arc::new(error)));
.to_owned(); } else if !is_wrapped_panic(state, 1) {
push_wrapped_error(state, LuaError::CallbackError(traceback, Arc::new(error))); let s = ffi::lua_tolstring(state, 1, ptr::null_mut());
} if !s.is_null() {
WrappedErrorType::NotWrappedError => { ffi::luaL_traceback(state, state, s, 0);
let s = ffi::lua_tolstring(state, 1, ptr::null_mut()); } else {
if !s.is_null() { ffi::luaL_traceback(state, state, cstr!("<unprintable lua error>"), 0);
ffi::luaL_traceback(state, state, s, 0);
} else {
ffi::luaL_traceback(state, state, cstr!("<unprintable lua error>"), 0);
}
} }
} }
1 1
@ -295,24 +256,20 @@ pub unsafe fn resume_with_traceback(
) -> c_int { ) -> c_int {
let res = ffi::lua_resume(state, from, nargs); let res = ffi::lua_resume(state, from, nargs);
if res != ffi::LUA_OK && res != ffi::LUA_YIELD { if res != ffi::LUA_OK && res != ffi::LUA_YIELD {
match wrapped_error_type(state, 1) { if is_wrapped_error(state, 1) {
WrappedErrorType::Panic => {} let error = pop_wrapped_error(state).unwrap();
WrappedErrorType::Error => { ffi::luaL_traceback(from, state, ptr::null(), 0);
let error = pop_wrapped_error(state).unwrap(); let traceback = CStr::from_ptr(ffi::lua_tolstring(from, -1, ptr::null_mut()))
ffi::luaL_traceback(from, state, ptr::null(), 0); .to_str()
let traceback = CStr::from_ptr(ffi::lua_tolstring(from, -1, ptr::null_mut())) .unwrap()
.to_str() .to_owned();
.unwrap() push_wrapped_error(from, LuaError::CallbackError(traceback, Arc::new(error)));
.to_owned(); } else if !is_wrapped_panic(state, 1) {
push_wrapped_error(from, LuaError::CallbackError(traceback, Arc::new(error))); let s = ffi::lua_tolstring(state, 1, ptr::null_mut());
} if !s.is_null() {
WrappedErrorType::NotWrappedError => { ffi::luaL_traceback(from, state, s, 0);
let s = ffi::lua_tolstring(state, 1, ptr::null_mut()); } else {
if !s.is_null() { ffi::luaL_traceback(from, state, cstr!("<unprintable lua error>"), 0);
ffi::luaL_traceback(from, state, s, 0);
} else {
ffi::luaL_traceback(from, state, cstr!("<unprintable lua error>"), 0);
}
} }
} }
} }
@ -322,7 +279,7 @@ pub unsafe fn resume_with_traceback(
// A variant of pcall that does not allow lua to catch panic errors from callback_error // 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 { 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 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); 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 // 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 { 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 { 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 1
} else { } else {
ffi::lua_pushvalue(state, ffi::lua_upvalueindex(1)); 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); let res = ffi::lua_pcall(state, ffi::lua_gettop(state) - 2, ffi::LUA_MULTRET, 1);
if res != ffi::LUA_OK { if res != ffi::LUA_OK {
if wrapped_error_type(state, -1) == WrappedErrorType::Panic { if is_wrapped_panic(state, -1) {
ffi::lua_error(state); ffi::lua_error(state);
} }
} }
@ -364,37 +321,21 @@ pub unsafe fn main_state(state: *mut ffi::lua_State) -> *mut ffi::lua_State {
state state
} }
static ERROR_METATABLE_REGISTRY_KEY: u8 = 0; pub struct WrappedError(LuaError);
pub struct WrappedPanic(Option<Box<Any + Send>>);
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.
// 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 { unsafe extern "C" fn error_tostring(state: *mut ffi::lua_State) -> c_int {
callback_error(state, || { callback_error(state, || {
if wrapped_error_type(state, -1) == WrappedErrorType::NotWrappedError { if !is_wrapped_error(state, -1) {
panic!("error metatable on wrong userdata, impossible") return Err(LuaUserDataError::TypeMismatch.into());
} }
let userdata = ffi::lua_touserdata(state, -1); let userdata = ffi::lua_touserdata(state, -1);
match &*(userdata as *const WrappedError) { let error = &*(userdata as *const WrappedError);
&WrappedError::Error(ref error) => { push_string(state, &error.0.to_string());
push_string(state, &error.to_string()); ffi::lua_remove(state, -2);
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) 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 let err_userdata = ffi::lua_newuserdata(state, mem::size_of::<WrappedError>()) as
*mut WrappedError; *mut WrappedError;
ptr::write(err_userdata, err); ptr::write(err_userdata, WrappedError(err));
get_error_metatable(state); get_error_metatable(state);
if ffi::lua_isnil(state, -1) != 0 { 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); ffi::lua_setmetatable(state, -2);
} }
#[derive(Eq, PartialEq)] // Pushes a WrappedError::Panic to the top of the stack
enum WrappedErrorType { pub unsafe fn push_wrapped_panic(state: *mut ffi::lua_State, panic: Box<Any + Send>) {
NotWrappedError, ffi::luaL_checkstack(state, 2, ptr::null());
Error,
Panic, 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!( assert_ne!(
ffi::lua_checkstack(state, 2), ffi::lua_checkstack(state, 2),
0, 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 index = ffi::lua_absindex(state, index);
let userdata = ffi::lua_touserdata(state, index); let userdata = ffi::lua_touserdata(state, index);
if userdata.is_null() { if userdata.is_null() {
return WrappedErrorType::NotWrappedError; return false;
} }
if ffi::lua_getmetatable(state, index) == 0 { if ffi::lua_getmetatable(state, index) == 0 {
return WrappedErrorType::NotWrappedError; return false;
} }
get_error_metatable(state); 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); ffi::lua_pop(state, 2);
res
match &*(userdata as *const WrappedError) {
&WrappedError::Error(_) => WrappedErrorType::Error,
&WrappedError::Panic(_) => WrappedErrorType::Panic,
}
} }
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( ffi::lua_pushlightuserdata(
state, state,
&ERROR_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void, &ERROR_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
); );
ffi::lua_gettable(state, ffi::LUA_REGISTRYINDEX) 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;