mirror of
https://github.com/mlua-rs/mlua
synced 2026-06-08 16:05:43 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d607039a31 | |||
| 0ea65a2985 | |||
| 8c333354d3 | |||
| f63f147265 | |||
| 595dc3e95f | |||
| ac28c8d8d2 | |||
| d5315da8d1 | |||
| a7cc7f328a | |||
| 516f01ed44 | |||
| 4492a20bbc | |||
| 595bc3a2b3 | |||
| 87c10ca93d | |||
| f75b7b7879 |
@@ -1,3 +1,11 @@
|
||||
## v0.8.0-beta.3
|
||||
|
||||
- Luau vector constructor
|
||||
- Luau sandboxing support
|
||||
- Luau interrupts (yieldable)
|
||||
- More Luau compiler options (mutable globals)
|
||||
- Other performance improvements
|
||||
|
||||
## v0.8.0-beta.2
|
||||
|
||||
- Luau vector datatype support
|
||||
|
||||
+4
-3
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mlua"
|
||||
version = "0.8.0-beta.2" # remember to update html_root_url and mlua_derive
|
||||
version = "0.8.0-beta.3" # remember to update html_root_url and mlua_derive
|
||||
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@chucklefish.org>"]
|
||||
edition = "2018"
|
||||
repository = "https://github.com/khvzak/mlua"
|
||||
@@ -57,9 +57,9 @@ erased-serde = { version = "0.3", optional = true }
|
||||
[build-dependencies]
|
||||
cc = { version = "1.0" }
|
||||
pkg-config = { version = "0.3.17" }
|
||||
lua-src = { version = ">= 540.0.0, < 550.0.0", optional = true }
|
||||
lua-src = { version = ">= 544.0.0, < 550.0.0", optional = true }
|
||||
luajit-src = { version = ">= 210.3.1, < 220.0.0", optional = true }
|
||||
luau0-src = { version = "0.2.1", optional = true }
|
||||
luau0-src = { version = "0.2.2", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
rustyline = "9.0"
|
||||
@@ -70,6 +70,7 @@ hyper = { version = "0.14", features = ["client", "server"] }
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
futures-timer = "3.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
maplit = "1.0"
|
||||
tempfile = "3"
|
||||
|
||||
@@ -104,7 +104,7 @@ Add to `Cargo.toml` :
|
||||
|
||||
``` toml
|
||||
[dependencies]
|
||||
mlua = { version = "0.8.0-beta.2", features = ["lua54", "vendored"] }
|
||||
mlua = { version = "0.8.0-beta.3", features = ["lua54", "vendored"] }
|
||||
```
|
||||
|
||||
`main.rs`
|
||||
@@ -139,7 +139,7 @@ Add to `Cargo.toml` :
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
mlua = { version = "0.8.0-beta.2", features = ["lua54", "vendored", "module"] }
|
||||
mlua = { version = "0.8.0-beta.3", features = ["lua54", "vendored", "module"] }
|
||||
```
|
||||
|
||||
`lib.rs` :
|
||||
|
||||
+110
-23
@@ -66,11 +66,14 @@ pub enum ChunkMode {
|
||||
/// Luau compiler
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Compiler {
|
||||
optimization_level: u8,
|
||||
debug_level: u8,
|
||||
coverage_level: u8,
|
||||
vector_lib: Option<String>,
|
||||
vector_ctor: Option<String>,
|
||||
mutable_globals: Vec<String>,
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
@@ -81,6 +84,9 @@ impl Default for Compiler {
|
||||
optimization_level: 1,
|
||||
debug_level: 1,
|
||||
coverage_level: 0,
|
||||
vector_lib: None,
|
||||
vector_ctor: None,
|
||||
mutable_globals: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,10 +101,10 @@ impl Compiler {
|
||||
/// Sets Luau compiler optimization level.
|
||||
///
|
||||
/// Possible values:
|
||||
/// 0 - no optimization
|
||||
/// 1 - baseline optimization level that doesn't prevent debuggability (default)
|
||||
/// 2 - includes optimizations that harm debuggability such as inlining
|
||||
pub fn set_optimization_level(mut self, level: u8) -> Self {
|
||||
/// * 0 - no optimization
|
||||
/// * 1 - baseline optimization level that doesn't prevent debuggability (default)
|
||||
/// * 2 - includes optimizations that harm debuggability such as inlining
|
||||
pub fn set_optimization_level(&mut self, level: u8) -> &mut Self {
|
||||
self.optimization_level = level;
|
||||
self
|
||||
}
|
||||
@@ -106,10 +112,10 @@ impl Compiler {
|
||||
/// Sets Luau compiler debug level.
|
||||
///
|
||||
/// Possible values:
|
||||
/// 0 - no debugging support
|
||||
/// 1 - line info & function names only; sufficient for backtraces (default)
|
||||
/// 2 - full debug info with local & upvalue names; necessary for debugger
|
||||
pub fn set_debug_level(mut self, level: u8) -> Self {
|
||||
/// * 0 - no debugging support
|
||||
/// * 1 - line info & function names only; sufficient for backtraces (default)
|
||||
/// * 2 - full debug info with local & upvalue names; necessary for debugger
|
||||
pub fn set_debug_level(&mut self, level: u8) -> &mut Self {
|
||||
self.debug_level = level;
|
||||
self
|
||||
}
|
||||
@@ -117,27 +123,70 @@ impl Compiler {
|
||||
/// Sets Luau compiler code coverage level.
|
||||
///
|
||||
/// Possible values:
|
||||
/// 0 - no code coverage support (default)
|
||||
/// 1 - statement coverage
|
||||
/// 2 - statement and expression coverage (verbose)
|
||||
pub fn set_coverage_level(mut self, level: u8) -> Self {
|
||||
/// * 0 - no code coverage support (default)
|
||||
/// * 1 - statement coverage
|
||||
/// * 2 - statement and expression coverage (verbose)
|
||||
pub fn set_coverage_level(&mut self, level: u8) -> &mut Self {
|
||||
self.coverage_level = level;
|
||||
self
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn set_vector_lib(&mut self, lib: Option<String>) -> &mut Self {
|
||||
self.vector_lib = lib;
|
||||
self
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn set_vector_ctor(&mut self, ctor: Option<String>) -> &mut Self {
|
||||
self.vector_ctor = ctor;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a list of globals that are mutable.
|
||||
///
|
||||
/// It disables the import optimization for fields accessed through these.
|
||||
pub fn set_mutable_globals(&mut self, globals: Vec<String>) -> &mut Self {
|
||||
self.mutable_globals = globals;
|
||||
self
|
||||
}
|
||||
|
||||
/// Compiles the `source` into bytecode.
|
||||
pub fn compile(&self, source: impl AsRef<[u8]>) -> Vec<u8> {
|
||||
use std::os::raw::c_int;
|
||||
use std::ptr;
|
||||
|
||||
let vector_lib = self.vector_lib.clone();
|
||||
let vector_lib = vector_lib.and_then(|lib| CString::new(lib).ok());
|
||||
let vector_lib = vector_lib.as_ref();
|
||||
let vector_ctor = self.vector_ctor.clone();
|
||||
let vector_ctor = vector_ctor.and_then(|ctor| CString::new(ctor).ok());
|
||||
let vector_ctor = vector_ctor.as_ref();
|
||||
|
||||
let mutable_globals = self
|
||||
.mutable_globals
|
||||
.iter()
|
||||
.map(|name| CString::new(name.clone()).ok())
|
||||
.collect::<Option<Vec<_>>>()
|
||||
.unwrap_or_default();
|
||||
let mut mutable_globals = mutable_globals
|
||||
.iter()
|
||||
.map(|s| s.as_ptr())
|
||||
.collect::<Vec<_>>();
|
||||
let mut mutable_globals_ptr = ptr::null_mut();
|
||||
if mutable_globals.len() > 0 {
|
||||
mutable_globals.push(ptr::null());
|
||||
mutable_globals_ptr = mutable_globals.as_mut_ptr();
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let options = ffi::lua_CompileOptions {
|
||||
optimizationLevel: self.optimization_level as c_int,
|
||||
debugLevel: self.debug_level as c_int,
|
||||
coverageLevel: self.coverage_level as c_int,
|
||||
vectorLib: ptr::null(),
|
||||
vectorCtor: ptr::null(),
|
||||
mutableGlobals: ptr::null_mut(),
|
||||
vectorLib: vector_lib.map_or(ptr::null(), |s| s.as_ptr()),
|
||||
vectorCtor: vector_ctor.map_or(ptr::null(), |s| s.as_ptr()),
|
||||
mutableGlobals: mutable_globals_ptr,
|
||||
};
|
||||
ffi::luau_compile(source.as_ref(), options)
|
||||
}
|
||||
@@ -146,7 +195,7 @@ impl Compiler {
|
||||
|
||||
impl<'lua, 'a> Chunk<'lua, 'a> {
|
||||
/// Sets the name of this chunk, which results in more informative error traces.
|
||||
pub fn set_name<S: AsRef<[u8]> + ?Sized>(mut self, name: &S) -> Result<Chunk<'lua, 'a>> {
|
||||
pub fn set_name<S: AsRef<[u8]> + ?Sized>(mut self, name: &S) -> Result<Self> {
|
||||
let name =
|
||||
CString::new(name.as_ref().to_vec()).map_err(|e| Error::ToLuaConversionError {
|
||||
from: "&str",
|
||||
@@ -168,7 +217,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
|
||||
/// All global variables (including the standard library!) are looked up in `_ENV`, so it may be
|
||||
/// necessary to populate the environment in order for scripts using custom environments to be
|
||||
/// useful.
|
||||
pub fn set_environment<V: ToLua<'lua>>(mut self, env: V) -> Result<Chunk<'lua, 'a>> {
|
||||
pub fn set_environment<V: ToLua<'lua>>(mut self, env: V) -> Result<Self> {
|
||||
// Prefer to propagate errors here and wrap to `Ok`
|
||||
self.env = Ok(Some(env.to_lua(self.lua)?));
|
||||
Ok(self)
|
||||
@@ -178,7 +227,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
|
||||
///
|
||||
/// Be aware, Lua does not check the consistency of the code inside binary chunks.
|
||||
/// Running maliciously crafted bytecode can crash the interpreter.
|
||||
pub fn set_mode(mut self, mode: ChunkMode) -> Chunk<'lua, 'a> {
|
||||
pub fn set_mode(mut self, mode: ChunkMode) -> Self {
|
||||
self.mode = Some(mode);
|
||||
self
|
||||
}
|
||||
@@ -187,7 +236,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
|
||||
///
|
||||
/// See [`Compiler::set_optimization_level`] for details.
|
||||
///
|
||||
/// Requires `feature = "luau`
|
||||
/// Requires `feature = "luau"`
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
pub fn set_optimization_level(mut self, level: u8) -> Self {
|
||||
@@ -215,7 +264,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
|
||||
///
|
||||
/// See [`Compiler::set_coverage_level`] for details.
|
||||
///
|
||||
/// Requires `feature = "luau`
|
||||
/// Requires `feature = "luau"`
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
pub fn set_coverage_level(mut self, level: u8) -> Self {
|
||||
@@ -225,6 +274,40 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
#[doc(hidden)]
|
||||
pub fn set_vector_lib(mut self, lib: Option<String>) -> Self {
|
||||
self.compiler
|
||||
.get_or_insert_with(Default::default)
|
||||
.set_vector_lib(lib);
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
#[doc(hidden)]
|
||||
pub fn set_vector_ctor(mut self, ctor: Option<String>) -> Self {
|
||||
self.compiler
|
||||
.get_or_insert_with(Default::default)
|
||||
.set_vector_ctor(ctor);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a list of globals that are mutable for Luau compiler.
|
||||
///
|
||||
/// See [`Compiler::set_mutable_globals`] for details.
|
||||
///
|
||||
/// Requires `feature = "luau"`
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
pub fn set_mutable_globals(mut self, globals: Vec<String>) -> Self {
|
||||
self.compiler
|
||||
.get_or_insert_with(Default::default)
|
||||
.set_mutable_globals(globals);
|
||||
self
|
||||
}
|
||||
|
||||
/// Compiles the chunk and changes mode to binary.
|
||||
///
|
||||
/// It does nothing if the chunk is already binary.
|
||||
@@ -234,7 +317,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
|
||||
if self.detect_mode() == ChunkMode::Text {
|
||||
let data = self
|
||||
.compiler
|
||||
.unwrap_or_default()
|
||||
.get_or_insert_with(Default::default)
|
||||
.compile(self.source.as_ref());
|
||||
self.mode = Some(ChunkMode::Binary);
|
||||
self.source = Cow::Owned(data);
|
||||
@@ -373,7 +456,11 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
|
||||
let source = self.expression_source();
|
||||
// We don't need to compile source if no compiler options set
|
||||
#[cfg(feature = "luau")]
|
||||
let source = self.compiler.map(|c| c.compile(&source)).unwrap_or(source);
|
||||
let source = self
|
||||
.compiler
|
||||
.as_ref()
|
||||
.map(|c| c.compile(&source))
|
||||
.unwrap_or(source);
|
||||
|
||||
self.lua
|
||||
.load_chunk(&source, self.name.as_ref(), self.env()?, None)
|
||||
|
||||
@@ -72,6 +72,11 @@ extern "C" {
|
||||
// TODO: luaL_findtable
|
||||
|
||||
pub fn luaL_typename(L: *mut lua_State, idx: c_int) -> *const c_char;
|
||||
|
||||
// sandbox libraries and globals
|
||||
#[link_name = "luaL_sandbox"]
|
||||
pub fn luaL_sandbox_(L: *mut lua_State);
|
||||
pub fn luaL_sandboxthread(L: *mut lua_State);
|
||||
}
|
||||
|
||||
//
|
||||
@@ -123,6 +128,29 @@ pub unsafe fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int) {
|
||||
lua::lua_unref(L, r#ref)
|
||||
}
|
||||
|
||||
pub unsafe fn luaL_sandbox(L: *mut lua_State, enabled: c_int) {
|
||||
use super::lua::*;
|
||||
|
||||
// set all libraries to read-only
|
||||
lua_pushnil(L);
|
||||
while lua_next(L, LUA_GLOBALSINDEX) != 0 {
|
||||
if lua_istable(L, -1) != 0 {
|
||||
lua_setreadonly(L, -1, enabled);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
// set all builtin metatables to read-only
|
||||
lua_pushliteral(L, "");
|
||||
lua_getmetatable(L, -1);
|
||||
lua_setreadonly(L, -1, enabled);
|
||||
lua_pop(L, 2);
|
||||
|
||||
// set globals to readonly and activate safeenv since the env is immutable
|
||||
lua_setreadonly(L, LUA_GLOBALSINDEX, enabled);
|
||||
lua_setsafeenv(L, LUA_GLOBALSINDEX, enabled);
|
||||
}
|
||||
|
||||
//
|
||||
// TODO: Generic Buffer Manipulation
|
||||
//
|
||||
|
||||
@@ -26,8 +26,4 @@ extern "C" {
|
||||
|
||||
// open all builtin libraries
|
||||
pub fn luaL_openlibs(L: *mut lua_State);
|
||||
|
||||
// sandbox libraries and globals
|
||||
pub fn luaL_sandbox(L: *mut lua_State);
|
||||
pub fn luaL_sandboxthread(L: *mut lua_State);
|
||||
}
|
||||
|
||||
+4
-2
@@ -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.0-beta.2")]
|
||||
#![doc(html_root_url = "https://docs.rs/mlua/0.8.0-beta.3")]
|
||||
// Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any*
|
||||
// warnings at all.
|
||||
#![doc(test(attr(deny(warnings))))]
|
||||
@@ -88,6 +88,8 @@ mod ffi;
|
||||
mod function;
|
||||
mod hook;
|
||||
mod lua;
|
||||
#[cfg(feature = "luau")]
|
||||
mod luau;
|
||||
mod multi;
|
||||
mod scope;
|
||||
mod stdlib;
|
||||
@@ -126,7 +128,7 @@ pub use crate::hook::HookTriggers;
|
||||
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
pub use crate::chunk::Compiler;
|
||||
pub use crate::{chunk::Compiler, types::VmState};
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
pub use crate::thread::AsyncThread;
|
||||
|
||||
+228
-139
@@ -47,6 +47,9 @@ use {
|
||||
#[cfg(not(feature = "luau"))]
|
||||
use crate::{hook::HookTriggers, types::HookCallback};
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
use crate::types::{InterruptCallback, VmState};
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
use {
|
||||
crate::types::{AsyncCallback, AsyncCallbackUpvalue, AsyncPollUpvalue},
|
||||
@@ -108,6 +111,11 @@ struct ExtraData {
|
||||
hook_callback: Option<HookCallback>,
|
||||
#[cfg(feature = "lua54")]
|
||||
warn_callback: Option<WarnCallback>,
|
||||
#[cfg(feature = "luau")]
|
||||
interrupt_callback: Option<InterruptCallback>,
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
sandboxed: bool,
|
||||
}
|
||||
|
||||
#[cfg_attr(any(feature = "lua51", feature = "luajit"), allow(dead_code))]
|
||||
@@ -204,8 +212,8 @@ impl LuaOptions {
|
||||
pub(crate) static ASYNC_POLL_PENDING: u8 = 0;
|
||||
pub(crate) static EXTRA_REGISTRY_KEY: u8 = 0;
|
||||
|
||||
const WRAPPED_FAILURES_CACHE_SIZE: usize = 16;
|
||||
const MULTIVALUE_CACHE_SIZE: usize = 16;
|
||||
const WRAPPED_FAILURES_CACHE_SIZE: usize = 32;
|
||||
const MULTIVALUE_CACHE_SIZE: usize = 32;
|
||||
|
||||
/// Requires `feature = "send"`
|
||||
#[cfg(feature = "send")]
|
||||
@@ -232,6 +240,13 @@ impl Drop for Lua {
|
||||
ffi::lua_replace(extra.ref_thread, extra.ref_waker_idx);
|
||||
extra.ref_free.push(extra.ref_waker_idx);
|
||||
}
|
||||
#[cfg(feature = "luau")]
|
||||
{
|
||||
let callbacks = ffi::lua_callbacks(self.state);
|
||||
let extra_ptr = (*callbacks).userdata as *mut Arc<UnsafeCell<ExtraData>>;
|
||||
drop(Box::from_raw(extra_ptr));
|
||||
(*callbacks).userdata = ptr::null_mut();
|
||||
}
|
||||
mlua_debug_assert!(
|
||||
ffi::lua_gettop(extra.ref_thread) == extra.ref_stack_top
|
||||
&& extra.ref_stack_top as usize == extra.ref_free.len(),
|
||||
@@ -548,6 +563,10 @@ impl Lua {
|
||||
hook_callback: None,
|
||||
#[cfg(feature = "lua54")]
|
||||
warn_callback: None,
|
||||
#[cfg(feature = "luau")]
|
||||
interrupt_callback: None,
|
||||
#[cfg(feature = "luau")]
|
||||
sandboxed: false,
|
||||
}));
|
||||
|
||||
mlua_expect!(
|
||||
@@ -576,6 +595,14 @@ impl Lua {
|
||||
);
|
||||
assert_stack(main_state, ffi::LUA_MINSTACK);
|
||||
|
||||
// Set Luau callbacks userdata to extra data
|
||||
// We can use global callbacks userdata since we don't allow C modules in Luau
|
||||
#[cfg(feature = "luau")]
|
||||
{
|
||||
let extra_raw = Box::into_raw(Box::new(Arc::clone(&extra)));
|
||||
(*ffi::lua_callbacks(main_state)).userdata = extra_raw as *mut c_void;
|
||||
}
|
||||
|
||||
Lua {
|
||||
state,
|
||||
main_state: maybe_main_state,
|
||||
@@ -696,32 +723,6 @@ impl Lua {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Consumes and leaks `Lua` object, returning a static reference `&'static Lua`.
|
||||
///
|
||||
/// This function is useful when the `Lua` object is supposed to live for the remainder
|
||||
/// of the program's life.
|
||||
/// In particular in asynchronous context this will allow to spawn Lua tasks to execute
|
||||
/// in background.
|
||||
///
|
||||
/// Dropping the returned reference will cause a memory leak. If this is not acceptable,
|
||||
/// the reference should first be wrapped with the [`Lua::from_static`] function producing a `Lua`.
|
||||
/// This `Lua` object can then be dropped which will properly release the allocated memory.
|
||||
///
|
||||
/// [`Lua::from_static`]: #method.from_static
|
||||
#[doc(hidden)]
|
||||
pub fn into_static(self) -> &'static Self {
|
||||
Box::leak(Box::new(self))
|
||||
}
|
||||
|
||||
/// Constructs a `Lua` from a static reference to it.
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe because improper use may lead to memory problems or undefined behavior.
|
||||
#[doc(hidden)]
|
||||
pub unsafe fn from_static(lua: &'static Lua) -> Self {
|
||||
*Box::from_raw(lua as *const Lua as *mut Lua)
|
||||
}
|
||||
|
||||
// Executes module entrypoint function, which returns only one Value.
|
||||
// The returned value then pushed onto the stack.
|
||||
#[doc(hidden)]
|
||||
@@ -776,6 +777,60 @@ impl Lua {
|
||||
self.entrypoint(move |lua, _: ()| func(lua))
|
||||
}
|
||||
|
||||
/// Enables (or disables) sandbox mode on this Lua instance.
|
||||
///
|
||||
/// This method, in particular:
|
||||
/// - Set all libraries to read-only
|
||||
/// - Set all builtin metatables to read-only
|
||||
/// - Set globals to read-only (and activates safeenv)
|
||||
/// - Setup local environment table that performs writes locally and proxies reads
|
||||
/// to the global environment.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use mlua::{Lua, Result};
|
||||
/// # fn main() -> Result<()> {
|
||||
/// let lua = Lua::new();
|
||||
///
|
||||
/// lua.sandbox(true)?;
|
||||
/// lua.load("var = 123").exec()?;
|
||||
/// assert_eq!(lua.globals().get::<_, u32>("var")?, 123);
|
||||
///
|
||||
/// // Restore the global environment (clear changes made in sandbox)
|
||||
/// lua.sandbox(false)?;
|
||||
/// assert_eq!(lua.globals().get::<_, Option<u32>>("var")?, None);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Requires `feature = "luau"`
|
||||
#[cfg(feature = "luau")]
|
||||
pub fn sandbox(&self, enabled: bool) -> Result<()> {
|
||||
unsafe {
|
||||
let extra = &mut *self.extra.get();
|
||||
if extra.sandboxed != enabled {
|
||||
let state = self.main_state.ok_or(Error::MainThreadNotAvailable)?;
|
||||
check_stack(state, 3)?;
|
||||
protect_lua!(state, 0, 0, |state| {
|
||||
if enabled {
|
||||
ffi::luaL_sandbox(state, 1);
|
||||
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::luaL_sandbox(state, 0);
|
||||
}
|
||||
})?;
|
||||
extra.sandboxed = enabled;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets a 'hook' function that will periodically be called as Lua code executes.
|
||||
///
|
||||
/// When exactly the hook function is called depends on the contents of the `triggers`
|
||||
@@ -813,7 +868,7 @@ impl Lua {
|
||||
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
|
||||
pub fn set_hook<F>(&self, triggers: HookTriggers, callback: F) -> Result<()>
|
||||
where
|
||||
F: 'static + MaybeSend + FnMut(&Lua, Debug) -> Result<()>,
|
||||
F: 'static + MaybeSend + Fn(&Lua, Debug) -> Result<()>,
|
||||
{
|
||||
unsafe extern "C" fn hook_proc(state: *mut ffi::lua_State, ar: *mut ffi::lua_Debug) {
|
||||
let lua = match Lua::make_from_ptr(state) {
|
||||
@@ -825,29 +880,24 @@ impl Lua {
|
||||
let debug = Debug::new(&lua, ar);
|
||||
let hook_cb = (*lua.extra.get()).hook_callback.clone();
|
||||
let hook_cb = mlua_expect!(hook_cb, "no hook callback set in hook_proc");
|
||||
|
||||
#[allow(clippy::match_wild_err_arm)]
|
||||
match hook_cb.try_lock() {
|
||||
Ok(mut cb) => cb(&lua, debug),
|
||||
Err(_) => {
|
||||
mlua_panic!("Lua should not allow hooks to be called within another hook")
|
||||
}
|
||||
}?;
|
||||
|
||||
Ok(())
|
||||
if Arc::strong_count(&hook_cb) > 2 {
|
||||
return Ok(()); // Don't allow recursion
|
||||
}
|
||||
hook_cb(&lua, debug)
|
||||
})
|
||||
}
|
||||
|
||||
let state = self.main_state.ok_or(Error::MainThreadNotAvailable)?;
|
||||
unsafe {
|
||||
(*self.extra.get()).hook_callback = Some(Arc::new(Mutex::new(callback)));
|
||||
(*self.extra.get()).hook_callback = Some(Arc::new(callback));
|
||||
ffi::lua_sethook(state, Some(hook_proc), triggers.mask(), triggers.count());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove any hook previously set by `set_hook`. This function has no effect if a hook was not
|
||||
/// previously set.
|
||||
/// Removes any hook previously set by `set_hook`.
|
||||
///
|
||||
/// This function has no effect if a hook was not previously set.
|
||||
#[cfg(not(feature = "luau"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
|
||||
pub fn remove_hook(&self) {
|
||||
@@ -862,6 +912,102 @@ impl Lua {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets an 'interrupt' function that will periodically be called by Luau VM.
|
||||
///
|
||||
/// Any Luau code is guaranteed to call this handler "eventually"
|
||||
/// (in practice this can happen at any function call or at any loop iteration).
|
||||
///
|
||||
/// The provided interrupt function can error, and this error will be propagated through
|
||||
/// the Luau code that was executing at the time the interrupt was triggered.
|
||||
/// Also this can be used to implement continuous execution limits by instructing Luau VM to yield
|
||||
/// by returning [`VmState::Yield`].
|
||||
///
|
||||
/// This is similar to [`Lua::set_hook`] but in more simplified form.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Periodically yield Luau VM to suspend execution.
|
||||
///
|
||||
/// ```
|
||||
/// # use std::sync::{Arc, atomic::{AtomicU64, Ordering}};
|
||||
/// # use mlua::{Lua, Result, ThreadStatus, VmState};
|
||||
/// # fn main() -> Result<()> {
|
||||
/// let lua = Lua::new();
|
||||
/// let count = Arc::new(AtomicU64::new(0));
|
||||
/// lua.set_interrupt(move |_lua| {
|
||||
/// if count.fetch_add(1, Ordering::Relaxed) % 2 == 0 {
|
||||
/// return Ok(VmState::Yield);
|
||||
/// }
|
||||
/// Ok(VmState::Continue)
|
||||
/// });
|
||||
///
|
||||
/// let co = lua.create_thread(
|
||||
/// lua.load(r#"
|
||||
/// local b = 0
|
||||
/// for _, x in ipairs({1, 2, 3}) do b += x end
|
||||
/// "#)
|
||||
/// .into_function()?,
|
||||
/// )?;
|
||||
/// while co.status() == ThreadStatus::Resumable {
|
||||
/// co.resume(())?;
|
||||
/// }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(feature = "luau")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
pub fn set_interrupt<F>(&self, callback: F)
|
||||
where
|
||||
F: 'static + MaybeSend + Fn(&Lua) -> Result<VmState>,
|
||||
{
|
||||
unsafe extern "C" fn interrupt_proc(state: *mut ffi::lua_State, gc: c_int) {
|
||||
if gc != -1 {
|
||||
// We don't support GC interrupts since they cannot survive Lua exceptions
|
||||
return;
|
||||
}
|
||||
// TODO: think about not using drop types here
|
||||
let lua = match Lua::make_from_ptr(state) {
|
||||
Some(lua) => lua,
|
||||
None => return,
|
||||
};
|
||||
let extra = lua.extra.get();
|
||||
let result = callback_error_ext(state, extra, move |_| {
|
||||
let interrupt_cb = (*extra).interrupt_callback.clone();
|
||||
let interrupt_cb =
|
||||
mlua_expect!(interrupt_cb, "no interrupt callback set in interrupt_proc");
|
||||
if Arc::strong_count(&interrupt_cb) > 2 {
|
||||
return Ok(VmState::Continue); // Don't allow recursion
|
||||
}
|
||||
interrupt_cb(&lua)
|
||||
});
|
||||
match result {
|
||||
VmState::Continue => {}
|
||||
VmState::Yield => {
|
||||
ffi::lua_yield(state, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let state = mlua_expect!(self.main_state, "Luau should always has main state");
|
||||
unsafe {
|
||||
(*self.extra.get()).interrupt_callback = Some(Arc::new(callback));
|
||||
(*ffi::lua_callbacks(state)).interrupt = Some(interrupt_proc);
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes any 'interrupt' previously set by `set_interrupt`.
|
||||
///
|
||||
/// This function has no effect if an 'interrupt' was not previously set.
|
||||
#[cfg(feature = "luau")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
pub fn remove_interrupt(&self) {
|
||||
let state = mlua_expect!(self.main_state, "Luau should always has main state");
|
||||
unsafe {
|
||||
(*self.extra.get()).interrupt_callback = None;
|
||||
(*ffi::lua_callbacks(state)).interrupt = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the warning function to be used by Lua to emit warnings.
|
||||
///
|
||||
/// Requires `feature = "lua54"`
|
||||
@@ -1450,7 +1596,11 @@ impl Lua {
|
||||
&'lua self,
|
||||
func: Function<'lua>,
|
||||
) -> Result<Thread<'lua>> {
|
||||
#[cfg(any(feature = "lua54", all(feature = "luajit", feature = "vendored")))]
|
||||
#[cfg(any(
|
||||
feature = "lua54",
|
||||
all(feature = "luajit", feature = "vendored"),
|
||||
feature = "luau",
|
||||
))]
|
||||
unsafe {
|
||||
let _sg = StackGuard::new(self.state);
|
||||
check_stack(self.state, 1)?;
|
||||
@@ -1460,6 +1610,14 @@ impl Lua {
|
||||
let thread_state = ffi::lua_tothread(extra.ref_thread, index);
|
||||
self.push_ref(&func.0);
|
||||
ffi::lua_xmove(self.state, thread_state, 1);
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
{
|
||||
// Inherit `LUA_GLOBALSINDEX` from the caller
|
||||
ffi::lua_xpush(self.state, thread_state, ffi::LUA_GLOBALSINDEX);
|
||||
ffi::lua_replace(thread_state, ffi::LUA_GLOBALSINDEX);
|
||||
}
|
||||
|
||||
return Ok(Thread(LuaRef { lua: self, index }));
|
||||
}
|
||||
};
|
||||
@@ -1468,7 +1626,11 @@ impl Lua {
|
||||
|
||||
/// Resets thread (coroutine) and returns to the cache for later use.
|
||||
#[cfg(feature = "async")]
|
||||
#[cfg(any(feature = "lua54", all(feature = "luajit", feature = "vendored")))]
|
||||
#[cfg(any(
|
||||
feature = "lua54",
|
||||
all(feature = "luajit", feature = "vendored"),
|
||||
feature = "luau",
|
||||
))]
|
||||
pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) {
|
||||
let extra = &mut *self.extra.get();
|
||||
let thread_state = ffi::lua_tothread(extra.ref_thread, thread.0.index);
|
||||
@@ -2619,105 +2781,11 @@ impl Lua {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
unsafe fn prepare_luau_state(&self) -> Result<()> {
|
||||
use std::ffi::CStr;
|
||||
|
||||
// Since Luau has some missing standard function, we re-implement them here
|
||||
|
||||
unsafe extern "C" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_int {
|
||||
let option = ffi::luaL_optstring(state, 1, cstr!("collect"));
|
||||
let option = CStr::from_ptr(option);
|
||||
match option.to_str() {
|
||||
Ok("collect") => {
|
||||
ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0);
|
||||
0
|
||||
}
|
||||
Ok("count") => {
|
||||
let n = ffi::lua_gc(state, ffi::LUA_GCCOUNT, 0);
|
||||
ffi::lua_pushnumber(state, n as ffi::lua_Number);
|
||||
1
|
||||
}
|
||||
// TODO: More variants
|
||||
_ => ffi::luaL_error(
|
||||
state,
|
||||
cstr!("collectgarbage must be called with 'count' or 'collect'"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn lua_require(lua: &Lua, name: Option<std::string::String>) -> Result<Value> {
|
||||
let name = name.ok_or_else(|| Error::RuntimeError("name is nil".into()))?;
|
||||
|
||||
// Find module in the cache
|
||||
let loaded = unsafe {
|
||||
let _sg = StackGuard::new(lua.state);
|
||||
check_stack(lua.state, 2)?;
|
||||
protect_lua!(lua.state, 0, 1, fn(state) {
|
||||
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED"));
|
||||
})?;
|
||||
Table(lua.pop_ref())
|
||||
};
|
||||
if let Some(v) = loaded.raw_get(name.clone())? {
|
||||
return Ok(v);
|
||||
}
|
||||
|
||||
// Load file from filesystem
|
||||
let mut search_path = std::env::var("LUAU_PATH").unwrap_or_default();
|
||||
if search_path.is_empty() {
|
||||
search_path = "?.luau;?.lua".into();
|
||||
}
|
||||
|
||||
let mut source = None;
|
||||
for path in search_path.split(';') {
|
||||
if let Ok(buf) = std::fs::read(path.replacen('?', &name, 1)) {
|
||||
source = Some(buf);
|
||||
break;
|
||||
}
|
||||
}
|
||||
let source =
|
||||
source.ok_or_else(|| Error::RuntimeError(format!("cannot find '{}'", name)))?;
|
||||
|
||||
let value = lua
|
||||
.load(&source)
|
||||
.set_name(&format!("={}", name))?
|
||||
.set_mode(ChunkMode::Text)
|
||||
.call::<_, Value>(())?;
|
||||
|
||||
// Save in the cache
|
||||
loaded.raw_set(
|
||||
name,
|
||||
match value.clone() {
|
||||
Value::Nil => Value::Boolean(true),
|
||||
v => v,
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
let globals = self.globals();
|
||||
globals.raw_set(
|
||||
"collectgarbage",
|
||||
self.create_c_function(lua_collectgarbage)?,
|
||||
)?;
|
||||
globals.raw_set("require", self.create_function(lua_require)?)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn make_from_ptr(state: *mut ffi::lua_State) -> Option<Self> {
|
||||
let _sg = StackGuard::new(state);
|
||||
assert_stack(state, 1);
|
||||
|
||||
let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void;
|
||||
if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, extra_key) != ffi::LUA_TUSERDATA {
|
||||
return None;
|
||||
}
|
||||
let extra_ptr = ffi::lua_touserdata(state, -1) as *mut Arc<UnsafeCell<ExtraData>>;
|
||||
let extra = Arc::clone(&*extra_ptr);
|
||||
ffi::lua_pop(state, 1);
|
||||
|
||||
let extra = extra_data(state)?;
|
||||
let safe = (*extra.get()).safe;
|
||||
Some(Lua {
|
||||
state,
|
||||
@@ -2749,6 +2817,27 @@ impl Lua {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
unsafe fn extra_data(state: *mut ffi::lua_State) -> Option<Arc<UnsafeCell<ExtraData>>> {
|
||||
let extra_ptr = (*ffi::lua_callbacks(state)).userdata as *mut Arc<UnsafeCell<ExtraData>>;
|
||||
if extra_ptr.is_null() {
|
||||
return None;
|
||||
}
|
||||
Some(Arc::clone(&*extra_ptr))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "luau"))]
|
||||
unsafe fn extra_data(state: *mut ffi::lua_State) -> Option<Arc<UnsafeCell<ExtraData>>> {
|
||||
let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void;
|
||||
if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, extra_key) != ffi::LUA_TUSERDATA {
|
||||
return None;
|
||||
}
|
||||
let extra_ptr = ffi::lua_touserdata(state, -1) as *mut Arc<UnsafeCell<ExtraData>>;
|
||||
let extra = Arc::clone(&*extra_ptr);
|
||||
ffi::lua_pop(state, 1);
|
||||
Some(extra)
|
||||
}
|
||||
|
||||
// Creates required entries in the metatable cache (see `util::METATABLE_CACHE`)
|
||||
pub(crate) fn init_metatable_cache(cache: &mut FxHashMap<TypeId, u8>) {
|
||||
cache.insert(TypeId::of::<Arc<UnsafeCell<ExtraData>>>(), 0);
|
||||
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::{c_float, c_int};
|
||||
|
||||
use crate::chunk::ChunkMode;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::ffi;
|
||||
use crate::lua::Lua;
|
||||
use crate::table::Table;
|
||||
use crate::util::{check_stack, StackGuard};
|
||||
use crate::value::Value;
|
||||
|
||||
// Since Luau has some missing standard function, we re-implement them here
|
||||
|
||||
impl Lua {
|
||||
pub(crate) unsafe fn prepare_luau_state(&self) -> Result<()> {
|
||||
let globals = self.globals();
|
||||
|
||||
globals.raw_set(
|
||||
"collectgarbage",
|
||||
self.create_c_function(lua_collectgarbage)?,
|
||||
)?;
|
||||
globals.raw_set("require", self.create_function(lua_require)?)?;
|
||||
globals.raw_set("vector", self.create_c_function(lua_vector)?)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_int {
|
||||
let option = ffi::luaL_optstring(state, 1, cstr!("collect"));
|
||||
let option = CStr::from_ptr(option);
|
||||
match option.to_str() {
|
||||
Ok("collect") => {
|
||||
ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0);
|
||||
0
|
||||
}
|
||||
Ok("count") => {
|
||||
let n = ffi::lua_gc(state, ffi::LUA_GCCOUNT, 0);
|
||||
ffi::lua_pushnumber(state, n as ffi::lua_Number);
|
||||
1
|
||||
}
|
||||
// TODO: More variants
|
||||
_ => ffi::luaL_error(
|
||||
state,
|
||||
cstr!("collectgarbage must be called with 'count' or 'collect'"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn lua_require(lua: &Lua, name: Option<std::string::String>) -> Result<Value> {
|
||||
let name = name.ok_or_else(|| Error::RuntimeError("name is nil".into()))?;
|
||||
|
||||
// Find module in the cache
|
||||
let loaded = unsafe {
|
||||
let _sg = StackGuard::new(lua.state);
|
||||
check_stack(lua.state, 2)?;
|
||||
protect_lua!(lua.state, 0, 1, fn(state) {
|
||||
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED"));
|
||||
})?;
|
||||
Table(lua.pop_ref())
|
||||
};
|
||||
if let Some(v) = loaded.raw_get(name.clone())? {
|
||||
return Ok(v);
|
||||
}
|
||||
|
||||
// Load file from filesystem
|
||||
let mut search_path = std::env::var("LUAU_PATH").unwrap_or_default();
|
||||
if search_path.is_empty() {
|
||||
search_path = "?.luau;?.lua".into();
|
||||
}
|
||||
|
||||
let mut source = None;
|
||||
for path in search_path.split(';') {
|
||||
if let Ok(buf) = std::fs::read(path.replacen('?', &name, 1)) {
|
||||
source = Some(buf);
|
||||
break;
|
||||
}
|
||||
}
|
||||
let source = source.ok_or_else(|| Error::RuntimeError(format!("cannot find '{}'", name)))?;
|
||||
|
||||
let value = lua
|
||||
.load(&source)
|
||||
.set_name(&format!("={}", name))?
|
||||
.set_mode(ChunkMode::Text)
|
||||
.call::<_, Value>(())?;
|
||||
|
||||
// Save in the cache
|
||||
loaded.raw_set(
|
||||
name,
|
||||
match value.clone() {
|
||||
Value::Nil => Value::Boolean(true),
|
||||
v => v,
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
// Luau vector datatype constructor
|
||||
unsafe extern "C" fn lua_vector(state: *mut ffi::lua_State) -> c_int {
|
||||
let x = ffi::luaL_checknumber(state, 1) as c_float;
|
||||
let y = ffi::luaL_checknumber(state, 2) as c_float;
|
||||
let z = ffi::luaL_checknumber(state, 3) as c_float;
|
||||
ffi::lua_pushvector(state, x, y, z);
|
||||
1
|
||||
}
|
||||
@@ -15,6 +15,14 @@ pub use crate::{
|
||||
Value as LuaValue,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "luau"))]
|
||||
#[doc(no_inline)]
|
||||
pub use crate::HookTriggers as LuaHookTriggers;
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
#[doc(no_inline)]
|
||||
pub use crate::VmState as LuaVmState;
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
#[doc(no_inline)]
|
||||
pub use crate::AsyncThread as LuaAsyncThread;
|
||||
|
||||
+7
-1
@@ -356,7 +356,13 @@ impl<'lua> Table<'lua> {
|
||||
pub fn set_readonly(&self, enabled: bool) {
|
||||
let lua = self.0.lua;
|
||||
unsafe {
|
||||
lua.ref_thread_exec(|refthr| ffi::lua_setreadonly(refthr, self.0.index, enabled as _));
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+63
-1
@@ -214,6 +214,13 @@ impl<'lua> Thread<'lua> {
|
||||
lua.push_ref(&func.0);
|
||||
ffi::lua_xmove(lua.state, thread_state, 1);
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
{
|
||||
// Inherit `LUA_GLOBALSINDEX` from the caller
|
||||
ffi::lua_xpush(lua.state, thread_state, ffi::LUA_GLOBALSINDEX);
|
||||
ffi::lua_replace(thread_state, ffi::LUA_GLOBALSINDEX);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -278,6 +285,57 @@ impl<'lua> Thread<'lua> {
|
||||
recycle: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Enables sandbox mode on this thread.
|
||||
///
|
||||
/// Under the hood replaces the global environment table with a new table,
|
||||
/// that performs writes locally and proxies reads to caller's global environment.
|
||||
///
|
||||
/// This mode ideally should be used together with the global sandbox mode [`Lua::sandbox()`].
|
||||
///
|
||||
/// Please note that Luau links environment table with chunk when loading it into Lua state.
|
||||
/// Therefore you need to load chunks into a thread to link with the thread environment.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[cfg(feature = "luau")]
|
||||
/// # fn main() -> mlua::Result<()> {
|
||||
/// use mlua::Lua;
|
||||
/// let lua = Lua::new();
|
||||
/// let thread = lua.create_thread(lua.create_function(|lua2, ()| {
|
||||
/// lua2.load("var = 123").exec()?;
|
||||
/// assert_eq!(lua2.globals().get::<_, u32>("var")?, 123);
|
||||
/// Ok(())
|
||||
/// })?)?;
|
||||
/// thread.sandbox()?;
|
||||
/// thread.resume(())?;
|
||||
///
|
||||
/// // The global environment should be unchanged
|
||||
/// assert_eq!(lua.globals().get::<_, Option<u32>>("var")?, None);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
///
|
||||
/// # #[cfg(not(feature = "luau"))]
|
||||
/// fn main() {}
|
||||
/// ```
|
||||
///
|
||||
/// Requires `feature = "luau"`
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
#[doc(hidden)]
|
||||
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));
|
||||
check_stack(thread, 1)?;
|
||||
check_stack(lua.state, 3)?;
|
||||
// Inherit `LUA_GLOBALSINDEX` from the caller
|
||||
ffi::lua_xpush(lua.state, thread, ffi::LUA_GLOBALSINDEX);
|
||||
ffi::lua_replace(thread, ffi::LUA_GLOBALSINDEX);
|
||||
protect_lua!(lua.state, 0, 0, |_| ffi::luaL_sandboxthread(thread))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> PartialEq for Thread<'lua> {
|
||||
@@ -295,7 +353,11 @@ impl<'lua, R> AsyncThread<'lua, R> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
#[cfg(any(feature = "lua54", all(feature = "luajit", feature = "vendored")))]
|
||||
#[cfg(any(
|
||||
feature = "lua54",
|
||||
all(feature = "luajit", feature = "vendored"),
|
||||
feature = "luau",
|
||||
))]
|
||||
impl<'lua, R> Drop for AsyncThread<'lua, R> {
|
||||
fn drop(&mut self) {
|
||||
if self.recycle {
|
||||
|
||||
+17
-2
@@ -49,11 +49,26 @@ pub(crate) struct AsyncPollUpvalue<'lua> {
|
||||
pub(crate) lua: Lua,
|
||||
pub(crate) fut: LocalBoxFuture<'lua, Result<MultiValue<'lua>>>,
|
||||
}
|
||||
|
||||
/// Type to set next Luau VM action after executing interrupt function.
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
pub enum VmState {
|
||||
Continue,
|
||||
Yield,
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "send", not(feature = "luau")))]
|
||||
pub(crate) type HookCallback = Arc<Mutex<dyn FnMut(&Lua, Debug) -> Result<()> + Send>>;
|
||||
pub(crate) type HookCallback = Arc<dyn Fn(&Lua, Debug) -> Result<()> + Send>;
|
||||
|
||||
#[cfg(all(not(feature = "send"), not(feature = "luau")))]
|
||||
pub(crate) type HookCallback = Arc<Mutex<dyn FnMut(&Lua, Debug) -> Result<()>>>;
|
||||
pub(crate) type HookCallback = Arc<dyn Fn(&Lua, Debug) -> Result<()>>;
|
||||
|
||||
#[cfg(all(feature = "luau", feature = "send"))]
|
||||
pub(crate) type InterruptCallback = Arc<dyn Fn(&Lua) -> Result<VmState> + Send>;
|
||||
|
||||
#[cfg(all(feature = "luau", not(feature = "send")))]
|
||||
pub(crate) type InterruptCallback = Arc<dyn Fn(&Lua) -> Result<VmState>>;
|
||||
|
||||
#[cfg(all(feature = "send", feature = "lua54"))]
|
||||
pub(crate) type WarnCallback = Box<dyn Fn(&Lua, &CStr, bool) -> Result<()> + Send>;
|
||||
|
||||
+4
-4
@@ -3,6 +3,7 @@
|
||||
use std::cell::RefCell;
|
||||
use std::ops::Deref;
|
||||
use std::str;
|
||||
use std::sync::atomic::{AtomicI64, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use mlua::{DebugEvent, Error, HookTriggers, Lua, Result, Value};
|
||||
@@ -128,18 +129,17 @@ fn test_error_within_hook() -> Result<()> {
|
||||
#[test]
|
||||
fn test_limit_execution_instructions() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
let mut max_instructions = 10000;
|
||||
|
||||
#[cfg(feature = "luajit")]
|
||||
// For LuaJIT disable JIT, as compiled code does not trigger hooks
|
||||
#[cfg(feature = "luajit")]
|
||||
lua.load("jit.off()").exec()?;
|
||||
|
||||
let max_instructions = AtomicI64::new(10000);
|
||||
lua.set_hook(
|
||||
HookTriggers::every_nth_instruction(30),
|
||||
move |_lua, debug| {
|
||||
assert_eq!(debug.event(), DebugEvent::Count);
|
||||
max_instructions -= 30;
|
||||
if max_instructions < 0 {
|
||||
if max_instructions.fetch_sub(30, Ordering::Relaxed) <= 30 {
|
||||
Err(Error::RuntimeError("time's up".to_string()))
|
||||
} else {
|
||||
Ok(())
|
||||
|
||||
+155
-10
@@ -2,8 +2,10 @@
|
||||
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use mlua::{Error, Lua, Result, Value};
|
||||
use mlua::{Error, Lua, Result, Table, ThreadStatus, Value, VmState};
|
||||
|
||||
#[test]
|
||||
fn test_require() -> Result<()> {
|
||||
@@ -34,17 +36,32 @@ fn test_require() -> Result<()> {
|
||||
fn test_vectors() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
let globals = lua.globals();
|
||||
globals.set(
|
||||
"vector",
|
||||
lua.create_function(|_, (x, y, z)| Ok(Value::Vector(x, y, z)))?,
|
||||
)?;
|
||||
|
||||
let v: [f32; 3] = lua
|
||||
.load("return vector(1, 2, 3) + vector(3, 2, 1)")
|
||||
.eval()?;
|
||||
let v: [f32; 3] = lua.load("vector(1, 2, 3) + vector(3, 2, 1)").eval()?;
|
||||
assert_eq!(v, [4.0, 4.0, 4.0]);
|
||||
|
||||
// Test vector methods
|
||||
lua.load(
|
||||
r#"
|
||||
local v = vector(1, 2, 3)
|
||||
assert(v.x == 1)
|
||||
assert(v.y == 2)
|
||||
assert(v.z == 3)
|
||||
"#,
|
||||
)
|
||||
.exec()?;
|
||||
|
||||
// Test vector methods (fastcall)
|
||||
lua.load(
|
||||
r#"
|
||||
local v = vector(1, 2, 3)
|
||||
assert(v.x == 1)
|
||||
assert(v.y == 2)
|
||||
assert(v.z == 3)
|
||||
"#,
|
||||
)
|
||||
.set_vector_ctor(Some("vector".to_string()))
|
||||
.exec()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -67,3 +84,131 @@ fn test_readonly_table() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sandbox() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
lua.sandbox(true)?;
|
||||
|
||||
lua.load("global = 123").exec()?;
|
||||
let n: i32 = lua.load("return global").eval()?;
|
||||
assert_eq!(n, 123);
|
||||
assert_eq!(lua.globals().get::<_, Option<i32>>("global")?, Some(123));
|
||||
|
||||
// Threads should inherit "main" globals
|
||||
let f = lua.create_function(|lua, ()| lua.globals().get::<_, i32>("global"))?;
|
||||
let co = lua.create_thread(f.clone())?;
|
||||
assert_eq!(co.resume::<_, Option<i32>>(())?, Some(123));
|
||||
|
||||
// Sandboxed threads should also inherit "main" globals
|
||||
let co = lua.create_thread(f)?;
|
||||
co.sandbox()?;
|
||||
assert_eq!(co.resume::<_, Option<i32>>(())?, Some(123));
|
||||
|
||||
lua.sandbox(false)?;
|
||||
|
||||
// Previously set variable `global` should be cleared now
|
||||
assert_eq!(lua.globals().get::<_, Option<i32>>("global")?, None);
|
||||
|
||||
// Readonly flags should be cleared as well
|
||||
let table = lua.globals().get::<_, Table>("table")?;
|
||||
table.set("test", "test")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sandbox_threads() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
let f = lua.create_function(|lua, v: Value| lua.globals().set("global", v))?;
|
||||
|
||||
let co = lua.create_thread(f.clone())?;
|
||||
co.resume(321)?;
|
||||
// The main state should see the `global` variable (as the thread is not sandboxed)
|
||||
assert_eq!(lua.globals().get::<_, Option<i32>>("global")?, Some(321));
|
||||
|
||||
let co = lua.create_thread(f.clone())?;
|
||||
co.sandbox()?;
|
||||
co.resume(123)?;
|
||||
// The main state should see the previous `global` value (as the thread is sandboxed)
|
||||
assert_eq!(lua.globals().get::<_, Option<i32>>("global")?, Some(321));
|
||||
|
||||
// Try to reset the (sandboxed) thread
|
||||
co.reset(f)?;
|
||||
co.resume(111)?;
|
||||
assert_eq!(lua.globals().get::<_, Option<i32>>("global")?, Some(111));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_interrupts() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
let interrupts_count = Arc::new(AtomicU64::new(0));
|
||||
let interrupts_count2 = interrupts_count.clone();
|
||||
|
||||
lua.set_interrupt(move |_lua| {
|
||||
interrupts_count2.fetch_add(1, Ordering::Relaxed);
|
||||
Ok(VmState::Continue)
|
||||
});
|
||||
let f = lua
|
||||
.load(
|
||||
r#"
|
||||
local x = 2 + 3
|
||||
local y = x * 63
|
||||
local z = string.len(x..", "..y)
|
||||
"#,
|
||||
)
|
||||
.into_function()?;
|
||||
f.call(())?;
|
||||
|
||||
assert!(interrupts_count.load(Ordering::Relaxed) > 0);
|
||||
|
||||
//
|
||||
// Test yields from interrupt
|
||||
//
|
||||
let yield_count = Arc::new(AtomicU64::new(0));
|
||||
let yield_count2 = yield_count.clone();
|
||||
lua.set_interrupt(move |_lua| {
|
||||
if yield_count2.fetch_add(1, Ordering::Relaxed) == 1 {
|
||||
return Ok(VmState::Yield);
|
||||
}
|
||||
Ok(VmState::Continue)
|
||||
});
|
||||
let co = lua.create_thread(
|
||||
lua.load(
|
||||
r#"
|
||||
local a = {1, 2, 3}
|
||||
local b = 0
|
||||
for _, x in ipairs(a) do b += x end
|
||||
return b
|
||||
"#,
|
||||
)
|
||||
.into_function()?,
|
||||
)?;
|
||||
co.resume(())?;
|
||||
assert_eq!(co.status(), ThreadStatus::Resumable);
|
||||
let result: i32 = co.resume(())?;
|
||||
assert_eq!(result, 6);
|
||||
assert_eq!(yield_count.load(Ordering::Relaxed), 7);
|
||||
assert_eq!(co.status(), ThreadStatus::Unresumable);
|
||||
|
||||
//
|
||||
// Test errors in interrupts
|
||||
//
|
||||
lua.set_interrupt(|_| Err(Error::RuntimeError("error from interrupt".into())));
|
||||
match f.call::<_, ()>(()) {
|
||||
Err(Error::CallbackError { cause, .. }) => match *cause {
|
||||
Error::RuntimeError(ref m) if m == "error from interrupt" => {}
|
||||
ref e => panic!("expected RuntimeError with a specific message, got {:?}", e),
|
||||
},
|
||||
r => panic!("expected CallbackError, got {:?}", r),
|
||||
}
|
||||
|
||||
lua.remove_interrupt();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user