Add `ExpiredUserData` error and avoid what was previously a panic

Also make sure that panic messages clearly state that they are internal errors,
so people report them as a bug.  Since the only panics left are all internal
errors, just move the internal error message into the panic / assert macros.
This commit is contained in:
kyren 2017-12-04 02:46:57 -05:00
parent 80a98ef29c
commit 66a4e9a8e7
7 changed files with 71 additions and 36 deletions

View File

@ -31,6 +31,12 @@ pub enum Error {
/// This is an error, since `rlua` callbacks are FnMut an the functions can only be mutably /// This is an error, since `rlua` callbacks are FnMut an the functions can only be mutably
/// borrowed once. /// borrowed once.
RecursiveCallbackError, RecursiveCallbackError,
/// Lua code has accessed a [`UserData`] value that was already garbage collected
///
/// This can happen when a [`UserData`] has a custom `__gc` metamethod, this method resurrects
/// the [`UserData`], and then the [`UserData`] is subsequently accessed.
/// [`UserData`]: trait.UserData.html
ExpiredUserData,
/// A Rust value could not be converted to a Lua value. /// A Rust value could not be converted to a Lua value.
ToLuaConversionError { ToLuaConversionError {
/// Name of the Rust type that could not be converted. /// Name of the Rust type that could not be converted.
@ -116,6 +122,10 @@ impl fmt::Display for Error {
write!(fmt, "garbage collector error: {}", msg) write!(fmt, "garbage collector error: {}", msg)
} }
Error::RecursiveCallbackError => write!(fmt, "callback called recursively"), Error::RecursiveCallbackError => write!(fmt, "callback called recursively"),
Error::ExpiredUserData => write!(
fmt,
"access of userdata which has already been garbage collected"
),
Error::ToLuaConversionError { Error::ToLuaConversionError {
from, from,
to, to,
@ -157,6 +167,7 @@ impl StdError for Error {
Error::RuntimeError(_) => "runtime error", Error::RuntimeError(_) => "runtime error",
Error::GarbageCollectorError(_) => "garbage collector error", Error::GarbageCollectorError(_) => "garbage collector error",
Error::RecursiveCallbackError => "callback called recursively", Error::RecursiveCallbackError => "callback called recursively",
Error::ExpiredUserData => "access of userdata which has already been garbage collected",
Error::ToLuaConversionError { .. } => "conversion error to lua", Error::ToLuaConversionError { .. } => "conversion error to lua",
Error::FromLuaConversionError { .. } => "conversion error from lua", Error::FromLuaConversionError { .. } => "conversion error from lua",
Error::CoroutineInactive => "attempt to resume inactive coroutine", Error::CoroutineInactive => "attempt to resume inactive coroutine",

View File

@ -567,7 +567,7 @@ impl Lua {
&LUA_USERDATA_REGISTRY_KEY as *const u8 as *mut c_void, &LUA_USERDATA_REGISTRY_KEY as *const u8 as *mut c_void,
); );
ffi::lua_gettable(self.state, ffi::LUA_REGISTRYINDEX); ffi::lua_gettable(self.state, ffi::LUA_REGISTRYINDEX);
let registered_userdata = get_userdata::<HashMap<TypeId, c_int>>(self.state, -1); let registered_userdata = get_userdata::<HashMap<TypeId, c_int>>(self.state, -1)?;
ffi::lua_pop(self.state, 1); ffi::lua_pop(self.state, 1);
if let Some(table_id) = (*registered_userdata).get(&TypeId::of::<T>()) { if let Some(table_id) = (*registered_userdata).get(&TypeId::of::<T>()) {
@ -798,7 +798,7 @@ impl Lua {
ephemeral: true, ephemeral: true,
}; };
let func = get_userdata::<RefCell<Callback>>(state, ffi::lua_upvalueindex(1)); let func = get_userdata::<RefCell<Callback>>(state, ffi::lua_upvalueindex(1))?;
let mut func = (*func) let mut func = (*func)
.try_borrow_mut() .try_borrow_mut()
.map_err(|_| Error::RecursiveCallbackError)?; .map_err(|_| Error::RecursiveCallbackError)?;

View File

@ -16,14 +16,14 @@ macro_rules! lua_panic {
($state:expr, $msg:expr) => { ($state:expr, $msg:expr) => {
{ {
$crate::ffi::lua_settop($state, 0); $crate::ffi::lua_settop($state, 0);
panic!(concat!("rlua: ", $msg)); panic!(concat!("rlua internal error: ", $msg));
} }
}; };
($state:expr, $fmt:expr, $($arg:tt)+) => { ($state:expr, $fmt:expr, $($arg:tt)+) => {
{ {
$crate::ffi::lua_settop($state, 0); $crate::ffi::lua_settop($state, 0);
panic!(concat!("rlua: ", $fmt), $($arg)+); panic!(concat!("rlua internal error: ", $fmt), $($arg)+);
} }
}; };
} }
@ -40,14 +40,14 @@ macro_rules! lua_assert {
($state:expr, $cond:expr, $msg:expr) => { ($state:expr, $cond:expr, $msg:expr) => {
if !$cond { if !$cond {
$crate::ffi::lua_settop($state, 0); $crate::ffi::lua_settop($state, 0);
panic!(concat!("rlua: ", $msg)); panic!(concat!("rlua internal error: ", $msg));
} }
}; };
($state:expr, $cond:expr, $fmt:expr, $($arg:tt)+) => { ($state:expr, $cond:expr, $fmt:expr, $($arg:tt)+) => {
if !$cond { if !$cond {
$crate::ffi::lua_settop($state, 0); $crate::ffi::lua_settop($state, 0);
panic!(concat!("rlua: ", $fmt), $($arg)+); panic!(concat!("rlua internal error: ", $fmt), $($arg)+);
} }
}; };
} }

View File

@ -448,13 +448,20 @@ fn test_recursive_callback_error() {
Ok(()) Ok(())
}).unwrap(); }).unwrap();
lua.globals().set("f", f).unwrap(); lua.globals().set("f", f).unwrap();
assert!( match lua.globals()
lua.globals() .get::<_, Function>("f")
.get::<_, Function>("f") .unwrap()
.unwrap() .call::<_, ()>(false)
.call::<_, ()>(false) {
.is_err() Err(Error::CallbackError { ref cause, .. }) => match *cause.as_ref() {
) Error::CallbackError { ref cause, .. } => match *cause.as_ref() {
Error::RecursiveCallbackError { .. } => {}
ref other => panic!("incorrect result: {:?}", other),
},
ref other => panic!("incorrect result: {:?}", other),
},
other => panic!("incorrect result: {:?}", other),
};
} }
#[test] #[test]

