diff --git a/CHANGELOG.md b/CHANGELOG.md index 78dfbf3c..8ad002e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## v0.10.2 (Dec 1st, 2024) + +- Switch proc-macro-error to proc-macro-error2 (#493) +- Do not allow Lua to run GC finalizers on ref thread (#491) +- Fix chunks loading in Luau when memory limit is enforced (#488) +- Added `String::wrap` method to wrap arbitrary `AsRef<[u8]>` into `impl IntoLua` +- Better FreeBSD/OpenBSD support (thanks to cos) +- Delay "any" userdata metatable creation until first instance is created (#482) +- Reduce amount of generated code for `UserData` (less generics) + ## v0.10.1 (Nov 9th, 2024) - Minimal Luau updated to 0.650 diff --git a/Cargo.toml b/Cargo.toml index b0ee449e..ad49f56f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.10.1" # remember to update mlua_derive +version = "0.10.2" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] rust-version = "1.79.0" edition = "2021" @@ -46,7 +46,7 @@ anyhow = ["dep:anyhow", "error-send"] userdata-wrappers = [] [dependencies] -mlua_derive = { version = "=0.10.0", optional = true, path = "mlua_derive" } +mlua_derive = { version = "=0.10.1", optional = true, path = "mlua_derive" } bstr = { version = "1.0", features = ["std"], default-features = false } either = "1.0" num-traits = { version = "0.2.14" } @@ -58,7 +58,7 @@ serde-value = { version = "0.7", optional = true } parking_lot = { version = "0.12", features = ["arc_lock"] } anyhow = { version = "1.0", optional = true } -ffi = { package = "mlua-sys", version = "0.6.5", path = "mlua-sys" } +ffi = { package = "mlua-sys", version = "0.6.6", path = "mlua-sys" } [target.'cfg(unix)'.dependencies] libloading = { version = "0.8", optional = true } diff --git a/README.md b/README.md index 1c68d9a8..c05426e3 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ Add to `Cargo.toml` : ``` toml [dependencies] -mlua = { version = "0.10.1", features = ["lua54", "vendored"] } +mlua = { version = "0.10.2", features = ["lua54", "vendored"] } ``` `main.rs` @@ -168,7 +168,7 @@ Add to `Cargo.toml` : crate-type = ["cdylib"] [dependencies] -mlua = { version = "0.10.1", features = ["lua54", "module"] } +mlua = { version = "0.10.2", features = ["lua54", "module"] } ``` `lib.rs` : diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index d6d08441..dc3168c4 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua-sys" -version = "0.6.5" +version = "0.6.6" authors = ["Aleksandr Orlenko "] rust-version = "1.71" edition = "2021" diff --git a/mlua-sys/src/luau/compat.rs b/mlua-sys/src/luau/compat.rs index a738dae3..3d189cec 100644 --- a/mlua-sys/src/luau/compat.rs +++ b/mlua-sys/src/luau/compat.rs @@ -326,12 +326,16 @@ pub unsafe fn luaL_loadbufferenv( mut size: usize, name: *const c_char, mode: *const c_char, - env: c_int, + mut env: c_int, ) -> c_int { extern "C" { fn free(p: *mut c_void); } + unsafe extern "C-unwind" fn data_dtor(data: *mut c_void) { + free(*(data as *mut *mut c_char) as *mut c_void); + } + let chunk_is_text = size == 0 || (*data as u8) >= b'\t'; if !mode.is_null() { let modeb = CStr::from_ptr(mode).to_bytes(); @@ -345,9 +349,16 @@ pub unsafe fn luaL_loadbufferenv( } if chunk_is_text { + if env < 0 { + env -= 1; + } + let data_ud = lua_newuserdatadtor(L, mem::size_of::<*mut c_char>(), data_dtor) as *mut *mut c_char; let data = luau_compile_(data, size, ptr::null_mut(), &mut size); + ptr::write(data_ud, data); + // By deferring the `free(data)` to the userdata destructor, we ensure that + // even if `luau_load` throws an error, the `data` is still released. let ok = luau_load(L, name, data, size, env) == 0; - free(data as *mut c_void); + lua_replace(L, -2); // replace data with the result if !ok { return LUA_ERRSYNTAX; } diff --git a/mlua_derive/Cargo.toml b/mlua_derive/Cargo.toml index 773351e7..1e121341 100644 --- a/mlua_derive/Cargo.toml +++ b/mlua_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua_derive" -version = "0.10.0" +version = "0.10.1" authors = ["Aleksandr Orlenko "] edition = "2021" description = "Procedural macros for the mlua crate." @@ -12,12 +12,12 @@ license = "MIT" proc-macro = true [features] -macros = ["proc-macro-error", "itertools", "regex", "once_cell"] +macros = ["proc-macro-error2", "itertools", "regex", "once_cell"] [dependencies] quote = "1.0" proc-macro2 = { version = "1.0", features = ["span-locations"] } -proc-macro-error = { version = "1.0", optional = true } +proc-macro-error2 = { version = "2.0.1", optional = true } syn = { version = "2.0", features = ["full"] } itertools = { version = "0.13", optional = true } regex = { version = "1.4", optional = true } diff --git a/mlua_derive/src/lib.rs b/mlua_derive/src/lib.rs index 4d3bfb29..af54bedf 100644 --- a/mlua_derive/src/lib.rs +++ b/mlua_derive/src/lib.rs @@ -7,7 +7,7 @@ use syn::{parse_macro_input, ItemFn, LitStr, Result}; #[cfg(feature = "macros")] use { crate::chunk::Chunk, proc_macro::TokenTree, proc_macro2::TokenStream as TokenStream2, - proc_macro_error::proc_macro_error, + proc_macro_error2::proc_macro_error, }; #[derive(Default)] diff --git a/mlua_derive/src/token.rs b/mlua_derive/src/token.rs index 19f3f05f..c6ce7c97 100644 --- a/mlua_derive/src/token.rs +++ b/mlua_derive/src/token.rs @@ -74,7 +74,7 @@ fn parse_pos(span: &Span) -> Option<(usize, usize)> { fn fallback_span_pos(span: &Span) -> (Pos, Pos) { let (start, end) = match parse_pos(span) { Some(v) => v, - None => proc_macro_error::abort_call_site!("Cannot retrieve span information; please use nightly"), + None => proc_macro_error2::abort_call_site!("Cannot retrieve span information; please use nightly"), }; (Pos::new(1, start), Pos::new(1, end)) } diff --git a/src/state.rs b/src/state.rs index 71114ba0..35d9e4ec 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1043,8 +1043,8 @@ impl Lua { let state = lua.state(); unsafe { if lua.unlikely_memory_error() { - crate::util::push_buffer(lua.ref_thread(), buf.as_ref(), false)?; - return Ok(Buffer(lua.pop_ref_thread())); + crate::util::push_buffer(state, buf.as_ref(), false)?; + return Ok(Buffer(lua.pop_ref())); } let _sg = StackGuard::new(state); diff --git a/src/state/extra.rs b/src/state/extra.rs index 19ff747d..d1823b5c 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -27,7 +27,7 @@ use super::{Lua, WeakLua}; // Unique key to store `ExtraData` in the registry static EXTRA_REGISTRY_KEY: u8 = 0; -const WRAPPED_FAILURE_POOL_SIZE: usize = 64; +const WRAPPED_FAILURE_POOL_DEFAULT_CAPACITY: usize = 64; const REF_STACK_RESERVE: c_int = 1; /// Data associated with the Lua state. @@ -60,6 +60,7 @@ pub(crate) struct ExtraData { // Pool of `WrappedFailure` enums in the ref thread (as userdata) pub(super) wrapped_failure_pool: Vec, + pub(super) wrapped_failure_top: usize, // Pool of `Thread`s (coroutines) for async execution #[cfg(feature = "async")] pub(super) thread_pool: Vec, @@ -160,7 +161,8 @@ impl ExtraData { ref_stack_size: ffi::LUA_MINSTACK - REF_STACK_RESERVE, ref_stack_top: ffi::lua_gettop(ref_thread), ref_free: Vec::new(), - wrapped_failure_pool: Vec::with_capacity(WRAPPED_FAILURE_POOL_SIZE), + wrapped_failure_pool: Vec::with_capacity(WRAPPED_FAILURE_POOL_DEFAULT_CAPACITY), + wrapped_failure_top: 0, #[cfg(feature = "async")] thread_pool: Vec::new(), wrapped_failure_mt_ptr, diff --git a/src/state/raw.rs b/src/state/raw.rs index 7b2e7b23..269b62e2 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -327,10 +327,10 @@ impl RawLua { Some(ChunkMode::Text) => cstr!("t"), None => cstr!("bt"), }; - let status = if cfg!(not(feature = "luau")) || self.unlikely_memory_error() { + let status = if self.unlikely_memory_error() { self.load_chunk_inner(state, name, env, mode, source) } else { - // Only Luau can trigger an exception during chunk loading + // Luau and Lua 5.2 can trigger an exception during chunk loading protect_lua!(state, 0, 1, |state| { self.load_chunk_inner(state, name, env, mode, source) })? @@ -427,8 +427,8 @@ impl RawLua { pub(crate) unsafe fn create_string(&self, s: impl AsRef<[u8]>) -> Result { let state = self.state(); if self.unlikely_memory_error() { - push_string(self.ref_thread(), s.as_ref(), false)?; - return Ok(String(self.pop_ref_thread())); + push_string(state, s.as_ref(), false)?; + return Ok(String(self.pop_ref())); } let _sg = StackGuard::new(state); @@ -439,12 +439,12 @@ impl RawLua { /// See [`Lua::create_table_with_capacity`] pub(crate) unsafe fn create_table_with_capacity(&self, narr: usize, nrec: usize) -> Result { + let state = self.state(); if self.unlikely_memory_error() { - push_table(self.ref_thread(), narr, nrec, false)?; - return Ok(Table(self.pop_ref_thread())); + push_table(state, narr, nrec, false)?; + return Ok(Table(self.pop_ref())); } - let state = self.state(); let _sg = StackGuard::new(state); check_stack(state, 3)?; push_table(state, narr, nrec, true)?; @@ -729,6 +729,10 @@ impl RawLua { pub(crate) unsafe fn drop_ref(&self, vref: &ValueRef) { let ref_thread = self.ref_thread(); + mlua_debug_assert!( + ffi::lua_gettop(ref_thread) >= vref.index, + "GC finalizer is not allowed in ref_thread" + ); ffi::lua_pushnil(ref_thread); ffi::lua_replace(ref_thread, vref.index); (*self.extra.get()).ref_free.push(vref.index); diff --git a/src/state/util.rs b/src/state/util.rs index ba6339b1..ec701eaf 100644 --- a/src/state/util.rs +++ b/src/state/util.rs @@ -7,8 +7,6 @@ use crate::error::{Error, Result}; use crate::state::{ExtraData, RawLua}; use crate::util::{self, get_internal_metatable, WrappedFailure}; -const WRAPPED_FAILURE_POOL_SIZE: usize = 64; - pub(super) struct StateGuard<'a>(&'a RawLua, *mut ffi::lua_State); impl<'a> StateGuard<'a> { @@ -42,26 +40,27 @@ where enum PreallocatedFailure { New(*mut WrappedFailure), - Existing(i32), + Reserved, } impl PreallocatedFailure { unsafe fn reserve(state: *mut ffi::lua_State, extra: *mut ExtraData) -> Self { - match (*extra).wrapped_failure_pool.pop() { - Some(index) => PreallocatedFailure::Existing(index), - None => { - // We need to check stack for Luau in case when callback is called from interrupt - // See https://github.com/Roblox/luau/issues/446 and mlua #142 and #153 - #[cfg(feature = "luau")] - ffi::lua_rawcheckstack(state, 2); - // Place it to the beginning of the stack - let ud = WrappedFailure::new_userdata(state); - ffi::lua_insert(state, 1); - PreallocatedFailure::New(ud) - } + if (*extra).wrapped_failure_top > 0 { + (*extra).wrapped_failure_top -= 1; + return PreallocatedFailure::Reserved; } + + // We need to check stack for Luau in case when callback is called from interrupt + // See https://github.com/Roblox/luau/issues/446 and mlua #142 and #153 + #[cfg(feature = "luau")] + ffi::lua_rawcheckstack(state, 2); + // Place it to the beginning of the stack + let ud = WrappedFailure::new_userdata(state); + ffi::lua_insert(state, 1); + PreallocatedFailure::New(ud) } + #[cold] unsafe fn r#use(&self, state: *mut ffi::lua_State, extra: *mut ExtraData) -> *mut WrappedFailure { let ref_thread = (*extra).ref_thread; match *self { @@ -69,12 +68,12 @@ where ffi::lua_settop(state, 1); ud } - PreallocatedFailure::Existing(index) => { + PreallocatedFailure::Reserved => { + let index = (*extra).wrapped_failure_pool.pop().unwrap(); ffi::lua_settop(state, 0); #[cfg(feature = "luau")] ffi::lua_rawcheckstack(state, 2); - ffi::lua_pushvalue(ref_thread, index); - ffi::lua_xmove(ref_thread, state, 1); + ffi::lua_xpush(ref_thread, state, index); ffi::lua_pushnil(ref_thread); ffi::lua_replace(ref_thread, index); (*extra).ref_free.push(index); @@ -87,24 +86,13 @@ where let ref_thread = (*extra).ref_thread; match self { PreallocatedFailure::New(_) => { - if (*extra).wrapped_failure_pool.len() < WRAPPED_FAILURE_POOL_SIZE { - ffi::lua_rotate(state, 1, -1); - ffi::lua_xmove(state, ref_thread, 1); - let index = ref_stack_pop(extra); - (*extra).wrapped_failure_pool.push(index); - } else { - ffi::lua_remove(state, 1); - } - } - PreallocatedFailure::Existing(index) => { - if (*extra).wrapped_failure_pool.len() < WRAPPED_FAILURE_POOL_SIZE { - (*extra).wrapped_failure_pool.push(index); - } else { - ffi::lua_pushnil(ref_thread); - ffi::lua_replace(ref_thread, index); - (*extra).ref_free.push(index); - } + ffi::lua_rotate(state, 1, -1); + ffi::lua_xmove(state, ref_thread, 1); + let index = ref_stack_pop(extra); + (*extra).wrapped_failure_pool.push(index); + (*extra).wrapped_failure_top += 1; } + PreallocatedFailure::Reserved => (*extra).wrapped_failure_top += 1, } } } diff --git a/src/table.rs b/src/table.rs index eb12e964..3f100cad 100644 --- a/src/table.rs +++ b/src/table.rs @@ -581,6 +581,24 @@ impl Table { unsafe { ffi::lua_getreadonly(ref_thread, self.0.index) != 0 } } + /// Controls `safeenv` attribute on the table. + /// + /// This a special flag that activates some performance optimizations for environment tables. + /// In particular, it controls: + /// - Optimization of import resolution (cache values of constant keys). + /// - Fast-path for built-in iteration with pairs/ipairs. + /// - Fast-path for some built-in functions (fastcall). + /// + /// For `safeenv` environments, monkey patching or modifying values may not work as expected. + /// + /// Requires `feature = "luau"` + #[cfg(any(feature = "luau", doc))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + pub fn set_safeenv(&self, enabled: bool) { + let lua = self.0.lua.lock(); + unsafe { ffi::lua_setsafeenv(lua.ref_thread(), self.0.index, enabled as _) }; + } + /// Converts this table to a generic C pointer. /// /// Different tables will give different pointers. diff --git a/src/types/value_ref.rs b/src/types/value_ref.rs index 89a60ec4..89bac543 100644 --- a/src/types/value_ref.rs +++ b/src/types/value_ref.rs @@ -4,7 +4,7 @@ use std::os::raw::{c_int, c_void}; use crate::state::{RawLua, WeakLua}; /// A reference to a Lua (complex) value stored in the Lua auxiliary thread. -pub(crate) struct ValueRef { +pub struct ValueRef { pub(crate) lua: WeakLua, pub(crate) index: c_int, pub(crate) drop: bool, diff --git a/src/value.rs b/src/value.rs index 6c81ee55..9292261b 100644 --- a/src/value.rs +++ b/src/value.rs @@ -67,8 +67,7 @@ pub enum Value { /// `Error` is a special builtin userdata type. When received from Lua it is implicitly cloned. Error(Box), /// Any other value not known to mlua (eg. LuaJIT CData). - #[allow(private_interfaces)] - Other(ValueRef), + Other(#[doc(hidden)] ValueRef), } pub use self::Value::Nil; diff --git a/tests/hooks.rs b/tests/hooks.rs index 7231bdfd..ddbfc37f 100644 --- a/tests/hooks.rs +++ b/tests/hooks.rs @@ -75,9 +75,21 @@ fn test_function_calls() -> Result<()> { let output = output.lock().unwrap(); if cfg!(feature = "luajit") && lua.load("jit.version_num").eval::()? >= 20100 { + #[cfg(not(force_memory_limit))] assert_eq!(*output, vec![(None, "main"), (Some("len".to_string()), "Lua")]); + #[cfg(force_memory_limit)] + assert_eq!( + *output, + vec![(None, "C"), (None, "main"), (Some("len".to_string()), "Lua")] + ); } else { + #[cfg(not(force_memory_limit))] assert_eq!(*output, vec![(None, "main"), (Some("len".to_string()), "C")]); + #[cfg(force_memory_limit)] + assert_eq!( + *output, + vec![(None, "C"), (None, "main"), (Some("len".to_string()), "C")] + ); } Ok(()) diff --git a/tests/luau.rs b/tests/luau.rs index b45404c9..c3bc8a63 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -282,6 +282,20 @@ fn test_sandbox() -> Result<()> { Ok(()) } +#[test] +fn test_sandbox_safeenv() -> Result<()> { + let lua = Lua::new(); + + lua.sandbox(true)?; + lua.globals().set("state", lua.create_table()?)?; + lua.globals().set_safeenv(false); + lua.load("state.a = 123").exec()?; + let a: i32 = lua.load("state.a = 321; return state.a").eval()?; + assert_eq!(a, 321); + + Ok(()) +} + #[test] fn test_sandbox_nolibs() -> Result<()> { let lua = Lua::new_with(StdLib::NONE, LuaOptions::default()).unwrap(); diff --git a/tests/tests.rs b/tests/tests.rs index 318b03bb..89bece8a 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1377,3 +1377,21 @@ fn test_exec_raw() -> Result<()> { Ok(()) } + +#[test] +fn test_gc_drop_ref_thread() -> Result<()> { + let lua = Lua::new(); + + let t = lua.create_table()?; + lua.create_function(move |_, ()| { + _ = &t; + Ok(()) + })?; + + for _ in 0..10000 { + // GC will run eventually to collect the function and the table above + lua.create_table()?; + } + + Ok(()) +} diff --git a/tests/value.rs b/tests/value.rs index 98fc222f..4185442e 100644 --- a/tests/value.rs +++ b/tests/value.rs @@ -296,3 +296,25 @@ fn test_value_conversions() -> Result<()> { Ok(()) } + +#[test] +fn test_value_exhaustive_match() { + match Value::Nil { + Value::Nil => {} + Value::Boolean(_) => {} + Value::LightUserData(_) => {} + Value::Integer(_) => {} + Value::Number(_) => {} + #[cfg(feature = "luau")] + Value::Vector(_) => {} + Value::String(_) => {} + Value::Table(_) => {} + Value::Function(_) => {} + Value::Thread(_) => {} + Value::UserData(_) => {} + #[cfg(feature = "luau")] + Value::Buffer(_) => {} + Value::Error(_) => {} + Value::Other(_) => {} + } +}