mirror of
https://github.com/mlua-rs/mlua
synced 2026-06-08 16:05:43 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 693a808b6e | |||
| a6ca65aa74 | |||
| a7278cab78 | |||
| e1bbd00a33 | |||
| e98998d6ac | |||
| cca177df5b | |||
| 65396a910f | |||
| de69d10d73 | |||
| 959e61b97c | |||
| 1040c0a347 | |||
| 9045419586 | |||
| fcd162f3eb | |||
| 0354703dbf | |||
| 9831d0e397 |
@@ -1,3 +1,12 @@
|
||||
## 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
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mlua"
|
||||
version = "0.8.4" # remember to update html_root_url and mlua_derive
|
||||
version = "0.8.5" # 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
@@ -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.5")]
|
||||
// Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any*
|
||||
// warnings at all.
|
||||
#![doc(test(attr(deny(warnings))))]
|
||||
|
||||
+46
-37
@@ -402,6 +402,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 +416,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 +435,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 +889,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 +1371,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 +1432,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 +1456,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 +2028,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()
|
||||
@@ -2094,11 +2106,8 @@ impl Lua {
|
||||
let _sg = StackGuard::new(self.state);
|
||||
check_stack(self.state, 1)?;
|
||||
|
||||
ffi::lua_rawgeti(
|
||||
self.state,
|
||||
ffi::LUA_REGISTRYINDEX,
|
||||
key.registry_id as Integer,
|
||||
);
|
||||
let id = key.registry_id as Integer;
|
||||
ffi::lua_rawgeti(self.state, ffi::LUA_REGISTRYINDEX, id);
|
||||
self.pop_value()
|
||||
};
|
||||
T::from_lua(value, self)
|
||||
@@ -2144,14 +2153,10 @@ impl Lua {
|
||||
|
||||
self.push_value(t)?;
|
||||
// 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(())
|
||||
let id = key.registry_id as Integer;
|
||||
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 +2444,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 +2469,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 +2971,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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-11
@@ -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
@@ -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
@@ -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));
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user