Better checks and tests when trying to modify a Luau readonly table
This commit is contained in:
parent
a7278cab78
commit
a6ca65aa74
41
src/table.rs
41
src/table.rs
|
@ -234,6 +234,9 @@ impl<'lua> Table<'lua> {
|
||||||
|
|
||||||
/// Sets a key-value pair without invoking metamethods.
|
/// Sets a key-value pair without invoking metamethods.
|
||||||
pub fn raw_set<K: ToLua<'lua>, V: ToLua<'lua>>(&self, key: K, value: V) -> Result<()> {
|
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 lua = self.0.lua;
|
||||||
let key = key.to_lua(lua)?;
|
let key = key.to_lua(lua)?;
|
||||||
let value = value.to_lua(lua)?;
|
let value = value.to_lua(lua)?;
|
||||||
|
@ -246,13 +249,7 @@ impl<'lua> Table<'lua> {
|
||||||
lua.push_value(key)?;
|
lua.push_value(key)?;
|
||||||
lua.push_value(value)?;
|
lua.push_value(value)?;
|
||||||
|
|
||||||
#[cfg(not(feature = "luau"))]
|
if lua.unlikely_memory_error() {
|
||||||
let protect = !lua.unlikely_memory_error();
|
|
||||||
// If Luau table is readonly it will throw an exception
|
|
||||||
#[cfg(feature = "luau")]
|
|
||||||
let protect = !lua.unlikely_memory_error() || self.is_readonly();
|
|
||||||
|
|
||||||
if !protect {
|
|
||||||
ffi::lua_rawset(lua.state, -3);
|
ffi::lua_rawset(lua.state, -3);
|
||||||
ffi::lua_pop(lua.state, 1);
|
ffi::lua_pop(lua.state, 1);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -309,8 +306,12 @@ impl<'lua> Table<'lua> {
|
||||||
|
|
||||||
/// Appends a value to the back of the table without invoking metamethods.
|
/// Appends a value to the back of the table without invoking metamethods.
|
||||||
pub fn raw_push<V: ToLua<'lua>>(&self, value: V) -> Result<()> {
|
pub fn raw_push<V: ToLua<'lua>>(&self, value: V) -> Result<()> {
|
||||||
|
#[cfg(feature = "luau")]
|
||||||
|
self.check_readonly_write()?;
|
||||||
|
|
||||||
let lua = self.0.lua;
|
let lua = self.0.lua;
|
||||||
let value = value.to_lua(lua)?;
|
let value = value.to_lua(lua)?;
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let _sg = StackGuard::new(lua.state);
|
let _sg = StackGuard::new(lua.state);
|
||||||
check_stack(lua.state, 4)?;
|
check_stack(lua.state, 4)?;
|
||||||
|
@ -323,12 +324,7 @@ impl<'lua> Table<'lua> {
|
||||||
ffi::lua_rawseti(state, -2, len + 1);
|
ffi::lua_rawseti(state, -2, len + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "luau"))]
|
if lua.unlikely_memory_error() {
|
||||||
let protect = !lua.unlikely_memory_error();
|
|
||||||
// If Luau table is readonly it will throw an exception
|
|
||||||
#[cfg(feature = "luau")]
|
|
||||||
let protect = !lua.unlikely_memory_error() || self.is_readonly();
|
|
||||||
if !protect {
|
|
||||||
callback(lua.state);
|
callback(lua.state);
|
||||||
} else {
|
} else {
|
||||||
protect_lua!(lua.state, 2, 0, fn(state) callback(state))?;
|
protect_lua!(lua.state, 2, 0, fn(state) callback(state))?;
|
||||||
|
@ -339,6 +335,9 @@ impl<'lua> Table<'lua> {
|
||||||
|
|
||||||
/// Removes the last element from the table and returns it, without invoking metamethods.
|
/// Removes the last element from the table and returns it, without invoking metamethods.
|
||||||
pub fn raw_pop<V: FromLua<'lua>>(&self) -> Result<V> {
|
pub fn raw_pop<V: FromLua<'lua>>(&self) -> Result<V> {
|
||||||
|
#[cfg(feature = "luau")]
|
||||||
|
self.check_readonly_write()?;
|
||||||
|
|
||||||
let lua = self.0.lua;
|
let lua = self.0.lua;
|
||||||
let value = unsafe {
|
let value = unsafe {
|
||||||
let _sg = StackGuard::new(lua.state);
|
let _sg = StackGuard::new(lua.state);
|
||||||
|
@ -440,6 +439,12 @@ impl<'lua> Table<'lua> {
|
||||||
/// If `metatable` is `None`, the metatable is removed (if no metatable is set, this does
|
/// If `metatable` is `None`, the metatable is removed (if no metatable is set, this does
|
||||||
/// nothing).
|
/// nothing).
|
||||||
pub fn set_metatable(&self, metatable: Option<Table<'lua>>) {
|
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;
|
let lua = self.0.lua;
|
||||||
unsafe {
|
unsafe {
|
||||||
let _sg = StackGuard::new(lua.state);
|
let _sg = StackGuard::new(lua.state);
|
||||||
|
@ -644,6 +649,16 @@ impl<'lua> Table<'lua> {
|
||||||
ffi::lua_rawequal(lua.state, -1, -2) != 0
|
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> {
|
impl<'lua> PartialEq for Table<'lua> {
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
#![cfg(feature = "luau")]
|
#![cfg(feature = "luau")]
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::fmt::Debug;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::panic::{catch_unwind, AssertUnwindSafe};
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -75,18 +77,33 @@ fn test_vectors() -> Result<()> {
|
||||||
fn test_readonly_table() -> Result<()> {
|
fn test_readonly_table() -> Result<()> {
|
||||||
let lua = Lua::new();
|
let lua = Lua::new();
|
||||||
|
|
||||||
let t = lua.create_table()?;
|
let t = lua.create_sequence_from([1])?;
|
||||||
assert!(!t.is_readonly());
|
assert!(!t.is_readonly());
|
||||||
t.set_readonly(true);
|
t.set_readonly(true);
|
||||||
assert!(t.is_readonly());
|
assert!(t.is_readonly());
|
||||||
|
|
||||||
match t.set("key", "value") {
|
#[track_caller]
|
||||||
Err(Error::RuntimeError(err)) if err.contains("attempt to modify a readonly table") => {}
|
fn check_readonly_error<T: Debug>(res: Result<T>) {
|
||||||
r => panic!(
|
match res {
|
||||||
"expected RuntimeError(...) with a specific message, got {:?}",
|
Err(Error::RuntimeError(e)) if e.contains("attempt to modify a readonly table") => {}
|
||||||
r
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue