From 0c230a30370346d9583ac71704da1aa0d6693604 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 28 Sep 2019 01:44:15 +0100 Subject: [PATCH] Allow to catch rust panics via pcall --- src/lua.rs | 20 ++--------- src/util.rs | 99 +++++++++++++++++------------------------------------ 2 files changed, 33 insertions(+), 86 deletions(-) diff --git a/src/lua.rs b/src/lua.rs index 297aafa..ef5fc81 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -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. diff --git a/src/util.rs b/src/util.rs index 7704fa5..170e4c3 100644 --- a/src/util.rs +++ b/src/util.rs @@ -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::(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::() { + 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::); 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>); +struct WrappedPanic(pub Option>); // Converts the given lua value to a string in a reasonable format without causing a Lua error or // panicking.