summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkyren <kerriganw@gmail.com>2017-06-24 22:26:35 -0400
committerkyren <kerriganw@gmail.com>2017-06-24 22:26:35 -0400
commitb59b8cc23bd2ce510c754bdede5095ffd7904dcf (patch)
treed60ade970e385e31f34a39e867959735e91f0234
parent1fda34225e63a8b1cdc873ff6aa2c7d5f2038705 (diff)
downloadmlua-b59b8cc23bd2ce510c754bdede5095ffd7904dcf.zip
Different strategy..
-rw-r--r--src/util.rs361
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;