View File

@ -142,6 +142,8 @@ impl<'lua> Thread<'lua> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::panic::catch_unwind;
use super::{Thread, ThreadStatus}; use super::{Thread, ThreadStatus};
use error::Error; use error::Error;
use function::Function; use function::Function;
@ -243,8 +245,8 @@ mod tests {
} }
#[test] #[test]
#[should_panic]
fn coroutine_panic() { fn coroutine_panic() {
// check that coroutines propagate panics correctly
let lua = Lua::new(); let lua = Lua::new();
let thrd_main = lua.create_function(|lua, ()| { let thrd_main = lua.create_function(|lua, ()| {
// whoops, 'main' has a wrong type // whoops, 'main' has a wrong type
@ -253,6 +255,10 @@ mod tests {
}).unwrap(); }).unwrap();
lua.globals().set("main", thrd_main.clone()).unwrap(); lua.globals().set("main", thrd_main.clone()).unwrap();
let thrd: Thread = lua.create_thread(thrd_main).unwrap(); let thrd: Thread = lua.create_thread(thrd_main).unwrap();
thrd.resume::<_, ()>(()).unwrap();
match catch_unwind(|| thrd.resume::<_, ()>(())) {
Ok(r) => panic!("coroutine panic not propagated, instead returned {:?}", r),
Err(_) => {}
}
} }
} }

