Compare commits

...

36 Commits

Author SHA1 Message Date
Alex Orlenko 84811b1be3 v0.8.10 2023-08-16 00:20:05 +01:00
Alex Orlenko 032d4af896 Fix loading luau code starting with \t 2023-08-16 00:15:34 +01:00
Alex Orlenko c9099a4364 Update to Luau 0.590 2023-08-16 00:15:05 +01:00
Alex Orlenko e4eeee05c4 Pin (more strict) lua-src and luajit-src versions 2023-08-16 00:09:20 +01:00
Alex Orlenko 15e353a7f8 v0.8.9 2023-05-16 23:02:19 +01:00
Alex Orlenko 765117c2bb Update tarpaulin settings 2023-05-16 23:02:17 +01:00
Alex Orlenko 573d71345f Don't set html_root_url (it's not recommended) 2023-05-16 22:55:29 +01:00
Alex Orlenko 72de17bf47 Allow deserializing Lua null into unit(()) or unit struct. See #264 2023-05-16 22:53:37 +01:00
Alex Orlenko 5a96e80266 Use lua_closethread instead of lua_resetthread in vendored mode (introduced in Lua 5.4.6) 2023-05-16 22:50:46 +01:00
Alex Orlenko bfdb4087b8 Update minimal (vendored) Lua 5.4 to 5.4.6 2023-05-16 22:49:49 +01:00
Alex Orlenko eb84284824 Fix ref_stack_exhaustion test (Lua 5.4.6) 2023-05-16 22:12:36 +01:00
Alex Orlenko 34679e105d v0.8.8 2023-03-05 17:50:53 +00:00
Alex Orlenko bc194981fc Optimize userdata methods call when __index and fields_getters are nil 2023-03-05 14:43:12 +00:00
Alex Orlenko c9715aa5d9 Fix potential deadlock when trying to reuse dropped RegistryKey.
If no free registry id found, we call protect_lua! macro while keeping mutex guard to the unref list.
Protected calls can trigger garbage collection and if RegistryKey is placed in userdata being collected, this can lead to deadlock.
The solution is drop mutex guard as soon as possible.
Also this commit includes optimization in creating reference in Lua registry.
2023-03-05 14:39:22 +00:00
Alex Orlenko c108dc8213 Force protected mode for long enough strings 2023-03-05 14:35:15 +00:00
Alex Orlenko e86ef9d755 v0.8.7 2023-01-04 16:15:23 +00:00
Alex Orlenko 05eb20f9c6 Fix subtraction overflow when calculating index for MultiValue::get().
Return `None` instead of panic in debug mode.
Fixes #232
2022-12-15 21:10:31 +00:00
Alex Orlenko 9716918517 Fix lifetime of DebugNames in Debug::names() and DebugSource in Debug::source().
This can cause use-after-free if used wrong.
Now invalid code would not compile.
Reported in #230
2022-12-09 23:55:38 +00:00
Alex Orlenko c88417a3b6 Redefine _VERSION for Luau to include version number.
https://github.com/khvzak/mlua/discussions/228
2022-12-03 21:21:28 +00:00
Alex Orlenko 6e95386f30 Update Luau to 0.555 (luau-src 0.5.0)
This version includes inreased LUAI_MAXCSTACK limit (100000)
2022-12-03 17:55:26 +00:00
Alex Orlenko 6807dfa22e v0.8.6 2022-11-07 01:05:14 +00:00
Alex Orlenko f27c49f931 Fix bug when recycled Registry slot can be set to Nil.
This can result in allocating the same slot twice and rewriting old value.
Lua uses (registry) table length to find next free slot and having Nil in the middle of the table can impact length calculation.
With this fix we ensure that Nil values uses a special LUA_REFNIL slot.
2022-11-07 00:10:57 +00:00
Alex Orlenko 693a808b6e v0.8.5 2022-10-30 12:31:26 +00:00
Alex Orlenko a6ca65aa74 Better checks and tests when trying to modify a Luau readonly table 2022-10-30 11:41:09 +00:00
Alex Orlenko a7278cab78 Fix Table::raw_push for luau when readonly 2022-10-29 23:02:19 +01:00
Alex Orlenko e1bbd00a33 Fix getting caller information from Lua::load 2022-10-28 10:43:05 +01:00
Alex Orlenko e98998d6ac Add push/pop methods to Table 2022-10-23 23:27:17 +01:00
Alex Orlenko cca177df5b Minor serde optimizations 2022-10-23 03:13:24 +01:00
Alex Orlenko 65396a910f Optimize Lua::create_table to use reference thread if possible 2022-10-23 00:24:04 +01:00
Alex Orlenko de69d10d73 Optimize Lua::create_string to use reference thread if possible 2022-10-23 00:14:13 +01:00
Alex Orlenko 959e61b97c Optimize tables:
- Use reference thread directly for simple cases
- Fix issue when calling raw_set on readonly table (Luau)
- Add fasttrack methods for get/set/len when metatable is not set
2022-10-22 23:45:58 +01:00
Alex Orlenko 1040c0a347 Fix typo 2022-10-22 23:44:21 +01:00
Alex Orlenko 9045419586 Strings optimizations: get access to bytes directly from ref thread. 2022-10-22 23:43:08 +01:00
Alex Orlenko fcd162f3eb Replace Lua::ref_thread_exec 2022-10-22 23:39:49 +01:00
Alex Orlenko 0354703dbf Update formatting (Lua registry values) 2022-10-22 23:34:19 +01:00
Alex Orlenko 9831d0e397 Check that allocation does not exceed isize::MAX
See https://github.com/rust-lang/rust/issues/101899
2022-10-22 23:13:02 +01:00
27 changed files with 564 additions and 170 deletions
+1 -1
View File
@@ -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
+37
View File
@@ -1,3 +1,40 @@
## 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
## v0.8.5
- Fixed potential unsoundness when using `Layout::from_size_align_unchecked` and Rust 1.65+
- Performance optimizations around string and table creation in standalone mode
- Added fast track path to Table `get`/`set`/`len` methods without metatable
- Added new methods `push`/`pop`/`raw_push`/`raw_pop` to Table
- Fix getting caller information from `Lua::load`
- Better checks and tests when trying to modify a Luau readonly table
## v0.8.4
- Minimal Luau updated to 0.548
+4 -4
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua"
version = "0.8.4" # remember to update html_root_url and mlua_derive
version = "0.8.10" # remember to update mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@chucklefish.org>"]
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.4.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"
+1 -1
View File
@@ -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
+1
View File
@@ -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)
+3
View File
@@ -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;
+1 -1
View File
@@ -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') {
+6 -3
View File
@@ -7,12 +7,15 @@ use std::ptr;
// Option for multiple returns in 'lua_pcall' and 'lua_call'
pub const LUA_MULTRET: c_int = -1;
// Max number of Lua stack slots
const LUAI_MAXCSTACK: c_int = 100000;
//
// Pseudo-indices
//
pub const LUA_REGISTRYINDEX: c_int = -10000;
pub const LUA_ENVIRONINDEX: c_int = -10001;
pub const LUA_GLOBALSINDEX: c_int = -10002;
pub const LUA_REGISTRYINDEX: c_int = -LUAI_MAXCSTACK - 2000;
pub const LUA_ENVIRONINDEX: c_int = -LUAI_MAXCSTACK - 2001;
pub const LUA_GLOBALSINDEX: c_int = -LUAI_MAXCSTACK - 2002;
pub const fn lua_upvalueindex(i: c_int) -> c_int {
LUA_GLOBALSINDEX - i
+2 -1
View File
@@ -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" {
+2 -2
View File
@@ -63,7 +63,7 @@ impl<'lua> Debug<'lua> {
}
/// Corresponds to the `n` what mask.
pub fn names(&self) -> DebugNames<'lua> {
pub fn names(&self) -> DebugNames {
unsafe {
#[cfg(not(feature = "luau"))]
mlua_assert!(
@@ -87,7 +87,7 @@ impl<'lua> Debug<'lua> {
}
/// Corresponds to the `S` what mask.
pub fn source(&self) -> DebugSource<'lua> {
pub fn source(&self) -> DebugSource {
unsafe {
#[cfg(not(feature = "luau"))]
mlua_assert!(
-2
View File
@@ -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.4")]
// Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any*
// warnings at all.
#![doc(test(attr(deny(warnings))))]
+95 -60
View File
@@ -88,6 +88,8 @@ pub(crate) struct ExtraData {
registered_userdata: FxHashMap<TypeId, c_int>,
registered_userdata_mt: FxHashMap<*const c_void, Option<TypeId>>,
// When Lua instance dropped, setting `None` would prevent collecting `RegistryKey`s
registry_unref_list: Arc<Mutex<Option<Vec<c_int>>>>,
#[cfg(not(feature = "send"))]
@@ -402,6 +404,11 @@ impl Lua {
return ptr::null_mut();
}
// Do not allocate more than isize::MAX
if nsize > isize::MAX as usize {
return ptr::null_mut();
}
// Are we fit to the memory limits?
let mut mem_diff = nsize as isize;
if !ptr.is_null() {
@@ -411,12 +418,14 @@ impl Lua {
if mem_info.memory_limit > 0 && new_used_memory > mem_info.memory_limit {
return ptr::null_mut();
}
let new_layout = Layout::from_size_align_unchecked(nsize, ffi::SYS_MIN_ALIGN);
mem_info.used_memory += mem_diff;
if ptr.is_null() {
// Allocate new memory
let new_layout = match Layout::from_size_align(nsize, ffi::SYS_MIN_ALIGN) {
Ok(layout) => layout,
Err(_) => return ptr::null_mut(),
};
let new_ptr = alloc::alloc(new_layout) as *mut c_void;
if new_ptr.is_null() {
alloc::handle_alloc_error(new_layout);
@@ -428,7 +437,7 @@ impl Lua {
let old_layout = Layout::from_size_align_unchecked(osize, ffi::SYS_MIN_ALIGN);
let new_ptr = alloc::realloc(ptr as *mut u8, old_layout, nsize) as *mut c_void;
if new_ptr.is_null() {
alloc::handle_alloc_error(new_layout);
alloc::handle_alloc_error(old_layout);
}
new_ptr
}
@@ -882,10 +891,8 @@ impl Lua {
ffi::luaL_sandboxthread(state);
} else {
// Restore original `LUA_GLOBALSINDEX`
self.ref_thread_exec(|ref_thread| {
ffi::lua_xpush(ref_thread, state, ffi::LUA_GLOBALSINDEX);
ffi::lua_replace(state, ffi::LUA_GLOBALSINDEX);
});
ffi::lua_xpush(self.ref_thread(), state, ffi::LUA_GLOBALSINDEX);
ffi::lua_replace(state, ffi::LUA_GLOBALSINDEX);
ffi::luaL_sandbox(state, 0);
}
})?;
@@ -1366,9 +1373,8 @@ impl Lua {
where
S: AsChunk<'lua> + ?Sized,
{
let name = chunk
.name()
.unwrap_or_else(|| Location::caller().to_string());
let caller = Location::caller();
let name = chunk.name().unwrap_or_else(|| caller.to_string());
Chunk {
lua: self,
@@ -1428,11 +1434,15 @@ impl Lua {
S: AsRef<[u8]> + ?Sized,
{
unsafe {
if self.unlikely_memory_error() {
push_string(self.ref_thread(), s.as_ref(), false)?;
return Ok(String(self.pop_ref_thread()));
}
let _sg = StackGuard::new(self.state);
check_stack(self.state, 3)?;
let protect = !self.unlikely_memory_error();
push_string(self.state, s, protect)?;
push_string(self.state, s.as_ref(), true)?;
Ok(String(self.pop_ref()))
}
}
@@ -1448,11 +1458,15 @@ impl Lua {
/// Lua may use these hints to preallocate memory for the new table.
pub fn create_table_with_capacity(&self, narr: c_int, nrec: c_int) -> Result<Table> {
unsafe {
if self.unlikely_memory_error() {
push_table(self.ref_thread(), narr, nrec, false)?;
return Ok(Table(self.pop_ref_thread()));
}
let _sg = StackGuard::new(self.state);
check_stack(self.state, 3)?;
let protect = !self.unlikely_memory_error();
push_table(self.state, narr, nrec, protect)?;
push_table(self.state, narr, nrec, true)?;
Ok(Table(self.pop_ref()))
}
}
@@ -1724,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
@@ -2016,7 +2032,7 @@ impl Lua {
check_stack(self.state, 3)?;
let protect = !self.unlikely_memory_error();
push_string(self.state, name, protect)?;
push_string(self.state, name.as_ref(), protect)?;
ffi::lua_rawget(self.state, ffi::LUA_REGISTRYINDEX);
self.pop_value()
@@ -2048,34 +2064,38 @@ impl Lua {
/// [`RegistryKey`]: crate::RegistryKey
pub fn create_registry_value<'lua, T: ToLua<'lua>>(&'lua self, t: T) -> Result<RegistryKey> {
let t = t.to_lua(self)?;
if t == Value::Nil {
// Special case to skip calling `luaL_ref` and use `LUA_REFNIL` instead
let unref_list = unsafe { (*self.extra.get()).registry_unref_list.clone() };
return Ok(RegistryKey::new(ffi::LUA_REFNIL, unref_list));
}
unsafe {
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 RegistryKey
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()) {
// Try to reuse previously allocated slot
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 {
registry_id,
unref_list,
});
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)
})?;
Ok(RegistryKey {
registry_id,
unref_list,
})
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))
}
}
@@ -2090,16 +2110,16 @@ impl Lua {
return Err(Error::MismatchedRegistryKey);
}
let value = unsafe {
let _sg = StackGuard::new(self.state);
check_stack(self.state, 1)?;
let value = match key.is_nil() {
true => Value::Nil,
false => unsafe {
let _sg = StackGuard::new(self.state);
check_stack(self.state, 1)?;
ffi::lua_rawgeti(
self.state,
ffi::LUA_REGISTRYINDEX,
key.registry_id as Integer,
);
self.pop_value()
let id = key.registry_id as Integer;
ffi::lua_rawgeti(self.state, ffi::LUA_REGISTRYINDEX, id);
self.pop_value()
},
};
T::from_lua(value, self)
}
@@ -2138,20 +2158,31 @@ impl Lua {
}
let t = t.to_lua(self)?;
if t == Value::Nil && key.is_nil() {
// Nothing to replace
return Ok(());
} else if t != Value::Nil && key.registry_id == ffi::LUA_REFNIL {
// We cannot update `LUA_REFNIL` slot
let err = "cannot replace nil value with non-nil".to_string();
return Err(Error::RuntimeError(err));
}
unsafe {
let _sg = StackGuard::new(self.state);
check_stack(self.state, 2)?;
self.push_value(t)?;
let id = key.registry_id as Integer;
if t == Value::Nil {
self.push_value(Value::Integer(id))?;
key.set_nil(true);
} else {
self.push_value(t)?;
key.set_nil(false);
}
// It must be safe to replace the value without triggering memory error
ffi::lua_rawseti(
self.state,
ffi::LUA_REGISTRYINDEX,
key.registry_id as Integer,
);
Ok(())
ffi::lua_rawseti(self.state, ffi::LUA_REGISTRYINDEX, id);
}
Ok(())
}
/// Returns true if the given `RegistryKey` was created by a `Lua` which shares the underlying
@@ -2439,6 +2470,13 @@ impl Lua {
LuaRef { lua: self, index }
}
// Same as `pop_ref` but assumes the value is already on the reference thread
pub(crate) unsafe fn pop_ref_thread(&self) -> LuaRef {
let extra = &mut *self.extra.get();
let index = ref_stack_pop(extra);
LuaRef { lua: self, index }
}
pub(crate) fn clone_ref<'lua>(&'lua self, lref: &LuaRef<'lua>) -> LuaRef<'lua> {
unsafe {
let extra = &mut *self.extra.get();
@@ -2457,16 +2495,6 @@ impl Lua {
}
}
/// Executes the function provided on the ref thread
#[inline]
pub(crate) unsafe fn ref_thread_exec<F, R>(&self, f: F) -> R
where
F: FnOnce(*mut ffi::lua_State) -> R,
{
let ref_thread = (*self.extra.get()).ref_thread;
f(ref_thread)
}
unsafe fn push_userdata_metatable<T: 'static + UserData>(&self) -> Result<()> {
let extra = &mut *self.extra.get();
@@ -2969,6 +2997,13 @@ impl Lua {
}
}
impl LuaInner {
#[inline(always)]
pub(crate) fn ref_thread(&self) -> *mut ffi::lua_State {
unsafe { (*self.extra.get()).ref_thread }
}
}
struct StateGuard<'a>(&'a mut LuaInner, *mut ffi::lua_State);
impl<'a> StateGuard<'a> {
+6
View File
@@ -22,6 +22,12 @@ impl Lua {
globals.raw_set("require", self.create_function(lua_require)?)?;
globals.raw_set("vector", self.create_c_function(lua_vector)?)?;
// Set `_VERSION` global to include version number
// The environment variable `LUAU_VERSION` set by the build script
if let Some(version) = option_env!("LUAU_VERSION") {
globals.raw_set("_VERSION", format!("Luau {version}"))?;
}
Ok(())
}
}
+24 -2
View File
@@ -71,7 +71,7 @@ impl Options {
///
/// [`deny_recursive_tables`]: #structfield.deny_recursive_tables
#[must_use]
pub fn deny_recursive_tables(mut self, enabled: bool) -> Self {
pub const fn deny_recursive_tables(mut self, enabled: bool) -> Self {
self.deny_recursive_tables = enabled;
self
}
@@ -327,9 +327,31 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
visitor.visit_newtype_struct(self)
}
#[inline]
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value>
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<V>(self, _name: &'static str, visitor: V) -> Result<V::Value>
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
}
}
+3 -7
View File
@@ -10,7 +10,7 @@ use crate::ffi;
use crate::lua::Lua;
use crate::table::Table;
use crate::types::LightUserData;
use crate::util::{assert_stack, check_stack, StackGuard};
use crate::util::check_stack;
use crate::value::Value;
/// Trait for serializing/deserializing Lua values using Serde.
@@ -205,12 +205,8 @@ impl<'lua> LuaSerdeExt<'lua> for Lua {
fn array_metatable(&'lua self) -> Table<'lua> {
unsafe {
let _sg = StackGuard::new(self.state);
assert_stack(self.state, 1);
push_array_metatable(self.state);
Table(self.pop_ref())
push_array_metatable(self.ref_thread());
Table(self.pop_ref_thread())
}
}
+8 -10
View File
@@ -13,7 +13,6 @@ use {
use crate::error::{Error, Result};
use crate::ffi;
use crate::types::LuaRef;
use crate::util::{assert_stack, StackGuard};
/// Handle to an internal Lua string.
///
@@ -40,6 +39,7 @@ impl<'lua> String<'lua> {
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn to_str(&self) -> Result<&str> {
str::from_utf8(self.as_bytes()).map_err(|e| Error::FromLuaConversionError {
from: "string",
@@ -66,6 +66,7 @@ impl<'lua> String<'lua> {
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn to_string_lossy(&self) -> Cow<'_, str> {
StdString::from_utf8_lossy(self.as_bytes())
}
@@ -87,6 +88,7 @@ impl<'lua> String<'lua> {
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn as_bytes(&self) -> &[u8] {
let nulled = self.as_bytes_with_nul();
&nulled[..nulled.len() - 1]
@@ -94,21 +96,17 @@ impl<'lua> String<'lua> {
/// Get the bytes that make up this string, including the trailing nul byte.
pub fn as_bytes_with_nul(&self) -> &[u8] {
let lua = self.0.lua;
let ref_thread = self.0.lua.ref_thread();
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 1);
lua.push_ref(&self.0);
mlua_debug_assert!(
ffi::lua_type(lua.state, -1) == ffi::LUA_TSTRING,
ffi::lua_type(ref_thread, self.0.index) == ffi::LUA_TSTRING,
"string ref is not string type"
);
let mut size = 0;
// This will not trigger a 'm' error, because the reference is guaranteed to be of
// string type
let data = ffi::lua_tolstring(lua.state, -1, &mut size);
let data = ffi::lua_tolstring(ref_thread, self.0.index, &mut size);
slice::from_raw_parts(data as *const u8, size + 1)
}
@@ -121,8 +119,8 @@ impl<'lua> String<'lua> {
/// Typically this function is used only for hashing and debug information.
#[inline]
pub fn to_pointer(&self) -> *const c_void {
let lua = self.0.lua;
unsafe { lua.ref_thread_exec(|refthr| ffi::lua_topointer(refthr, self.0.index)) }
let ref_thread = self.0.lua.ref_thread();
unsafe { ffi::lua_topointer(ref_thread, self.0.index) }
}
}
+152 -25
View File
@@ -58,6 +58,11 @@ impl<'lua> Table<'lua> {
///
/// [`raw_set`]: #method.raw_set
pub fn set<K: ToLua<'lua>, V: ToLua<'lua>>(&self, key: K, value: V) -> Result<()> {
// Fast track
if !self.has_metatable() {
return self.raw_set(key, value);
}
let lua = self.0.lua;
let key = key.to_lua(lua)?;
let value = value.to_lua(lua)?;
@@ -98,6 +103,11 @@ impl<'lua> Table<'lua> {
///
/// [`raw_get`]: #method.raw_get
pub fn get<K: ToLua<'lua>, V: FromLua<'lua>>(&self, key: K) -> Result<V> {
// Fast track
if !self.has_metatable() {
return self.raw_get(key);
}
let lua = self.0.lua;
let key = key.to_lua(lua)?;
@@ -116,18 +126,54 @@ impl<'lua> Table<'lua> {
/// Checks whether the table contains a non-nil value for `key`.
pub fn contains_key<K: ToLua<'lua>>(&self, key: K) -> Result<bool> {
let lua = self.0.lua;
let key = key.to_lua(lua)?;
Ok(self.get::<_, Value>(key)? != Value::Nil)
}
/// Appends a value to the back of the table.
pub fn push<V: ToLua<'lua>>(&self, value: V) -> Result<()> {
// Fast track
if !self.has_metatable() {
return self.raw_push(value);
}
let lua = self.0.lua;
let value = value.to_lua(lua)?;
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 4)?;
lua.push_ref(&self.0);
lua.push_value(key)?;
protect_lua!(lua.state, 2, 1, fn(state) ffi::lua_gettable(state, -2))?;
Ok(ffi::lua_isnil(lua.state, -1) == 0)
lua.push_value(value)?;
protect_lua!(lua.state, 2, 0, fn(state) {
let len = ffi::luaL_len(state, -2) as Integer;
ffi::lua_seti(state, -2, len + 1);
})?
}
Ok(())
}
/// Removes the last element from the table and returns it.
pub fn pop<V: FromLua<'lua>>(&self) -> Result<V> {
// Fast track
if !self.has_metatable() {
return self.raw_pop();
}
let lua = self.0.lua;
let value = unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 4)?;
lua.push_ref(&self.0);
protect_lua!(lua.state, 1, 1, fn(state) {
let len = ffi::luaL_len(state, -1) as Integer;
ffi::lua_geti(state, -1, len);
ffi::lua_pushnil(state);
ffi::lua_seti(state, -3, len);
})?;
lua.pop_value()
};
V::from_lua(value, lua)
}
/// Compares two tables for equality.
@@ -188,6 +234,9 @@ impl<'lua> Table<'lua> {
/// Sets a key-value pair without invoking metamethods.
pub fn raw_set<K: ToLua<'lua>, V: ToLua<'lua>>(&self, key: K, value: V) -> Result<()> {
#[cfg(feature = "luau")]
self.check_readonly_write()?;
let lua = self.0.lua;
let key = key.to_lua(lua)?;
let value = value.to_lua(lua)?;
@@ -199,6 +248,7 @@ impl<'lua> Table<'lua> {
lua.push_ref(&self.0);
lua.push_value(key)?;
lua.push_value(value)?;
if lua.unlikely_memory_error() {
ffi::lua_rawset(lua.state, -3);
ffi::lua_pop(lua.state, 1);
@@ -254,6 +304,56 @@ impl<'lua> Table<'lua> {
}
}
/// Appends a value to the back of the table without invoking metamethods.
pub fn raw_push<V: ToLua<'lua>>(&self, value: V) -> Result<()> {
#[cfg(feature = "luau")]
self.check_readonly_write()?;
let lua = self.0.lua;
let value = value.to_lua(lua)?;
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 4)?;
lua.push_ref(&self.0);
lua.push_value(value)?;
unsafe fn callback(state: *mut ffi::lua_State) {
let len = ffi::lua_rawlen(state, -2) as Integer;
ffi::lua_rawseti(state, -2, len + 1);
}
if lua.unlikely_memory_error() {
callback(lua.state);
} else {
protect_lua!(lua.state, 2, 0, fn(state) callback(state))?;
}
}
Ok(())
}
/// Removes the last element from the table and returns it, without invoking metamethods.
pub fn raw_pop<V: FromLua<'lua>>(&self) -> Result<V> {
#[cfg(feature = "luau")]
self.check_readonly_write()?;
let lua = self.0.lua;
let value = unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 3)?;
lua.push_ref(&self.0);
let len = ffi::lua_rawlen(lua.state, -1) as Integer;
ffi::lua_rawgeti(lua.state, -1, len);
// Set slot to nil (it must be safe to do)
ffi::lua_pushnil(lua.state);
ffi::lua_rawseti(lua.state, -3, len);
lua.pop_value()
};
V::from_lua(value, lua)
}
/// Removes a key from the table.
///
/// If `key` is an integer, mlua shifts down the elements from `table[key+1]`,
@@ -295,6 +395,11 @@ impl<'lua> Table<'lua> {
///
/// [`raw_len`]: #method.raw_len
pub fn len(&self) -> Result<Integer> {
// Fast track
if !self.has_metatable() {
return Ok(self.raw_len());
}
let lua = self.0.lua;
unsafe {
let _sg = StackGuard::new(lua.state);
@@ -307,14 +412,8 @@ impl<'lua> Table<'lua> {
/// Returns the result of the Lua `#` operator, without invoking the `__len` metamethod.
pub fn raw_len(&self) -> Integer {
let lua = self.0.lua;
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 1);
lua.push_ref(&self.0);
ffi::lua_rawlen(lua.state, -1) as Integer
}
let ref_thread = self.0.lua.ref_thread();
unsafe { ffi::lua_rawlen(ref_thread, self.0.index) as Integer }
}
/// Returns a reference to the metatable of this table, or `None` if no metatable is set.
@@ -340,6 +439,12 @@ impl<'lua> Table<'lua> {
/// If `metatable` is `None`, the metatable is removed (if no metatable is set, this does
/// nothing).
pub fn set_metatable(&self, metatable: Option<Table<'lua>>) {
// Workaround to throw readonly error without returning Result
#[cfg(feature = "luau")]
if self.is_readonly() {
panic!("attempt to modify a readonly table");
}
let lua = self.0.lua;
unsafe {
let _sg = StackGuard::new(lua.state);
@@ -355,21 +460,33 @@ impl<'lua> Table<'lua> {
}
}
/// Returns true if the table has metatable attached.
#[doc(hidden)]
#[inline]
pub fn has_metatable(&self) -> bool {
let ref_thread = self.0.lua.ref_thread();
unsafe {
if ffi::lua_getmetatable(ref_thread, self.0.index) != 0 {
ffi::lua_pop(ref_thread, 1);
return true;
}
}
false
}
/// Sets `readonly` attribute on the table.
///
/// Requires `feature = "luau"`
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_readonly(&self, enabled: bool) {
let lua = self.0.lua;
let ref_thread = self.0.lua.ref_thread();
unsafe {
lua.ref_thread_exec(|refthr| {
ffi::lua_setreadonly(refthr, self.0.index, enabled as _);
if !enabled {
// Reset "safeenv" flag
ffi::lua_setsafeenv(refthr, self.0.index, 0);
}
});
ffi::lua_setreadonly(ref_thread, self.0.index, enabled as _);
if !enabled {
// Reset "safeenv" flag
ffi::lua_setsafeenv(ref_thread, self.0.index, 0);
}
}
}
@@ -379,8 +496,8 @@ impl<'lua> Table<'lua> {
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn is_readonly(&self) -> bool {
let lua = self.0.lua;
unsafe { lua.ref_thread_exec(|refthr| ffi::lua_getreadonly(refthr, self.0.index) != 0) }
let ref_thread = self.0.lua.ref_thread();
unsafe { ffi::lua_getreadonly(ref_thread, self.0.index) != 0 }
}
/// Converts the table to a generic C pointer.
@@ -391,8 +508,8 @@ impl<'lua> Table<'lua> {
/// Typically this function is used only for hashing and debug information.
#[inline]
pub fn to_pointer(&self) -> *const c_void {
let lua = self.0.lua;
unsafe { lua.ref_thread_exec(|refthr| ffi::lua_topointer(refthr, self.0.index)) }
let ref_thread = self.0.lua.ref_thread();
unsafe { ffi::lua_topointer(ref_thread, self.0.index) }
}
/// Consume this table and return an iterator over the pairs of the table.
@@ -532,6 +649,16 @@ impl<'lua> Table<'lua> {
ffi::lua_rawequal(lua.state, -1, -2) != 0
}
}
#[cfg(feature = "luau")]
#[inline(always)]
pub(crate) fn check_readonly_write(&self) -> Result<()> {
if self.is_readonly() {
let err = "attempt to modify a readonly table".to_string();
return Err(Error::RuntimeError(err));
}
Ok(())
}
}
impl<'lua> PartialEq for Table<'lua> {
+7 -8
View File
@@ -118,8 +118,7 @@ impl<'lua> Thread<'lua> {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, cmp::max(nargs + 1, 3))?;
let thread_state =
lua.ref_thread_exec(|ref_thread| ffi::lua_tothread(ref_thread, self.0.index));
let thread_state = ffi::lua_tothread(lua.ref_thread(), self.0.index);
let status = ffi::lua_status(thread_state);
if status != ffi::LUA_YIELD && ffi::lua_gettop(thread_state) == 0 {
@@ -160,8 +159,7 @@ impl<'lua> Thread<'lua> {
pub fn status(&self) -> ThreadStatus {
let lua = self.0.lua;
unsafe {
let thread_state =
lua.ref_thread_exec(|ref_thread| ffi::lua_tothread(ref_thread, self.0.index));
let thread_state = ffi::lua_tothread(lua.ref_thread(), self.0.index);
let status = ffi::lua_status(thread_state);
if status != ffi::LUA_OK && status != ffi::LUA_YIELD {
@@ -203,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));
@@ -326,7 +326,7 @@ impl<'lua> Thread<'lua> {
pub fn sandbox(&self) -> Result<()> {
let lua = self.0.lua;
unsafe {
let thread = lua.ref_thread_exec(|t| ffi::lua_tothread(t, self.0.index));
let thread = ffi::lua_tothread(lua.ref_thread(), self.0.index);
check_stack(thread, 1)?;
check_stack(lua.state, 3)?;
// Inherit `LUA_GLOBALSINDEX` from the caller
@@ -366,8 +366,7 @@ impl<'lua, R> Drop for AsyncThread<'lua, R> {
if !lua.recycle_thread(&mut self.thread) {
#[cfg(feature = "lua54")]
if self.thread.status() == ThreadStatus::Error {
let thread_state =
lua.ref_thread_exec(|t| ffi::lua_tothread(t, self.thread.0.index));
let thread_state = ffi::lua_tothread(lua.ref_thread(), self.thread.0.index);
ffi::lua_resetthread(thread_state);
}
}
+33 -4
View File
@@ -1,6 +1,7 @@
use std::cell::UnsafeCell;
use std::hash::{Hash, Hasher};
use std::os::raw::{c_int, c_void};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::{fmt, mem, ptr};
@@ -104,6 +105,7 @@ pub(crate) struct DestructedUserdata;
/// [`AnyUserData::get_user_value`]: crate::AnyUserData::get_user_value
pub struct RegistryKey {
pub(crate) registry_id: c_int,
pub(crate) is_nil: AtomicBool,
pub(crate) unref_list: Arc<Mutex<Option<Vec<c_int>>>>,
}
@@ -129,15 +131,27 @@ impl Eq for RegistryKey {}
impl Drop for RegistryKey {
fn drop(&mut self) {
let mut unref_list = mlua_expect!(self.unref_list.lock(), "unref list poisoned");
if let Some(list) = unref_list.as_mut() {
list.push(self.registry_id);
// We don't need to collect nil slot
if self.registry_id > ffi::LUA_REFNIL {
let mut unref_list = mlua_expect!(self.unref_list.lock(), "unref list poisoned");
if let Some(list) = unref_list.as_mut() {
list.push(self.registry_id);
}
}
}
}
impl RegistryKey {
// Destroys the RegistryKey without adding to the drop list
// Creates a new instance of `RegistryKey`
pub(crate) const fn new(id: c_int, unref_list: Arc<Mutex<Option<Vec<c_int>>>>) -> Self {
RegistryKey {
registry_id: id,
is_nil: AtomicBool::new(id == ffi::LUA_REFNIL),
unref_list,
}
}
// Destroys the `RegistryKey` without adding to the unref list
pub(crate) fn take(self) -> c_int {
let registry_id = self.registry_id;
unsafe {
@@ -146,6 +160,21 @@ impl RegistryKey {
}
registry_id
}
// Returns true if this `RegistryKey` holds a nil value
#[inline(always)]
pub(crate) fn is_nil(&self) -> bool {
self.is_nil.load(Ordering::Relaxed)
}
// Marks value of this `RegistryKey` as `Nil`
#[inline(always)]
pub(crate) fn set_nil(&self, enabled: bool) {
// We cannot replace previous value with nil in as this will break
// Lua mechanism to find free keys.
// Instead, we set a special flag to mark value as nil.
self.is_nil.store(enabled, Ordering::Relaxed);
}
}
pub(crate) struct LuaRef<'lua> {
+16 -14
View File
@@ -242,15 +242,11 @@ pub unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> Error {
}
}
// Uses 3 stack spaces, does not call checkstack.
#[inline]
pub unsafe fn push_string<S: AsRef<[u8]> + ?Sized>(
state: *mut ffi::lua_State,
s: &S,
protect: bool,
) -> Result<()> {
let s = s.as_ref();
if protect {
// 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<()> {
// 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());
})
@@ -432,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]
@@ -486,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 = ...
@@ -548,7 +550,7 @@ pub unsafe fn init_userdata_metatable<T>(
// Push `__index` generator function
init_userdata_metatable_index(state)?;
push_string(state, "__index", true)?;
push_string(state, b"__index", true)?;
let index_type = ffi::lua_rawget(state, -3);
match index_type {
ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => {
@@ -573,7 +575,7 @@ pub unsafe fn init_userdata_metatable<T>(
// Push `__newindex` generator function
init_userdata_metatable_newindex(state)?;
push_string(state, "__newindex", true)?;
push_string(state, b"__newindex", true)?;
let newindex_type = ffi::lua_rawget(state, -3);
match newindex_type {
ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => {
@@ -893,7 +895,7 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> {
}
}?;
push_string(state, &*err_buf, true)?;
push_string(state, (*err_buf).as_bytes(), true)?;
(*err_buf).clear();
Ok(1)
+9 -7
View File
@@ -111,11 +111,11 @@ impl<'lua> Value<'lua> {
Value::LightUserData(ud) => ud.0,
Value::Table(t) => t.to_pointer(),
Value::String(s) => s.to_pointer(),
Value::Function(Function(v))
| Value::Thread(Thread(v))
| Value::UserData(AnyUserData(v)) => v
.lua
.ref_thread_exec(|refthr| ffi::lua_topointer(refthr, v.index)),
Value::Function(Function(r))
| Value::Thread(Thread(r))
| Value::UserData(AnyUserData(r)) => {
ffi::lua_topointer(r.lua.ref_thread(), r.index)
}
_ => ptr::null(),
}
}
@@ -197,7 +197,6 @@ pub struct MultiValue<'lua>(Vec<Value<'lua>>);
impl<'lua> MultiValue<'lua> {
/// Creates an empty `MultiValue` containing no values.
#[inline]
pub const fn new() -> MultiValue<'lua> {
MultiValue(Vec::new())
}
@@ -276,7 +275,10 @@ impl<'lua> MultiValue<'lua> {
#[inline]
pub fn get(&self, index: usize) -> Option<&Value<'lua>> {
self.0.get(self.0.len() - index - 1)
if index < self.0.len() {
return self.0.get(self.0.len() - index - 1);
}
None
}
#[inline]
+8
View File
@@ -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"
+26 -9
View File
@@ -1,7 +1,9 @@
#![cfg(feature = "luau")]
use std::env;
use std::fmt::Debug;
use std::fs;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
@@ -75,18 +77,33 @@ fn test_vectors() -> Result<()> {
fn test_readonly_table() -> Result<()> {
let lua = Lua::new();
let t = lua.create_table()?;
let t = lua.create_sequence_from([1])?;
assert!(!t.is_readonly());
t.set_readonly(true);
assert!(t.is_readonly());
match t.set("key", "value") {
Err(Error::RuntimeError(err)) if err.contains("attempt to modify a readonly table") => {}
r => panic!(
"expected RuntimeError(...) with a specific message, got {:?}",
r
),
};
#[track_caller]
fn check_readonly_error<T: Debug>(res: Result<T>) {
match res {
Err(Error::RuntimeError(e)) if e.contains("attempt to modify a readonly table") => {}
r => panic!("expected RuntimeError(...) with a specific message, got {r:?}"),
}
}
check_readonly_error(t.set("key", "value"));
check_readonly_error(t.raw_set("key", "value"));
check_readonly_error(t.raw_insert(1, "value"));
check_readonly_error(t.raw_remove(1));
check_readonly_error(t.push("value"));
check_readonly_error(t.pop::<Value>());
check_readonly_error(t.raw_push("value"));
check_readonly_error(t.raw_pop::<Value>());
// Special case
match catch_unwind(AssertUnwindSafe(|| t.set_metatable(None))) {
Ok(_) => panic!("expected panic, got nothing"),
Err(_) => {}
}
Ok(())
}
@@ -220,7 +237,7 @@ fn test_interrupts() -> Result<()> {
}
#[test]
fn test_coverate() -> Result<()> {
fn test_coverage() -> Result<()> {
let lua = Lua::new();
lua.set_compiler(Compiler::default().set_coverage_level(1));
+18 -5
View File
@@ -391,31 +391,44 @@ fn test_from_value_newtype_struct() -> Result<(), Box<dyn std::error::Error>> {
#[test]
fn test_from_value_enum() -> Result<(), Box<dyn std::error::Error>> {
let lua = Lua::new();
lua.globals().set("null", lua.null())?;
#[derive(Deserialize, PartialEq, Debug)]
enum E {
struct UnitStruct;
#[derive(Deserialize, PartialEq, Debug)]
enum E<T = ()> {
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(())
}
+43
View File
@@ -109,6 +109,49 @@ fn test_table() -> Result<()> {
Ok(())
}
#[test]
fn test_table_push_pop() -> Result<()> {
let lua = Lua::new();
// Test raw access
let table1 = lua.create_sequence_from(vec![123])?;
table1.raw_push(321)?;
assert_eq!(
table1
.clone()
.raw_sequence_values::<i64>()
.collect::<Result<Vec<_>>>()?,
vec![123, 321]
);
assert_eq!(table1.raw_pop::<i64>()?, 321);
assert_eq!(table1.raw_pop::<i64>()?, 123);
assert_eq!(table1.raw_pop::<Value>()?, Value::Nil); // An extra pop should do nothing
assert_eq!(table1.raw_len(), 0);
// Test access through metamethods
let table2 = lua
.load(
r#"
local proxy_table = {234}
table2 = setmetatable({}, {
__len = function() return #proxy_table end,
__index = proxy_table,
__newindex = proxy_table,
})
return table2
"#,
)
.eval::<Table>()?;
table2.push(345)?;
assert_eq!(table2.len()?, 2);
assert_eq!(table2.pop::<i64>()?, 345);
assert_eq!(table2.pop::<i64>()?, 234);
assert_eq!(table2.pop::<Value>()?, Value::Nil);
assert_eq!(table2.len()?, 0);
Ok(())
}
#[test]
fn test_table_sequence_from() -> Result<()> {
let lua = Lua::new();
+39 -3
View File
@@ -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);
@@ -276,7 +276,10 @@ fn test_error() -> Result<()> {
end, 3)
local function handler(err)
if string.match(_VERSION, ' 5%.1$') or string.match(_VERSION, ' 5%.2$') or _VERSION == "Luau" then
if string.match(_VERSION, " 5%.1$")
or string.match(_VERSION, " 5%.2$")
or string.match(_VERSION, "Luau")
then
-- Special case for Lua 5.1/5.2 and Luau
local caps = string.match(err, ': (%d+)$')
if caps then
@@ -793,6 +796,17 @@ fn test_replace_registry_value() -> Result<()> {
let key = lua.create_registry_value::<i32>(42)?;
lua.replace_registry_value(&key, "new value")?;
assert_eq!(lua.registry_value::<String>(&key)?, "new value");
lua.replace_registry_value(&key, Value::Nil)?;
assert_eq!(lua.registry_value::<Value>(&key)?, Value::Nil);
lua.replace_registry_value(&key, 123)?;
assert_eq!(lua.registry_value::<i32>(&key)?, 123);
// It should be impossible to replace (initial) nil value with non-nil
let key2 = lua.create_registry_value(Value::Nil)?;
match lua.replace_registry_value(&key2, "abc") {
Err(Error::RuntimeError(_)) => {}
r => panic!("expected RuntimeError, got {r:?}"),
}
Ok(())
}
@@ -844,6 +858,28 @@ fn test_mismatched_registry_key() -> Result<()> {
Ok(())
}
#[test]
fn test_registry_value_reuse() -> Result<()> {
let lua = Lua::new();
let r1 = lua.create_registry_value("value1")?;
let r1_slot = format!("{r1:?}");
drop(r1);
// Previous slot must not be reused by nil value
let r2 = lua.create_registry_value(Value::Nil)?;
let r2_slot = format!("{r2:?}");
assert_ne!(r1_slot, r2_slot);
drop(r2);
// But should be reused by non-nil value
let r3 = lua.create_registry_value("value3")?;
let r3_slot = format!("{r3:?}");
assert_eq!(r1_slot, r3_slot);
Ok(())
}
#[test]
fn test_application_data() -> Result<()> {
let lua = Lua::new();
@@ -955,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(())
+19 -1
View File
@@ -1,6 +1,6 @@
use std::ptr;
use mlua::{Lua, Result, Value};
use mlua::{Lua, MultiValue, Result, Value};
#[test]
fn test_value_eq() -> Result<()> {
@@ -63,3 +63,21 @@ fn test_value_eq() -> Result<()> {
Ok(())
}
#[test]
fn test_multi_value() {
let mut multi_value = MultiValue::new();
assert_eq!(multi_value.len(), 0);
assert_eq!(multi_value.get(0), None);
multi_value.push_front(Value::Number(2.));
multi_value.push_front(Value::Number(1.));
assert_eq!(multi_value.get(0), Some(&Value::Number(1.)));
assert_eq!(multi_value.get(1), Some(&Value::Number(2.)));
assert_eq!(multi_value.pop_front(), Some(Value::Number(1.)));
assert_eq!(multi_value[0], Value::Number(2.));
multi_value.clear();
assert!(multi_value.is_empty());
}