Compare commits

..

16 Commits

Author SHA1 Message Date
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
15 changed files with 409 additions and 133 deletions
+13
View File
@@ -1,3 +1,16 @@
## 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
+1 -1
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.6" # remember to update html_root_url and mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@chucklefish.org>"]
edition = "2021"
repository = "https://github.com/khvzak/mlua"
+1 -1
View File
@@ -72,7 +72,7 @@
//! [`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")]
#![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))))]
+80 -52
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()))
}
}
@@ -2016,7 +2030,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,6 +2062,12 @@ 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)?;
@@ -2055,27 +2075,20 @@ impl Lua {
let unref_list = (*self.extra.get()).registry_unref_list.clone();
self.push_value(t)?;
// Try to reuse previously allocated RegistryKey
// 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()) {
// 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,
})
Ok(RegistryKey::new(registry_id, unref_list))
}
}
@@ -2090,16 +2103,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 +2151,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 +2463,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 +2488,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 +2990,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> {
+1 -1
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
}
+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> {
+4 -7
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 {
@@ -326,7 +324,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 +364,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> {
+6 -11
View File
@@ -242,14 +242,9 @@ 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();
// 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 {
protect_lua!(state, 0, 1, |state| {
ffi::lua_pushlstring(state, s.as_ptr() as *const c_char, s.len());
@@ -548,7 +543,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 +568,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 +888,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)
+5 -5
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(),
}
}
+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));
+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();
+33
View File
@@ -793,6 +793,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 +855,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();