Allow to catch rust panics via pcall

This commit is contained in:
Alex Orlenko 2019-09-28 01:44:15 +01:00
parent b23ee6a162
commit 0c230a3037
2 changed files with 33 additions and 86 deletions

View file

@ -19,8 +19,8 @@ use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods};
use crate::util::{
assert_stack, callback_error, check_stack, get_userdata, get_wrapped_error,
init_error_registry, init_userdata_metatable, main_state, pop_error, protect_lua,
protect_lua_closure, push_string, push_userdata, push_wrapped_error, safe_pcall, safe_xpcall,
userdata_destructor, StackGuard,
protect_lua_closure, push_string, push_userdata, push_wrapped_error, userdata_destructor,
StackGuard,
};
use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti, Value};
@ -62,22 +62,6 @@ impl Lua {
ffi::lua_rawset(state, ffi::LUA_REGISTRYINDEX);
// Override pcall and xpcall with versions that cannot be used to catch rust panics.
/*
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS);
ffi::lua_pushstring(state, cstr!("pcall"));
ffi::lua_pushcfunction(state, safe_pcall);
ffi::lua_rawset(state, -3);
ffi::lua_pushstring(state, cstr!("xpcall"));
ffi::lua_pushcfunction(state, safe_xpcall);
ffi::lua_rawset(state, -3);
ffi::lua_pop(state, 1);
*/
// Create ref stack thread and place it in the registry to prevent it from being garbage
// collected.

View file

@ -372,8 +372,7 @@ where
match catch_unwind(AssertUnwindSafe(|| f(nargs))) {
Ok(Ok(r)) => {
ffi::lua_rotate(state, 1, -1);
ffi::lua_pop(state, 1);
ffi::lua_remove(state, 1);
r
}
Ok(Err(err)) => {
@ -442,71 +441,6 @@ pub unsafe extern "C" fn error_traceback(state: *mut ffi::lua_State) -> c_int {
1
}
// 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 {
ffi::luaL_checkstack(state, 2, ptr::null());
let top = ffi::lua_gettop(state);
if top == 0 {
ffi::lua_pushstring(state, cstr!("not enough arguments to pcall"));
ffi::lua_error(state);
} else if ffi::lua_pcall(state, top - 1, ffi::LUA_MULTRET, 0) != ffi::LUA_OK {
if is_wrapped_panic(state, -1) {
ffi::lua_error(state);
}
ffi::lua_pushboolean(state, 0);
ffi::lua_insert(state, -2);
2
} else {
ffi::lua_pushboolean(state, 1);
ffi::lua_insert(state, 1);
ffi::lua_gettop(state)
}
}
// 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 {
ffi::luaL_checkstack(state, 2, ptr::null());
if is_wrapped_panic(state, -1) {
1
} else {
ffi::lua_pushvalue(state, ffi::lua_upvalueindex(1));
ffi::lua_insert(state, 1);
ffi::lua_call(state, ffi::lua_gettop(state) - 1, ffi::LUA_MULTRET);
ffi::lua_gettop(state)
}
}
ffi::luaL_checkstack(state, 2, ptr::null());
let top = ffi::lua_gettop(state);
if top < 2 {
ffi::lua_pushstring(state, cstr!("not enough arguments to xpcall"));
ffi::lua_error(state);
}
ffi::lua_pushvalue(state, 2);
ffi::lua_pushcclosure(state, xpcall_msgh, 1);
ffi::lua_copy(state, 1, 2);
ffi::lua_replace(state, 1);
let res = ffi::lua_pcall(state, ffi::lua_gettop(state) - 2, ffi::LUA_MULTRET, 1);
if res != ffi::LUA_OK {
if is_wrapped_panic(state, -1) {
ffi::lua_error(state);
}
ffi::lua_pushboolean(state, 0);
ffi::lua_insert(state, -2);
2
} else {
ffi::lua_pushboolean(state, 1);
ffi::lua_insert(state, 2);
ffi::lua_gettop(state) - 1
}
}
// Does not call lua_checkstack, uses 1 stack space.
pub unsafe fn main_state(state: *mut ffi::lua_State) -> *mut ffi::lua_State {
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_MAINTHREAD);
@ -574,6 +508,31 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) {
// kind of recursive error structure?)
let _ = write!(&mut (*err_buf), "{}", error);
Ok(err_buf)
} else if is_wrapped_panic(state, -1) {
let panic = get_userdata::<WrappedPanic>(state, -1);
if let Some(p) = (*panic).0.take() {
ffi::lua_pushlightuserdata(
state,
&ERROR_PRINT_BUFFER_KEY as *const u8 as *mut c_void,
);
ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX);
let err_buf = ffi::lua_touserdata(state, -1) as *mut String;
ffi::lua_pop(state, 2);
let error = if let Some(x) = p.downcast_ref::<&str>() {
x.to_string()
} else if let Some(x) = p.downcast_ref::<String>() {
x.to_string()
} else {
"panic".to_string()
};
(*err_buf).clear();
let _ = write!(&mut (*err_buf), "{}", error);
Ok(err_buf)
} else {
rlua_panic!("error during panic handling, panic was resumed twice")
}
} else {
// I'm not sure whether this is possible to trigger without bugs in rlua?
Err(Error::UserDataTypeMismatch)
@ -621,6 +580,10 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) {
ffi::lua_pushcfunction(state, userdata_destructor::<WrappedPanic>);
ffi::lua_rawset(state, -3);
ffi::lua_pushstring(state, cstr!("__tostring"));
ffi::lua_pushcfunction(state, error_tostring);
ffi::lua_rawset(state, -3);
ffi::lua_pushstring(state, cstr!("__metatable"));
ffi::lua_pushboolean(state, 0);
ffi::lua_rawset(state, -3);
@ -696,7 +659,7 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) {
}
struct WrappedError(pub Error);
struct WrappedPanic(pub Option<Box<dyn Any + Send>>);
struct WrappedPanic(pub Option<Box<dyn Any + Send + 'static>>);
// Converts the given lua value to a string in a reasonable format without causing a Lua error or
// panicking.