View File

@ -358,7 +358,7 @@ impl<'lua> AnyUserData<'lua> {
{ {
unsafe { unsafe {
let lua = self.0.lua; let lua = self.0.lua;
stack_guard(lua.state, 0, move || { stack_err_guard(lua.state, 0, move || {
check_stack(lua.state, 3); check_stack(lua.state, 3);
lua.push_ref(lua.state, &self.0); lua.push_ref(lua.state, &self.0);
@ -379,7 +379,7 @@ impl<'lua> AnyUserData<'lua> {
ffi::lua_pop(lua.state, 3); ffi::lua_pop(lua.state, 3);
Err(Error::UserDataTypeMismatch) Err(Error::UserDataTypeMismatch)
} else { } else {
let res = func(&*get_userdata::<RefCell<T>>(lua.state, -3)); let res = func(&*get_userdata::<RefCell<T>>(lua.state, -3)?);
ffi::lua_pop(lua.state, 3); ffi::lua_pop(lua.state, 3);
res res
} }
@ -391,7 +391,7 @@ impl<'lua> AnyUserData<'lua> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{MetaMethod, UserData, UserDataMethods}; use super::{MetaMethod, UserData, UserDataMethods};
use error::ExternalError; use error::{Error, ExternalError};
use string::String; use string::String;
use function::Function; use function::Function;
use lua::Lua; use lua::Lua;
@ -505,7 +505,6 @@ mod tests {
} }
#[test] #[test]
#[should_panic]
fn test_expired_userdata() { fn test_expired_userdata() {
struct MyUserdata { struct MyUserdata {
id: u8, id: u8,
@ -526,7 +525,7 @@ mod tests {
globals.set("userdata", MyUserdata { id: 123 }).unwrap(); globals.set("userdata", MyUserdata { id: 123 }).unwrap();
} }
lua.eval::<()>( match lua.eval::<()>(
r#" r#"
local tbl = setmetatable({ local tbl = setmetatable({
userdata = userdata userdata = userdata
@ -541,7 +540,13 @@ mod tests {
hatch:access() hatch:access()
"#, "#,
None, None,
).unwrap(); ) {
Err(Error::CallbackError { cause, .. }) => match *cause {
Error::ExpiredUserData { .. } => {}
ref other => panic!("incorrect result: {}", other),
},
other => panic!("incorrect result: {:?}", other),
}
} }
#[test] #[test]

View File

@ -28,7 +28,7 @@ where
lua_assert!( lua_assert!(
state, state,
expected >= 0, expected >= 0,
"internal stack error: too many values would be popped" "too many stack values would be popped"
); );
let res = op(); let res = op();
@ -37,7 +37,7 @@ where
lua_assert!( lua_assert!(
state, state,
ffi::lua_gettop(state) == expected, ffi::lua_gettop(state) == expected,
"internal stack error: expected stack to be {}, got {}", "expected stack to be {}, got {}",
expected, expected,
top top
); );
@ -62,7 +62,7 @@ where
lua_assert!( lua_assert!(
state, state,
expected >= 0, expected >= 0,
"internal stack error: too many values would be popped" "too many stack values would be popped"
); );
let res = op(); let res = op();
@ -72,7 +72,7 @@ where
lua_assert!( lua_assert!(
state, state,
ffi::lua_gettop(state) == expected, ffi::lua_gettop(state) == expected,
"internal stack error: expected stack to be {}, got {}", "expected stack to be {}, got {}",
expected, expected,
top top
); );
@ -80,7 +80,7 @@ where
lua_assert!( lua_assert!(
state, state,
top >= expected, top >= expected,
"internal stack error: {} too many values popped", "{} too many stack values popped",
top - expected top - expected
); );
if top > expected { if top > expected {
@ -164,12 +164,13 @@ pub unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> Error {
if let Some(err) = pop_wrapped_error(state) { if let Some(err) = pop_wrapped_error(state) {
err err
} else if is_wrapped_panic(state, -1) { } else if is_wrapped_panic(state, -1) {
let panic = get_userdata::<WrappedPanic>(state, -1); let panic = get_userdata::<WrappedPanic>(state, -1)
.expect("WrappedPanic was somehow resurrected after garbage collection");
if let Some(p) = (*panic).0.take() { if let Some(p) = (*panic).0.take() {
ffi::lua_settop(state, 0); ffi::lua_settop(state, 0);
resume_unwind(p); resume_unwind(p);
} else { } else {
lua_panic!(state, "internal error: panic was resumed twice") lua_panic!(state, "panic was resumed twice")
} }
} else { } else {
let err_string = gc_guard(state, || { let err_string = gc_guard(state, || {
@ -205,7 +206,7 @@ pub unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> Error {
process::abort() process::abort()
} }
ffi::LUA_ERRGCMM => Error::GarbageCollectorError(err_string), ffi::LUA_ERRGCMM => Error::GarbageCollectorError(err_string),
_ => lua_panic!(state, "internal error: unrecognized lua error code"), _ => lua_panic!(state, "unrecognized lua error code"),
} }
} }
} }
@ -224,11 +225,14 @@ pub unsafe fn push_userdata<T>(state: *mut ffi::lua_State, t: T) -> Result<()> {
}) })
} }
pub unsafe fn get_userdata<T>(state: *mut ffi::lua_State, index: c_int) -> *mut T { // Returns None in the case that the userdata has already been garbage collected.
pub unsafe fn get_userdata<T>(state: *mut ffi::lua_State, index: c_int) -> Result<*mut T> {
let ud = ffi::lua_touserdata(state, index) as *mut Option<T>; let ud = ffi::lua_touserdata(state, index) as *mut Option<T>;
lua_assert!(state, !ud.is_null()); lua_assert!(state, !ud.is_null(), "userdata pointer is null");
lua_assert!(state, (*ud).is_some(), "access of expired userdata"); (*ud)
(*ud).as_mut().unwrap() .as_mut()
.map(|v| v as *mut T)
.ok_or(Error::ExpiredUserData)
} }
pub unsafe extern "C" fn userdata_destructor<T>(state: *mut ffi::lua_State) -> c_int { pub unsafe extern "C" fn userdata_destructor<T>(state: *mut ffi::lua_State) -> c_int {
@ -377,7 +381,8 @@ pub unsafe fn pop_wrapped_error(state: *mut ffi::lua_State) -> Option<Error> {
if ffi::lua_gettop(state) == 0 || !is_wrapped_error(state, -1) { if ffi::lua_gettop(state) == 0 || !is_wrapped_error(state, -1) {
None None
} else { } else {
let err = &*get_userdata::<WrappedError>(state, -1); let err = &*get_userdata::<WrappedError>(state, -1)
.expect("WrappedError was somehow resurrected after garbage collection");
let err = err.0.clone(); let err = err.0.clone();
ffi::lua_pop(state, 1); ffi::lua_pop(state, 1);
Some(err) Some(err)
@ -457,7 +462,8 @@ unsafe fn get_error_metatable(state: *mut ffi::lua_State) -> c_int {
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 is_wrapped_error(state, -1) {
let error = get_userdata::<WrappedError>(state, -1); let error = get_userdata::<WrappedError>(state, -1)
.expect("WrappedError was somehow resurrected after garbage collection");
let error_str = (*error).0.to_string(); let error_str = (*error).0.to_string();
gc_guard(state, || { gc_guard(state, || {
ffi::lua_pushlstring( ffi::lua_pushlstring(
@ -470,7 +476,7 @@ unsafe fn get_error_metatable(state: *mut ffi::lua_State) -> c_int {
Ok(1) Ok(1)
} else { } else {
panic!("internal error: userdata mismatch in Error metamethod"); panic!("userdata mismatch in Error metamethod");
} }
}) })
} }