diff options
author | kyren <kerriganw@gmail.com> | 2017-06-24 22:26:35 -0400 |
---|---|---|
committer | kyren <kerriganw@gmail.com> | 2017-06-24 22:26:35 -0400 |
commit | b59b8cc23bd2ce510c754bdede5095ffd7904dcf (patch) | |
tree | d60ade970e385e31f34a39e867959735e91f0234 /src/util.rs | |
parent | 1fda34225e63a8b1cdc873ff6aa2c7d5f2038705 (diff) | |
download | mlua-b59b8cc23bd2ce510c754bdede5095ffd7904dcf.zip |
Different strategy..
Diffstat (limited to 'src/util.rs')
-rw-r--r-- | src/util.rs | 361 |
1 files changed, 192 insertions, 169 deletions
diff --git a/src/util.rs b/src/util.rs index c903921..d043248 100644 --- a/src/util.rs +++ b/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,49 +121,56 @@ 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() { - CStr::from_ptr(s).to_str().unwrap().to_owned() - } else { - "<unprintable error>".to_owned() - }; - ffi::lua_pop(state, 1); + if is_wrapped_error(state, -1) { + Err(pop_wrapped_error(state).unwrap()) - 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 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 { - 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_ERRMEM => { - // 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 - // (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 - // pcall protection for every lua function that might allocate. - // If lua does this in an unprotected context, it will abort - // anyway, so the best we can do right now is guarantee an abort - // even in a protected context. - println!("Lua memory error, aborting!"); - process::abort() - } - ffi::LUA_ERRGCMM => { - // This should be impossible, or at least is indicative of an - // internal bug. Similarly to LUA_ERRMEM, this could indicate a - // longjmp out of rust code, so we just abort. - println!("Lua error during __gc, aborting!"); - process::abort() - } - _ => panic!("unrecognized lua error code"), - }) + ffi::LUA_ERRERR => LuaError::ErrorError(err_string), + ffi::LUA_ERRMEM => { + // 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 + // (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 + // pcall protection for every lua function that might allocate. + // If lua does this in an unprotected context, it will abort + // anyway, so the best we can do right now is guarantee an abort + // even in a protected context. + println!("Lua memory error, aborting!"); + process::abort() + } + ffi::LUA_ERRGCMM => { + // This should be impossible, or at least is indicative of an + // internal bug. Similarly to LUA_ERRMEM, this could indicate a + // longjmp out of rust code, so we just abort. + println!("Lua error during __gc, aborting!"); + process::abort() + } + _ => 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 -// 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,24 +222,20 @@ 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 => { - 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())) - .to_str() - .unwrap() - .to_owned(); - push_wrapped_error(state, LuaError::CallbackError(traceback, Arc::new(error))); - } - WrappedErrorType::NotWrappedError => { - let s = ffi::lua_tolstring(state, 1, ptr::null_mut()); - if !s.is_null() { - ffi::luaL_traceback(state, state, s, 0); - } else { - ffi::luaL_traceback(state, state, cstr!("<unprintable lua error>"), 0); - } + 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())) + .to_str() + .unwrap() + .to_owned(); + push_wrapped_error(state, LuaError::CallbackError(traceback, Arc::new(error))); + } 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); + } else { + ffi::luaL_traceback(state, state, cstr!("<unprintable lua error>"), 0); } } 1 @@ -295,24 +256,20 @@ 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 => { - 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())) - .to_str() - .unwrap() - .to_owned(); - push_wrapped_error(from, LuaError::CallbackError(traceback, Arc::new(error))); - } - WrappedErrorType::NotWrappedError => { - let s = ffi::lua_tolstring(state, 1, ptr::null_mut()); - if !s.is_null() { - ffi::luaL_traceback(from, state, s, 0); - } else { - ffi::luaL_traceback(from, state, cstr!("<unprintable lua error>"), 0); - } + 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())) + .to_str() + .unwrap() + .to_owned(); + push_wrapped_error(from, LuaError::CallbackError(traceback, Arc::new(error))); + } 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); + } 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 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()); - 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); - } - } + let error = &*(userdata as *const WrappedError); + push_string(state, &error.0.to_string()); + 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); +} + +// 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()) + } } -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_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 { - ffi::lua_pop(state, 2); - return WrappedErrorType::NotWrappedError; - } - + let res = ffi::lua_rawequal(state, -1, -2) != 0; ffi::lua_pop(state, 2); + res +} + +// 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; + } - match &*(userdata as *const WrappedError) { - &WrappedError::Error(_) => WrappedErrorType::Error, - &WrappedError::Panic(_) => WrappedErrorType::Panic, + 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; } -unsafe fn get_error_metatable(state: *mut ffi::lua_State) -> c_int { +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; |