Small improvements to WrappedError

Currently trying to figure out how to make LuaError a proper LuaUserDataType
This commit is contained in:
kyren 2017-06-24 21:44:27 -04:00
parent 3deb6df525
commit 1fda34225e
1 changed files with 106 additions and 120 deletions

View File

@ -121,12 +121,10 @@ 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 {
if is_wrapped_error(state, -1) { let err_string = if let Some(s) = ffi::lua_tolstring(state, -1, ptr::null_mut()).as_ref() {
Err(pop_wrapped_error(state))
} 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() CStr::from_ptr(s).to_str().unwrap().to_owned()
} else { } else {
"<unprintable error>".to_owned() "<unprintable error>".to_owned()
@ -167,7 +165,6 @@ pub unsafe fn handle_error(state: *mut ffi::lua_State, err: c_int) -> LuaResult<
_ => panic!("unrecognized lua error code"), _ => panic!("unrecognized lua error code"),
}) })
} }
}
} }
pub unsafe fn push_string(state: *mut ffi::lua_State, s: &str) { pub unsafe fn push_string(state: *mut ffi::lua_State, s: &str) {
@ -188,10 +185,11 @@ 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 // In the context of a lua callback, this will call the given function and if
// returns an error, *or if the given function panics*, this will result in a call to lua_error (a // the given function returns an error, *or if the given function panics*, this
// longjmp). The error or panic is wrapped in such a way that when calling pop_error back on // will result in a call to lua_error (a longjmp). Panics are wrapped in such a
// the rust side, it will resume the panic. // way that when calling handle_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 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,
@ -219,24 +217,25 @@ pub unsafe fn push_wrapped_panic(state: *mut ffi::lua_State, panic: Box<Any + Se
do_push_wrapped_error(state, WrappedError::Panic(Some(panic))); do_push_wrapped_error(state, WrappedError::Panic(Some(panic)));
} }
// Pops a WrappedError off of the top of the stack, if it is a WrappedError::Error, returns it, if // If a WrappedError is at the top of the lua stack, pops it off. If the
// it is a WrappedError::Panic, clears the current stack and panics. // WrappedError was a WrappedError::Panic, clears the lua stack and resumes the
pub unsafe fn pop_wrapped_error(state: *mut ffi::lua_State) -> LuaError { // 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!( assert!(
ffi::lua_gettop(state) > 0, ffi::lua_gettop(state) > 0,
"pop_wrapped_error called with nothing on the stack" "pop_wrapped_error called with nothing on the stack"
); );
assert!(
is_wrapped_error(state, -1),
"pop_wrapped_error called when the top of the stack is not a WrappedError"
);
if wrapped_error_type(state, -1) == WrappedErrorType::NotWrappedError {
None
} else {
let userdata = ffi::lua_touserdata(state, -1); let userdata = ffi::lua_touserdata(state, -1);
match &mut *(userdata as *mut WrappedError) { match &mut *(userdata as *mut WrappedError) {
&mut WrappedError::Error(ref err) => { &mut WrappedError::Error(ref err) => {
let err = err.clone(); let err = err.clone();
ffi::lua_pop(state, 1); ffi::lua_pop(state, 1);
err Some(err)
} }
&mut WrappedError::Panic(ref mut p) => { &mut WrappedError::Panic(ref mut p) => {
let p = p.take().unwrap_or_else(|| { let p = p.take().unwrap_or_else(|| {
@ -246,6 +245,7 @@ pub unsafe fn pop_wrapped_error(state: *mut ffi::lua_State) -> LuaError {
resume_unwind(p) 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
@ -257,9 +257,10 @@ 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 {
if is_wrapped_error(state, 1) { match wrapped_error_type(state, 1) {
if !is_panic_error(state, 1) { WrappedErrorType::Panic => {}
let error = pop_wrapped_error(state); WrappedErrorType::Error => {
let error = pop_wrapped_error(state).unwrap();
ffi::luaL_traceback(state, state, ptr::null(), 0); ffi::luaL_traceback(state, state, ptr::null(), 0);
let traceback = CStr::from_ptr(ffi::lua_tolstring(state, -1, ptr::null_mut())) let traceback = CStr::from_ptr(ffi::lua_tolstring(state, -1, ptr::null_mut()))
.to_str() .to_str()
@ -267,7 +268,7 @@ pub unsafe fn pcall_with_traceback(
.to_owned(); .to_owned();
push_wrapped_error(state, LuaError::CallbackError(traceback, Arc::new(error))); push_wrapped_error(state, LuaError::CallbackError(traceback, Arc::new(error)));
} }
} else { WrappedErrorType::NotWrappedError => {
let s = ffi::lua_tolstring(state, 1, ptr::null_mut()); let s = ffi::lua_tolstring(state, 1, ptr::null_mut());
if !s.is_null() { if !s.is_null() {
ffi::luaL_traceback(state, state, s, 0); ffi::luaL_traceback(state, state, s, 0);
@ -275,6 +276,7 @@ pub unsafe fn pcall_with_traceback(
ffi::luaL_traceback(state, state, cstr!("<unprintable lua error>"), 0); ffi::luaL_traceback(state, state, cstr!("<unprintable lua error>"), 0);
} }
} }
}
1 1
} }
@ -293,9 +295,10 @@ 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 {
if is_wrapped_error(state, 1) { match wrapped_error_type(state, 1) {
if !is_panic_error(state, 1) { WrappedErrorType::Panic => {}
let error = pop_wrapped_error(state); WrappedErrorType::Error => {
let error = pop_wrapped_error(state).unwrap();
ffi::luaL_traceback(from, state, ptr::null(), 0); ffi::luaL_traceback(from, state, ptr::null(), 0);
let traceback = CStr::from_ptr(ffi::lua_tolstring(from, -1, ptr::null_mut())) let traceback = CStr::from_ptr(ffi::lua_tolstring(from, -1, ptr::null_mut()))
.to_str() .to_str()
@ -303,7 +306,7 @@ pub unsafe fn resume_with_traceback(
.to_owned(); .to_owned();
push_wrapped_error(from, LuaError::CallbackError(traceback, Arc::new(error))); push_wrapped_error(from, LuaError::CallbackError(traceback, Arc::new(error)));
} }
} else { WrappedErrorType::NotWrappedError => {
let s = ffi::lua_tolstring(state, 1, ptr::null_mut()); let s = ffi::lua_tolstring(state, 1, ptr::null_mut());
if !s.is_null() { if !s.is_null() {
ffi::luaL_traceback(from, state, s, 0); ffi::luaL_traceback(from, state, s, 0);
@ -312,13 +315,14 @@ pub unsafe fn resume_with_traceback(
} }
} }
} }
}
res res
} }
// 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 is_panic_error(state, -1) { if wrapped_error_type(state, -1) == WrappedErrorType::Panic {
ffi::lua_error(state); ffi::lua_error(state);
} }
} }
@ -328,7 +332,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 is_panic_error(state, -1) { if wrapped_error_type(state, -1) == WrappedErrorType::Panic {
1 1
} else { } else {
ffi::lua_pushvalue(state, ffi::lua_upvalueindex(1)); ffi::lua_pushvalue(state, ffi::lua_upvalueindex(1));
@ -345,7 +349,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 is_panic_error(state, -1) { if wrapped_error_type(state, -1) == WrappedErrorType::Panic {
ffi::lua_error(state); ffi::lua_error(state);
} }
} }
@ -374,7 +378,7 @@ unsafe fn do_push_wrapped_error(state: *mut ffi::lua_State, err: WrappedError) {
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 !is_wrapped_error(state, -1) { if wrapped_error_type(state, -1) == WrappedErrorType::NotWrappedError {
panic!("error metatable on wrong userdata, impossible") panic!("error metatable on wrong userdata, impossible")
} }
@ -434,63 +438,45 @@ unsafe fn do_push_wrapped_error(state: *mut ffi::lua_State, err: WrappedError) {
ffi::lua_setmetatable(state, -2); ffi::lua_setmetatable(state, -2);
} }
// Checks if the error at the given index is a WrappedError::Panic #[derive(Eq, PartialEq)]
unsafe fn is_panic_error(state: *mut ffi::lua_State, index: c_int) -> bool { enum WrappedErrorType {
NotWrappedError,
Error,
Panic,
}
unsafe fn wrapped_error_type(state: *mut ffi::lua_State, index: c_int) -> WrappedErrorType {
assert_ne!( assert_ne!(
ffi::lua_checkstack(state, 2), ffi::lua_checkstack(state, 2),
0, 0,
"somehow not enough stack space to check if an error is panic" "somehow not enough stack space to check wrapped error type"
); );
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 false; return WrappedErrorType::NotWrappedError;
} }
if ffi::lua_getmetatable(state, index) == 0 { if ffi::lua_getmetatable(state, index) == 0 {
return false; return WrappedErrorType::NotWrappedError;
} }
get_error_metatable(state); get_error_metatable(state);
if ffi::lua_rawequal(state, -1, -2) == 0 { if ffi::lua_rawequal(state, -1, -2) == 0 {
ffi::lua_pop(state, 2); ffi::lua_pop(state, 2);
return false; return WrappedErrorType::NotWrappedError;
} }
ffi::lua_pop(state, 2); ffi::lua_pop(state, 2);
match &*(userdata as *const WrappedError) { match &*(userdata as *const WrappedError) {
&WrappedError::Error(_) => false, &WrappedError::Error(_) => WrappedErrorType::Error,
&WrappedError::Panic(_) => true, &WrappedError::Panic(_) => WrappedErrorType::Panic,
} }
} }
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 if an error is rrapped"
);
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_error_metatable(state);
let res = ffi::lua_rawequal(state, -1, -2) != 0;
ffi::lua_pop(state, 2);
res
}
unsafe fn get_error_metatable(state: *mut ffi::lua_State) -> c_int { unsafe fn get_error_metatable(state: *mut ffi::lua_State) -> c_int {
ffi::lua_pushlightuserdata( ffi::lua_pushlightuserdata(
state, state,