diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 647f2aa7..bc34a0e1 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -14,7 +14,7 @@ jobs: - name: Generate coverage report run: | - cargo tarpaulin --verbose --features lua54,vendored,async,send,serialize,macros --out xml --exclude-files benches --exclude-files build --exclude-files mlua_derive --exclude-files src/ffi --exclude-files tests + cargo tarpaulin --out xml --tests --exclude-files benches/* --exclude-files src/ffi/*/* - name: Upload report to codecov.io uses: codecov/codecov-action@v3 diff --git a/CHANGELOG.md b/CHANGELOG.md index 62535ccd..31e151ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,27 @@ +## v0.8.10 + +- Update to Luau 0.590 (luau0-src to 0.7.x) +- Fix loading luau code starting with \t +- Pin lua-src and luajit-src versions + +## v0.8.9 + +- Update minimal (vendored) Lua 5.4 to 5.4.6 +- Use `lua_closethread` instead of `lua_resetthread` in vendored mode (Lua 5.4.6) +- Allow deserializing Lua null into unit (`()`) or unit struct. + +## v0.8.8 + +- Fix potential deadlock when trying to reuse dropped registry keys. +- Optimize userdata methods call when __index and fields_getters are nil + +## v0.8.7 + +- Minimum Luau updated to 0.555 (`LUAI_MAXCSTACK` limit increased to 100000) +- `_VERSION` in Luau now includes version number +- Fixed lifetime of `DebugNames` in `Debug::names()` and `DebugSource` in `Debug::source()` +- Fixed subtraction overflow when calculating index for `MultiValue::get()` + ## v0.8.6 - Fixed bug when recycled Registry slot can be set to Nil diff --git a/Cargo.toml b/Cargo.toml index c228275b..dd94454b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mlua" -version = "0.8.6" # remember to update html_root_url and mlua_derive +version = "0.8.10" # remember to update mlua_derive authors = ["Aleksandr Orlenko ", "kyren "] edition = "2021" repository = "https://github.com/khvzak/mlua" @@ -56,9 +56,9 @@ parking_lot = { version = "0.12", optional = true } [build-dependencies] cc = { version = "1.0" } pkg-config = { version = "0.3.17" } -lua-src = { version = ">= 544.0.0, < 550.0.0", optional = true } -luajit-src = { version = ">= 210.4.0, < 220.0.0", optional = true } -luau0-src = { version = "0.5.0", optional = true } +lua-src = { version = ">= 546.0.0, < 546.1.0", optional = true } +luajit-src = { version = ">= 210.4.0, < 210.5.0", optional = true } +luau0-src = { version = "0.7.0", optional = true } [dev-dependencies] rustyline = "10.0" diff --git a/README.md b/README.md index 211da8b6..9cdf895f 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [crates.io]: https://crates.io/crates/mlua [API Documentation]: https://docs.rs/mlua/badge.svg [docs.rs]: https://docs.rs/mlua -[Coverage Status]: https://codecov.io/gh/khvzak/mlua/branch/master/graph/badge.svg?token=99339FS1CG +[Coverage Status]: https://codecov.io/gh/khvzak/mlua/branch/v0.8/graph/badge.svg?token=99339FS1CG [codecov.io]: https://codecov.io/gh/khvzak/mlua [MSRV]: https://img.shields.io/badge/rust-1.56+-brightgreen.svg?&logo=rust diff --git a/src/chunk.rs b/src/chunk.rs index bd24ae4b..1766dfa8 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -228,6 +228,7 @@ impl Compiler { coverageLevel: self.coverage_level as c_int, vectorLib: vector_lib.map_or(ptr::null(), |s| s.as_ptr()), vectorCtor: vector_ctor.map_or(ptr::null(), |s| s.as_ptr()), + vectorType: ptr::null(), mutableGlobals: mutable_globals_ptr, }; ffi::luau_compile(source.as_ref(), options) diff --git a/src/ffi/lua54/lua.rs b/src/ffi/lua54/lua.rs index f124c377..218255e3 100644 --- a/src/ffi/lua54/lua.rs +++ b/src/ffi/lua54/lua.rs @@ -112,7 +112,10 @@ extern "C" { pub fn lua_newstate(f: lua_Alloc, ud: *mut c_void) -> *mut lua_State; pub fn lua_close(L: *mut lua_State); pub fn lua_newthread(L: *mut lua_State) -> *mut lua_State; + // Deprecated in Lua 5.4.6 pub fn lua_resetthread(L: *mut lua_State) -> c_int; + #[cfg(feature = "vendored")] + pub fn lua_closethread(L: *mut lua_State, from: *mut lua_State) -> c_int; pub fn lua_atpanic(L: *mut lua_State, panicf: lua_CFunction) -> lua_CFunction; diff --git a/src/ffi/luau/compat.rs b/src/ffi/luau/compat.rs index 043245a2..0ff8572a 100644 --- a/src/ffi/luau/compat.rs +++ b/src/ffi/luau/compat.rs @@ -341,7 +341,7 @@ pub unsafe fn luaL_loadbufferx( fn free(p: *mut c_void); } - let chunk_is_text = size == 0 || (*data as u8) >= b'\n'; + let chunk_is_text = size == 0 || (*data as u8) >= b'\t'; if !mode.is_null() { let modeb = CStr::from_ptr(mode).to_bytes(); if !chunk_is_text && !modeb.contains(&b'b') { diff --git a/src/ffi/luau/luacode.rs b/src/ffi/luau/luacode.rs index 571db4c2..853e0946 100644 --- a/src/ffi/luau/luacode.rs +++ b/src/ffi/luau/luacode.rs @@ -10,7 +10,8 @@ pub struct lua_CompileOptions { pub coverageLevel: c_int, pub vectorLib: *const c_char, pub vectorCtor: *const c_char, - pub mutableGlobals: *mut *const c_char, + pub vectorType: *const c_char, + pub mutableGlobals: *const *const c_char, } extern "C" { diff --git a/src/lib.rs b/src/lib.rs index 24208b8f..e8caa360 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,8 +71,6 @@ //! [`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html //! [`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html -// mlua types in rustdoc of other crates get linked to here. -#![doc(html_root_url = "https://docs.rs/mlua/0.8.6")] // Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any* // warnings at all. #![doc(test(attr(deny(warnings))))] diff --git a/src/lua.rs b/src/lua.rs index d1001dec..d8931509 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -1738,8 +1738,10 @@ impl Lua { let extra = &mut *self.extra.get(); if extra.recycled_thread_cache.len() < extra.recycled_thread_cache.capacity() { let thread_state = ffi::lua_tothread(extra.ref_thread, thread.0.index); - #[cfg(feature = "lua54")] + #[cfg(all(feature = "lua54", not(feature = "vendored")))] let status = ffi::lua_resetthread(thread_state); + #[cfg(all(feature = "lua54", feature = "vendored"))] + let status = ffi::lua_closethread(thread_state, self.state); #[cfg(feature = "lua54")] if status != ffi::LUA_OK { // Error object is on top, drop it @@ -2072,22 +2074,27 @@ impl Lua { let _sg = StackGuard::new(self.state); check_stack(self.state, 4)?; - let unref_list = (*self.extra.get()).registry_unref_list.clone(); self.push_value(t)?; // Try to reuse previously allocated slot - let unref_list2 = unref_list.clone(); - let mut unref_list2 = mlua_expect!(unref_list2.lock(), "unref list poisoned"); - if let Some(registry_id) = unref_list2.as_mut().and_then(|x| x.pop()) { + let unref_list = (*self.extra.get()).registry_unref_list.clone(); + let free_registry_id = mlua_expect!(unref_list.lock(), "unref list poisoned") + .as_mut() + .and_then(|x| x.pop()); + if let Some(registry_id) = free_registry_id { // It must be safe to replace the value without triggering memory error ffi::lua_rawseti(self.state, ffi::LUA_REGISTRYINDEX, registry_id as Integer); return Ok(RegistryKey::new(registry_id, unref_list)); } // Allocate a new RegistryKey - let registry_id = protect_lua!(self.state, 1, 0, |state| { - ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX) - })?; + let registry_id = if self.unlikely_memory_error() { + ffi::luaL_ref(self.state, ffi::LUA_REGISTRYINDEX) + } else { + protect_lua!(self.state, 1, 0, |state| { + ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX) + })? + }; Ok(RegistryKey::new(registry_id, unref_list)) } } diff --git a/src/serde/de.rs b/src/serde/de.rs index 376bd94c..b3bb43b2 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -327,9 +327,31 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> { visitor.visit_newtype_struct(self) } + #[inline] + fn deserialize_unit(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + match self.value { + Value::LightUserData(ud) if ud.0.is_null() => visitor.visit_unit(), + _ => self.deserialize_any(visitor), + } + } + + #[inline] + fn deserialize_unit_struct(self, _name: &'static str, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + match self.value { + Value::LightUserData(ud) if ud.0.is_null() => visitor.visit_unit(), + _ => self.deserialize_any(visitor), + } + } + serde::forward_to_deserialize_any! { bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string bytes - byte_buf unit unit_struct identifier ignored_any + byte_buf identifier ignored_any } } diff --git a/src/thread.rs b/src/thread.rs index c0a42605..cb1b67e6 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -201,8 +201,10 @@ impl<'lua> Thread<'lua> { lua.push_ref(&self.0); let thread_state = ffi::lua_tothread(lua.state, -1); - #[cfg(feature = "lua54")] + #[cfg(all(feature = "lua54", not(feature = "vendored")))] let status = ffi::lua_resetthread(thread_state); + #[cfg(all(feature = "lua54", feature = "vendored"))] + let status = ffi::lua_closethread(thread_state, lua.state); #[cfg(feature = "lua54")] if status != ffi::LUA_OK { return Err(pop_error(thread_state, status)); diff --git a/src/util.rs b/src/util.rs index 45a47f50..24b9dc34 100644 --- a/src/util.rs +++ b/src/util.rs @@ -245,7 +245,8 @@ pub unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> Error { // Uses 3 (or 1 if unprotected) stack spaces, does not call checkstack. #[inline(always)] pub unsafe fn push_string(state: *mut ffi::lua_State, s: &[u8], protect: bool) -> Result<()> { - if protect { + // Always use protected mode if the string is too long + if protect || s.len() > (1 << 30) { protect_lua!(state, 0, 1, |state| { ffi::lua_pushlstring(state, s.as_ptr() as *const c_char, s.len()); }) @@ -427,11 +428,17 @@ unsafe fn init_userdata_metatable_index(state: *mut ffi::lua_State) -> Result<() } ffi::lua_pop(state, 1); - // Create and cache `__index` helper + // Create and cache `__index` generator let code = cstr!( r#" local error, isfunction = ... return function (__index, field_getters, methods) + -- Fastpath to return methods table for index access + if __index == nil and field_getters == nil then + return methods + end + + -- Alternatively return a function for index access return function (self, key) if field_getters ~= nil then local field_getter = field_getters[key] @@ -481,7 +488,7 @@ pub unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Re } ffi::lua_pop(state, 1); - // Create and cache `__newindex` helper + // Create and cache `__newindex` generator let code = cstr!( r#" local error, isfunction = ... diff --git a/tarpaulin.toml b/tarpaulin.toml new file mode 100644 index 00000000..f5f71b4f --- /dev/null +++ b/tarpaulin.toml @@ -0,0 +1,8 @@ +[lua54_coverage] +features = "lua54,vendored,async,serialize,macros" + +[lua51_coverage] +features = "lua51,vendored,async,serialize,macros" + +[luau_coverage] +features = "luau,async,serialize,macros" diff --git a/tests/serde.rs b/tests/serde.rs index e161493b..1a11fa53 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -391,31 +391,44 @@ fn test_from_value_newtype_struct() -> Result<(), Box> { #[test] fn test_from_value_enum() -> Result<(), Box> { let lua = Lua::new(); + lua.globals().set("null", lua.null())?; #[derive(Deserialize, PartialEq, Debug)] - enum E { + struct UnitStruct; + + #[derive(Deserialize, PartialEq, Debug)] + enum E { Unit, Integer(u32), Tuple(u32, u32), Struct { a: u32 }, + Wrap(T), } let value = lua.load(r#""Unit""#).eval()?; - let got = lua.from_value(value)?; + let got: E = lua.from_value(value)?; assert_eq!(E::Unit, got); let value = lua.load(r#"{Integer = 1}"#).eval()?; - let got = lua.from_value(value)?; + let got: E = lua.from_value(value)?; assert_eq!(E::Integer(1), got); let value = lua.load(r#"{Tuple = {1, 2}}"#).eval()?; - let got = lua.from_value(value)?; + let got: E = lua.from_value(value)?; assert_eq!(E::Tuple(1, 2), got); let value = lua.load(r#"{Struct = {a = 3}}"#).eval()?; - let got = lua.from_value(value)?; + let got: E = lua.from_value(value)?; assert_eq!(E::Struct { a: 3 }, got); + let value = lua.load(r#"{Wrap = null}"#).eval()?; + let got = lua.from_value(value)?; + assert_eq!(E::Wrap(UnitStruct), got); + + let value = lua.load(r#"{Wrap = null}"#).eval()?; + let got = lua.from_value(value)?; + assert_eq!(E::Wrap(()), got); + Ok(()) } diff --git a/tests/tests.rs b/tests/tests.rs index 7d246563..d20b9da9 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -69,7 +69,7 @@ fn test_safety() -> Result<()> { fn test_load() -> Result<()> { let lua = Lua::new(); - let func = lua.load("return 1+2").into_function()?; + let func = lua.load("\treturn 1+2").into_function()?; let result: i32 = func.call(())?; assert_eq!(result, 3); @@ -991,7 +991,7 @@ fn test_ref_stack_exhaustion() { match catch_unwind(AssertUnwindSafe(|| -> Result<()> { let lua = Lua::new(); let mut vals = Vec::new(); - for _ in 0..1000000 { + for _ in 0..10000000 { vals.push(lua.create_table()?); } Ok(())