diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/ffi/safe.rs | 2 | ||||
-rw-r--r-- | src/ffi/shim/shim.c | 67 | ||||
-rw-r--r-- | src/lib.rs | 2 | ||||
-rw-r--r-- | src/lua.rs | 74 | ||||
-rw-r--r-- | src/prelude.rs | 2 |
5 files changed, 140 insertions, 7 deletions
diff --git a/src/ffi/safe.rs b/src/ffi/safe.rs index ee70316..77931dd 100644 --- a/src/ffi/safe.rs +++ b/src/ffi/safe.rs @@ -22,6 +22,8 @@ extern "C" { pub fn meta_newindex_impl(state: *mut lua_State) -> c_int; pub fn bind_call_impl(state: *mut lua_State) -> c_int; pub fn error_traceback(state: *mut lua_State) -> c_int; + pub fn lua_nopanic_pcall(state: *mut lua_State) -> c_int; + pub fn lua_nopanic_xpcall(state: *mut lua_State) -> c_int; fn lua_gc_s(L: *mut lua_State) -> c_int; fn luaL_ref_s(L: *mut lua_State) -> c_int; diff --git a/src/ffi/shim/shim.c b/src/ffi/shim/shim.c index f88fe2d..6309970 100644 --- a/src/ffi/shim/shim.c +++ b/src/ffi/shim/shim.c @@ -440,3 +440,70 @@ int error_traceback_s(lua_State *L) { lua_pop(L, 1); return error_traceback(L1); } + +// A `pcall` implementation that does not allow Lua to catch Rust panics. +// Instead, panics automatically resumed. +int lua_nopanic_pcall(lua_State *state) { + luaL_checkstack(state, 2, NULL); + + int top = lua_gettop(state); + if (top == 0) { + lua_pushstring(state, "not enough arguments to pcall"); + lua_error(state); + } + + if (lua_pcall(state, top - 1, LUA_MULTRET, 0) == LUA_OK) { + lua_pushboolean(state, 1); + lua_insert(state, 1); + return lua_gettop(state); + } + + if (is_wrapped_struct(state, -1, MLUA_WRAPPED_PANIC_KEY)) { + lua_error(state); + } + lua_pushboolean(state, 0); + lua_insert(state, -2); + return 2; +} + +// A `xpcall` implementation that does not allow Lua to catch Rust panics. +// Instead, panics automatically resumed. + +static int xpcall_msgh(lua_State *state) { + luaL_checkstack(state, 2, NULL); + if (is_wrapped_struct(state, -1, MLUA_WRAPPED_PANIC_KEY)) { + return 1; + } + lua_pushvalue(state, lua_upvalueindex(1)); + lua_insert(state, 1); + lua_call(state, lua_gettop(state) - 1, LUA_MULTRET); + return lua_gettop(state); +} + +int lua_nopanic_xpcall(lua_State *state) { + luaL_checkstack(state, 2, NULL); + + int top = lua_gettop(state); + if (top < 2) { + lua_pushstring(state, "not enough arguments to xpcall"); + lua_error(state); + } + + lua_pushvalue(state, 2); + lua_pushcclosure(state, xpcall_msgh, 1); + lua_copy(state, 1, 2); + lua_replace(state, 1); + + if (lua_pcall(state, lua_gettop(state) - 2, LUA_MULTRET, 1) == LUA_OK) { + lua_pushboolean(state, 1); + lua_insert(state, 2); + return lua_gettop(state) - 1; + } + + if (is_wrapped_struct(state, -1, MLUA_WRAPPED_PANIC_KEY)) { + lua_error(state); + } + lua_pushboolean(state, 0); + lua_insert(state, -2); + return 2; +} @@ -103,7 +103,7 @@ pub use crate::ffi::lua_State; pub use crate::error::{Error, ExternalError, ExternalResult, Result}; pub use crate::function::Function; pub use crate::hook::{Debug, DebugNames, DebugSource, DebugStack, HookTriggers}; -pub use crate::lua::{Chunk, ChunkMode, GCMode, Lua}; +pub use crate::lua::{Chunk, ChunkMode, GCMode, Lua, LuaOptions}; pub use crate::multi::Variadic; pub use crate::scope::Scope; pub use crate::stdlib::StdLib; @@ -97,6 +97,48 @@ pub enum GCMode { Generational, } +/// Controls Lua interpreter behaviour such as Rust panics handling. +#[derive(Clone, Debug)] +#[non_exhaustive] +pub struct LuaOptions { + /// Catch Rust panics when using [`pcall`]/[`xpcall`]. + /// + /// If disabled, wraps these functions and automatically resumes panic if found. + /// Also in Lua 5.1 adds ability to provide arguments to [`xpcall`] similar to Lua >= 5.2. + /// + /// If enabled, keeps [`pcall`]/[`xpcall`] unmodified. + /// Panics are still automatically resumed if returned back to the Rust side. + /// + /// Default: **true** + /// + /// [`pcall`]: https://www.lua.org/manual/5.3/manual.html#pdf-pcall + /// [`xpcall`]: https://www.lua.org/manual/5.3/manual.html#pdf-xpcall + pub catch_rust_panics: bool, +} + +impl Default for LuaOptions { + fn default() -> Self { + LuaOptions { + catch_rust_panics: true, + } + } +} + +impl LuaOptions { + /// Retruns a new instance of `LuaOptions` with default parameters. + pub fn new() -> Self { + Self::default() + } + + /// Sets [`catch_rust_panics`] option. + /// + /// [`catch_rust_panics`]: #structfield.catch_rust_panics + pub fn catch_rust_panics(mut self, enabled: bool) -> Self { + self.catch_rust_panics = enabled; + self + } +} + #[cfg(feature = "async")] pub(crate) static ASYNC_POLL_PENDING: u8 = 0; #[cfg(feature = "async")] @@ -149,7 +191,7 @@ impl Lua { #[allow(clippy::new_without_default)] pub fn new() -> Lua { mlua_expect!( - Self::new_with(StdLib::ALL_SAFE), + Self::new_with(StdLib::ALL_SAFE, LuaOptions::default()), "can't create new safe Lua state" ) } @@ -159,7 +201,7 @@ impl Lua { /// # Safety /// The created Lua state would not have safety guarantees and would allow to load C modules. pub unsafe fn unsafe_new() -> Lua { - Self::unsafe_new_with(StdLib::ALL) + Self::unsafe_new_with(StdLib::ALL, LuaOptions::default()) } /// Creates a new Lua state and loads the specified safe subset of the standard libraries. @@ -173,7 +215,7 @@ impl Lua { /// See [`StdLib`] documentation for a list of unsafe modules that cannot be loaded. /// /// [`StdLib`]: struct.StdLib.html - pub fn new_with(libs: StdLib) -> Result<Lua> { + pub fn new_with(libs: StdLib, options: LuaOptions) -> Result<Lua> { if libs.contains(StdLib::DEBUG) { return Err(Error::SafetyError( "the unsafe `debug` module can't be loaded using safe `new_with`".to_string(), @@ -188,7 +230,7 @@ impl Lua { } } - let mut lua = unsafe { Self::unsafe_new_with(libs) }; + let mut lua = unsafe { Self::unsafe_new_with(libs, options) }; if libs.contains(StdLib::PACKAGE) { mlua_expect!(lua.disable_c_modules(), "Error during disabling C modules"); @@ -207,7 +249,7 @@ impl Lua { /// The created Lua state will not have safety guarantees and allow to load C modules. /// /// [`StdLib`]: struct.StdLib.html - pub unsafe fn unsafe_new_with(libs: StdLib) -> Lua { + pub unsafe fn unsafe_new_with(libs: StdLib, options: LuaOptions) -> Lua { #[cfg_attr(any(feature = "lua51", feature = "luajit"), allow(dead_code))] unsafe extern "C" fn allocator( extra_data: *mut c_void, @@ -295,6 +337,28 @@ impl Lua { ); mlua_expect!(lua.extra.lock(), "extra is poisoned").libs |= libs; + if !options.catch_rust_panics { + mlua_expect!( + (|| -> Result<()> { + let _sg = StackGuard::new(lua.state); + + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + ffi::lua_rawgeti(lua.state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS); + #[cfg(any(feature = "lua51", feature = "luajit"))] + ffi::lua_pushvalue(lua.state, ffi::LUA_GLOBALSINDEX); + + ffi::lua_pushcfunction(lua.state, ffi::safe::lua_nopanic_pcall); + ffi::safe::lua_rawsetfield(lua.state, -2, "pcall")?; + + ffi::lua_pushcfunction(lua.state, ffi::safe::lua_nopanic_xpcall); + ffi::safe::lua_rawsetfield(lua.state, -2, "xpcall")?; + + Ok(()) + })(), + "Error during applying option `catch_rust_panics`" + ) + } + lua } diff --git a/src/prelude.rs b/src/prelude.rs index b043ac6..5ec7f3e 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -4,7 +4,7 @@ pub use crate::{ AnyUserData as LuaAnyUserData, Chunk as LuaChunk, Error as LuaError, ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, FromLua, FromLuaMulti, Function as LuaFunction, GCMode as LuaGCMode, Integer as LuaInteger, - LightUserData as LuaLightUserData, Lua, MetaMethod as LuaMetaMethod, + LightUserData as LuaLightUserData, Lua, LuaOptions, MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, RegistryKey as LuaRegistryKey, Result as LuaResult, String as LuaString, Table as LuaTable, TableExt as LuaTableExt, TablePairs as LuaTablePairs, TableSequence as LuaTableSequence, Thread as LuaThread, |