diff --git a/src/lua.rs b/src/lua.rs index 003a27f..95a3191 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -27,10 +27,10 @@ use crate::userdata::{ }; use crate::util::{ self, assert_stack, callback_error, check_stack, get_destructed_userdata_metatable, - get_gc_metatable, get_gc_userdata, get_main_state, get_userdata, get_wrapped_error, - init_error_registry, init_gc_metatable, init_userdata_metatable, pop_error, protect_lua, - push_gc_userdata, push_string, push_table, push_userdata, push_wrapped_error, rawset_field, - safe_pcall, safe_xpcall, StackGuard, WrappedError, WrappedPanic, + get_gc_metatable, get_gc_userdata, get_main_state, get_userdata, init_error_registry, + init_gc_metatable, init_userdata_metatable, pop_error, protect_lua, push_gc_userdata, + push_string, push_table, push_userdata, rawset_field, safe_pcall, safe_xpcall, StackGuard, + WrappedFailure, }; use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti, Value}; @@ -77,9 +77,9 @@ struct ExtraData { ref_stack_top: c_int, ref_free: Vec, - // Vec of preallocated WrappedError/WrappedPanic structs + // Vec of preallocated WrappedFailure enums // Used for callback optimization - prealloc_wrapped_errors: Vec, + prealloc_wrapped_failures: Vec, hook_callback: Option, } @@ -164,7 +164,7 @@ impl Drop for Lua { unsafe { if !self.ephemeral { let extra = &mut *self.extra; - for index in extra.prealloc_wrapped_errors.clone() { + for index in extra.prealloc_wrapped_failures.clone() { ffi::lua_pushnil(extra.ref_thread); ffi::lua_replace(extra.ref_thread, index); extra.ref_free.push(index); @@ -446,7 +446,7 @@ impl Lua { ref_stack_size: ffi::LUA_MINSTACK - 1, ref_stack_top: 0, ref_free: Vec::new(), - prealloc_wrapped_errors: Vec::new(), + prealloc_wrapped_failures: Vec::new(), hook_callback: None, })); @@ -1558,8 +1558,8 @@ impl Lua { self.push_ref(&ud.0); } - Value::Error(e) => { - push_wrapped_error(self.state, e)?; + Value::Error(err) => { + push_gc_userdata(self.state, WrappedFailure::Error(err))?; } } @@ -1608,20 +1608,22 @@ impl Lua { ffi::LUA_TUSERDATA => { // We must prevent interaction with userdata types other than UserData OR a WrappedError. // WrappedPanics are automatically resumed. - if let Some(err) = get_wrapped_error(state, -1).as_ref() { - let err = err.clone(); - ffi::lua_pop(state, 1); - Value::Error(err) - } else if let Some(panic) = get_gc_userdata::(state, -1).as_mut() { - if let Some(panic) = (*panic).0.take() { + match get_gc_userdata::(state, -1).as_mut() { + Some(WrappedFailure::Error(err)) => { + let err = err.clone(); ffi::lua_pop(state, 1); - resume_unwind(panic); + Value::Error(err) } - // Previously resumed panic? - ffi::lua_pop(state, 1); - Nil - } else { - Value::UserData(AnyUserData(self.pop_ref())) + Some(WrappedFailure::Panic(panic)) => { + if let Some(panic) = panic.take() { + ffi::lua_pop(state, 1); + resume_unwind(panic); + } + // Previously resumed panic? + ffi::lua_pop(state, 1); + Nil + } + _ => Value::UserData(AnyUserData(self.pop_ref())), } } @@ -2334,7 +2336,7 @@ impl<'lua, T: AsRef<[u8]> + ?Sized> AsChunk<'lua> for T { } } -// An optimized version of `callback_error` that does not allocate `WrappedError+Panic` userdata +// An optimized version of `callback_error` that does not allocate `WrappedFailure` userdata // and instead reuses unsed and cached values from previous calls (or allocates new). // It requires `get_extra` function to return `ExtraData` value. unsafe fn callback_error_ext(state: *mut ffi::lua_State, get_extra: E, f: F) -> R @@ -2357,59 +2359,60 @@ where cstr!("not enough stack space for callback error handling"), ); - enum PreallocatedError { - New(*mut c_void), + enum PreallocatedFailure { + New(*mut WrappedFailure), Cached(i32), } // We cannot shadow Rust errors with Lua ones, so we need to obtain pre-allocated memory // to store a wrapped error or panic *before* we proceed. let extra = &mut *get_extra(state); - let prealloc_err = { - match extra.prealloc_wrapped_errors.pop() { - Some(index) => PreallocatedError::Cached(index), + let prealloc_failure = { + match extra.prealloc_wrapped_failures.pop() { + Some(index) => PreallocatedFailure::Cached(index), None => { - let size = mem::size_of::().max(mem::size_of::()); - let ud = ffi::lua_newuserdata(state, size); + let ud = ffi::lua_newuserdata(state, mem::size_of::()); ffi::lua_rotate(state, 1, 1); - PreallocatedError::New(ud) + PreallocatedFailure::New(ud as *mut WrappedFailure) } } }; - let mut get_prealloc_err = || match prealloc_err { - PreallocatedError::New(ud) => { + let mut get_prealloc_failure = || match prealloc_failure { + PreallocatedFailure::New(ud) => { ffi::lua_settop(state, 1); ud } - PreallocatedError::Cached(index) => { + PreallocatedFailure::Cached(index) => { ffi::lua_settop(state, 0); ffi::lua_pushvalue(extra.ref_thread, index); ffi::lua_xmove(extra.ref_thread, state, 1); ffi::lua_pushnil(extra.ref_thread); ffi::lua_replace(extra.ref_thread, index); extra.ref_free.push(index); - ffi::lua_touserdata(state, -1) + ffi::lua_touserdata(state, -1) as *mut WrappedFailure } }; match catch_unwind(AssertUnwindSafe(|| f(nargs))) { Ok(Ok(r)) => { - // Return unused WrappedError+Panic to the cache - match prealloc_err { - PreallocatedError::New(_) if extra.prealloc_wrapped_errors.len() < 16 => { + // Return unused WrappedFailure to the cache + match prealloc_failure { + PreallocatedFailure::New(_) if extra.prealloc_wrapped_failures.len() < 16 => { ffi::lua_rotate(state, 1, -1); ffi::lua_xmove(state, extra.ref_thread, 1); let index = ref_stack_pop(extra); - extra.prealloc_wrapped_errors.push(index); + extra.prealloc_wrapped_failures.push(index); } - PreallocatedError::New(_) => { + PreallocatedFailure::New(_) => { ffi::lua_remove(state, 1); } - PreallocatedError::Cached(index) if extra.prealloc_wrapped_errors.len() < 16 => { - extra.prealloc_wrapped_errors.push(index); + PreallocatedFailure::Cached(index) + if extra.prealloc_wrapped_failures.len() < 16 => + { + extra.prealloc_wrapped_failures.push(index); } - PreallocatedError::Cached(index) => { + PreallocatedFailure::Cached(index) => { ffi::lua_pushnil(extra.ref_thread); ffi::lua_replace(extra.ref_thread, index); extra.ref_free.push(index); @@ -2418,9 +2421,9 @@ where r } Ok(Err(err)) => { - let wrapped_error = get_prealloc_err() as *mut WrappedError; - ptr::write(wrapped_error, WrappedError(err)); - get_gc_metatable::(state); + let wrapped_error = get_prealloc_failure(); + ptr::write(wrapped_error, WrappedFailure::Error(err)); + get_gc_metatable::(state); ffi::lua_setmetatable(state, -2); // Convert to CallbackError and attach traceback @@ -2432,15 +2435,17 @@ where } else { "".to_string() }; - let cause = Arc::new((*wrapped_error).0.clone()); - (*wrapped_error).0 = Error::CallbackError { traceback, cause }; + if let WrappedFailure::Error(ref mut err) = *wrapped_error { + let cause = Arc::new(err.clone()); + *err = Error::CallbackError { traceback, cause }; + } ffi::lua_error(state) } Err(p) => { - let wrapped_panic = get_prealloc_err() as *mut WrappedPanic; - ptr::write(wrapped_panic, WrappedPanic(Some(p))); - get_gc_metatable::(state); + let wrapped_panic = get_prealloc_failure(); + ptr::write(wrapped_panic, WrappedFailure::Panic(Some(p))); + get_gc_metatable::(state); ffi::lua_setmetatable(state, -2); ffi::lua_error(state) } diff --git a/src/util.rs b/src/util.rs index df82e0b..026b99f 100644 --- a/src/util.rs +++ b/src/util.rs @@ -167,41 +167,45 @@ pub unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> Error { "pop_error called with non-error return code" ); - if let Some(err) = get_wrapped_error(state, -1).as_ref() { - ffi::lua_pop(state, 1); - err.clone() - } else if let Some(panic) = get_gc_userdata::(state, -1).as_mut() { - if let Some(p) = (*panic).0.take() { - resume_unwind(p); - } else { - Error::PreviouslyResumedPanic + match get_gc_userdata::(state, -1).as_mut() { + Some(WrappedFailure::Error(err)) => { + ffi::lua_pop(state, 1); + err.clone() } - } else { - let err_string = to_string(state, -1); - ffi::lua_pop(state, 1); + Some(WrappedFailure::Panic(panic)) => { + if let Some(p) = panic.take() { + resume_unwind(p); + } else { + Error::PreviouslyResumedPanic + } + } + _ => { + let err_string = to_string(state, -1); + ffi::lua_pop(state, 1); - match err_code { - ffi::LUA_ERRRUN => Error::RuntimeError(err_string), - ffi::LUA_ERRSYNTAX => { - Error::SyntaxError { - // This seems terrible, but as far as I can tell, this is exactly what the - // stock Lua REPL does. - incomplete_input: err_string.ends_with("") - || err_string.ends_with("''"), - message: err_string, + match err_code { + ffi::LUA_ERRRUN => Error::RuntimeError(err_string), + ffi::LUA_ERRSYNTAX => { + Error::SyntaxError { + // This seems terrible, but as far as I can tell, this is exactly what the + // stock Lua REPL does. + incomplete_input: err_string.ends_with("") + || err_string.ends_with("''"), + message: err_string, + } } + ffi::LUA_ERRERR => { + // This error is raised when the error handler raises an error too many times + // recursively, and continuing to trigger the error handler would cause a stack + // overflow. It is not very useful to differentiate between this and "ordinary" + // runtime errors, so we handle them the same way. + Error::RuntimeError(err_string) + } + ffi::LUA_ERRMEM => Error::MemoryError(err_string), + #[cfg(any(feature = "lua53", feature = "lua52"))] + ffi::LUA_ERRGCMM => Error::GarbageCollectorError(err_string), + _ => mlua_panic!("unrecognized lua error code"), } - ffi::LUA_ERRERR => { - // This error is raised when the error handler raises an error too many times - // recursively, and continuing to trigger the error handler would cause a stack - // overflow. It is not very useful to differentiate between this and "ordinary" - // runtime errors, so we handle them the same way. - Error::RuntimeError(err_string) - } - ffi::LUA_ERRMEM => Error::MemoryError(err_string), - #[cfg(any(feature = "lua53", feature = "lua52"))] - ffi::LUA_ERRGCMM => Error::GarbageCollectorError(err_string), - _ => mlua_panic!("unrecognized lua error code"), } } } @@ -480,10 +484,7 @@ where // We cannot shadow Rust errors with Lua ones, we pre-allocate enough memory // to store a wrapped error or panic *before* we proceed. - let ud = ffi::lua_newuserdata( - state, - mem::size_of::().max(mem::size_of::()), - ); + let ud = ffi::lua_newuserdata(state, mem::size_of::()); ffi::lua_rotate(state, 1, 1); match catch_unwind(AssertUnwindSafe(|| f(nargs))) { @@ -494,9 +495,9 @@ where Ok(Err(err)) => { ffi::lua_settop(state, 1); - let wrapped_error = ud as *mut WrappedError; - ptr::write(wrapped_error, WrappedError(err)); - get_gc_metatable::(state); + let wrapped_error = ud as *mut WrappedFailure; + ptr::write(wrapped_error, WrappedFailure::Error(err)); + get_gc_metatable::(state); ffi::lua_setmetatable(state, -2); // Convert to CallbackError and attach traceback @@ -508,15 +509,17 @@ where } else { "".to_string() }; - let cause = Arc::new((*wrapped_error).0.clone()); - (*wrapped_error).0 = Error::CallbackError { traceback, cause }; + if let WrappedFailure::Error(ref mut err) = *wrapped_error { + let cause = Arc::new(err.clone()); + *err = Error::CallbackError { traceback, cause }; + } ffi::lua_error(state) } Err(p) => { ffi::lua_settop(state, 1); - ptr::write(ud as *mut WrappedPanic, WrappedPanic(Some(p))); - get_gc_metatable::(state); + ptr::write(ud as *mut WrappedFailure, WrappedFailure::Panic(Some(p))); + get_gc_metatable::(state); ffi::lua_setmetatable(state, -2); ffi::lua_error(state) } @@ -530,9 +533,7 @@ pub unsafe extern "C" fn error_traceback(state: *mut ffi::lua_State) -> c_int { return 1; } - if get_gc_userdata::(state, -1).is_null() - && get_gc_userdata::(state, -1).is_null() - { + if get_gc_userdata::(state, -1).is_null() { let s = ffi::luaL_tolstring(state, -1, ptr::null_mut()); if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 { ffi::luaL_traceback(state, state, s, 1); @@ -558,7 +559,9 @@ pub unsafe extern "C" fn safe_pcall(state: *mut ffi::lua_State) -> c_int { ffi::lua_insert(state, 1); ffi::lua_gettop(state) } else { - if !get_gc_userdata::(state, -1).is_null() { + if let Some(WrappedFailure::Panic(_)) = + get_gc_userdata::(state, -1).as_ref() + { ffi::lua_error(state); } ffi::lua_pushboolean(state, 0); @@ -572,7 +575,9 @@ 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 !get_gc_userdata::(state, -1).is_null() { + if let Some(WrappedFailure::Panic(_)) = + get_gc_userdata::(state, -1).as_ref() + { 1 } else { ffi::lua_pushvalue(state, ffi::lua_upvalueindex(1)); @@ -600,7 +605,9 @@ pub unsafe extern "C" fn safe_xpcall(state: *mut ffi::lua_State) -> c_int { ffi::lua_insert(state, 2); ffi::lua_gettop(state) - 1 } else { - if !get_gc_userdata::(state, -1).is_null() { + if let Some(WrappedFailure::Panic(_)) = + get_gc_userdata::(state, -1).as_ref() + { ffi::lua_error(state); } ffi::lua_pushboolean(state, 0); @@ -632,23 +639,6 @@ pub unsafe fn get_main_state(state: *mut ffi::lua_State) -> Option<*mut ffi::lua } } -// Pushes a WrappedError to the top of the stack. -// Uses 3 stack spaces and does not call checkstack. -pub unsafe fn push_wrapped_error(state: *mut ffi::lua_State, err: Error) -> Result<()> { - push_gc_userdata::(state, WrappedError(err)) -} - -// Checks if the value at the given index is a WrappedError, and if it is returns a pointer to it, -// otherwise returns null. -// Uses 2 stack spaces and does not call checkstack. -pub unsafe fn get_wrapped_error(state: *mut ffi::lua_State, index: c_int) -> *const Error { - let ud = get_gc_userdata::(state, index); - if ud.is_null() { - return ptr::null(); - } - &(*ud).0 -} - // Initialize the internal (with __gc method) metatable for a type T. // Uses 6 stack spaces and calls checkstack. pub unsafe fn init_gc_metatable( @@ -697,7 +687,6 @@ pub unsafe fn get_gc_metatable(state: *mut ffi::lua_State) { } // Initialize the error, panic, and destructed userdata metatables. -// Returns address of WrappedError and WrappedPanic metatables in Lua registry. pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> { check_stack(state, 7)?; @@ -707,63 +696,67 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> { callback_error(state, |_| { check_stack(state, 3)?; - let err_buf = if let Some(error) = get_wrapped_error(state, -1).as_ref() { - let err_buf_key = &ERROR_PRINT_BUFFER_KEY as *const u8 as *const c_void; - ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, err_buf_key); - let err_buf = ffi::lua_touserdata(state, -1) as *mut String; - ffi::lua_pop(state, 2); + let err_buf = match get_gc_userdata::(state, -1).as_ref() { + Some(WrappedFailure::Error(error)) => { + let err_buf_key = &ERROR_PRINT_BUFFER_KEY as *const u8 as *const c_void; + ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, err_buf_key); + let err_buf = ffi::lua_touserdata(state, -1) as *mut String; + ffi::lua_pop(state, 2); - (*err_buf).clear(); - // Depending on how the API is used and what error types scripts are given, it may - // be possible to make this consume arbitrary amounts of memory (for example, some - // kind of recursive error structure?) - let _ = write!(&mut (*err_buf), "{}", error); - // Find first two sources that caused the error - let mut source1 = error.source(); - let mut source0 = source1.and_then(|s| s.source()); - while let Some(source) = source0.and_then(|s| s.source()) { - source1 = source0; - source0 = Some(source); - } - match (source1, source0) { - (_, Some(error0)) if error0.to_string().contains("\nstack traceback:\n") => { - let _ = write!(&mut (*err_buf), "\ncaused by: {}", error0); + (*err_buf).clear(); + // Depending on how the API is used and what error types scripts are given, it may + // be possible to make this consume arbitrary amounts of memory (for example, some + // kind of recursive error structure?) + let _ = write!(&mut (*err_buf), "{}", error); + // Find first two sources that caused the error + let mut source1 = error.source(); + let mut source0 = source1.and_then(|s| s.source()); + while let Some(source) = source0.and_then(|s| s.source()) { + source1 = source0; + source0 = Some(source); } - (Some(error1), Some(error0)) => { - let _ = write!(&mut (*err_buf), "\ncaused by: {}", error0); - let s = error1.to_string(); - if let Some(traceback) = s.splitn(2, "\nstack traceback:\n").nth(1) { - let _ = write!(&mut (*err_buf), "\nstack traceback:\n{}", traceback); + match (source1, source0) { + (_, Some(error0)) + if error0.to_string().contains("\nstack traceback:\n") => + { + let _ = write!(&mut (*err_buf), "\ncaused by: {}", error0); } + (Some(error1), Some(error0)) => { + let _ = write!(&mut (*err_buf), "\ncaused by: {}", error0); + let s = error1.to_string(); + if let Some(traceback) = s.splitn(2, "\nstack traceback:\n").nth(1) { + let _ = + write!(&mut (*err_buf), "\nstack traceback:\n{}", traceback); + } + } + (Some(error1), None) => { + let _ = write!(&mut (*err_buf), "\ncaused by: {}", error1); + } + _ => {} } - (Some(error1), None) => { - let _ = write!(&mut (*err_buf), "\ncaused by: {}", error1); - } - _ => {} + Ok(err_buf) } - Ok(err_buf) - } else if let Some(panic) = get_gc_userdata::(state, -1).as_ref() { - if let Some(ref p) = (*panic).0 { + Some(WrappedFailure::Panic(Some(ref panic))) => { let err_buf_key = &ERROR_PRINT_BUFFER_KEY as *const u8 as *const c_void; ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, err_buf_key); let err_buf = ffi::lua_touserdata(state, -1) as *mut String; (*err_buf).clear(); ffi::lua_pop(state, 2); - if let Some(msg) = p.downcast_ref::<&str>() { + if let Some(msg) = panic.downcast_ref::<&str>() { let _ = write!(&mut (*err_buf), "{}", msg); - } else if let Some(msg) = p.downcast_ref::() { + } else if let Some(msg) = panic.downcast_ref::() { let _ = write!(&mut (*err_buf), "{}", msg); } else { let _ = write!(&mut (*err_buf), ""); }; Ok(err_buf) - } else { - Err(Error::PreviouslyResumedPanic) } - } else { - // I'm not sure whether this is possible to trigger without bugs in mlua? - Err(Error::UserDataTypeMismatch) + Some(WrappedFailure::Panic(None)) => Err(Error::PreviouslyResumedPanic), + _ => { + // I'm not sure whether this is possible to trigger without bugs in mlua? + Err(Error::UserDataTypeMismatch) + } }?; push_string(state, &*err_buf)?; @@ -773,15 +766,7 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> { }) } - init_gc_metatable::( - state, - Some(|state| { - ffi::lua_pushcfunction(state, error_tostring); - rawset_field(state, -2, "__tostring") - }), - )?; - - init_gc_metatable::( + init_gc_metatable::( state, Some(|state| { ffi::lua_pushcfunction(state, error_tostring); @@ -856,8 +841,10 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> { Ok(()) } -pub(crate) struct WrappedError(pub Error); -pub(crate) struct WrappedPanic(pub Option>); +pub(crate) enum WrappedFailure { + Error(Error), + Panic(Option>), +} // Converts the given lua value to a string in a reasonable format without causing a Lua error or // panicking.