From cd6d86a5ce1f7041a65d97f504b19b2fb3365373 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 8 Jul 2024 00:36:29 +0100 Subject: [PATCH] Split single `lua` module to multiple submodules under `state` --- src/chunk.rs | 2 +- src/conversion.rs | 38 +- src/function.rs | 14 +- src/hook.rs | 34 +- src/lib.rs | 4 +- src/lua.rs | 3804 -------------------------------------- src/luau/mod.rs | 2 +- src/luau/package.rs | 2 +- src/multi.rs | 23 +- src/serde/mod.rs | 2 +- src/serde/ser.rs | 2 +- src/state.rs | 1936 +++++++++++++++++++ src/state/extra.rs | 236 +++ src/state/raw.rs | 1421 ++++++++++++++ src/state/util.rs | 187 ++ src/thread.rs | 13 +- src/types.rs | 13 +- src/userdata.rs | 2 +- src/userdata/cell.rs | 7 +- src/userdata/registry.rs | 2 +- src/util/mod.rs | 15 +- src/value.rs | 19 +- tests/tests.rs | 6 +- 23 files changed, 3888 insertions(+), 3896 deletions(-) delete mode 100644 src/lua.rs create mode 100644 src/state.rs create mode 100644 src/state/extra.rs create mode 100644 src/state/raw.rs create mode 100644 src/state/util.rs diff --git a/src/chunk.rs b/src/chunk.rs index a0a48e1..a7d7812 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -7,7 +7,7 @@ use std::string::String as StdString; use crate::error::{Error, ErrorContext, Result}; use crate::function::Function; -use crate::lua::{Lua, WeakLua}; +use crate::state::{Lua, WeakLua}; use crate::table::Table; use crate::value::{FromLuaMulti, IntoLua, IntoLuaMulti}; diff --git a/src/conversion.rs b/src/conversion.rs index 1194c63..f82039f 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -12,7 +12,7 @@ use num_traits::cast; use crate::error::{Error, Result}; use crate::function::Function; -use crate::lua::{Lua, LuaInner}; +use crate::state::{Lua, RawLua}; use crate::string::String; use crate::table::Table; use crate::thread::Thread; @@ -34,7 +34,7 @@ impl IntoLua for &Value { } #[inline] - unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { lua.push_value(self) } } @@ -60,7 +60,7 @@ impl IntoLua for &String { } #[inline] - unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { lua.push_ref(&self.0); Ok(()) } @@ -93,7 +93,7 @@ impl IntoLua for &Table { } #[inline] - unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { lua.push_ref(&self.0); Ok(()) } @@ -127,7 +127,7 @@ impl IntoLua for &Function { } #[inline] - unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { lua.push_ref(&self.0); Ok(()) } @@ -161,7 +161,7 @@ impl IntoLua for &Thread { } #[inline] - unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { lua.push_ref(&self.0); Ok(()) } @@ -195,7 +195,7 @@ impl IntoLua for &AnyUserData { } #[inline] - unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { lua.push_ref(&self.0); Ok(()) } @@ -250,7 +250,7 @@ impl IntoLua for RegistryKey { } #[inline] - unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { <&RegistryKey>::push_into_stack(&self, lua) } } @@ -261,7 +261,7 @@ impl IntoLua for &RegistryKey { lua.registry_value(self) } - unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { if !lua.owns_registry_value(self) { return Err(Error::MismatchedRegistryKey); } @@ -290,7 +290,7 @@ impl IntoLua for bool { } #[inline] - unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { ffi::lua_pushboolean(lua.state(), self as c_int); Ok(()) } @@ -307,7 +307,7 @@ impl FromLua for bool { } #[inline] - unsafe fn from_stack(idx: c_int, lua: &LuaInner) -> Result { + unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { Ok(ffi::lua_toboolean(lua.state(), idx) != 0) } } @@ -363,7 +363,7 @@ impl IntoLua for StdString { } #[inline] - unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { push_bytes_into_stack(self, lua) } } @@ -384,7 +384,7 @@ impl FromLua for StdString { } #[inline] - unsafe fn from_stack(idx: c_int, lua: &LuaInner) -> Result { + unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { let state = lua.state(); if ffi::lua_type(state, idx) == ffi::LUA_TSTRING { let mut size = 0; @@ -410,7 +410,7 @@ impl IntoLua for &str { } #[inline] - unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { push_bytes_into_stack(self, lua) } } @@ -522,7 +522,7 @@ impl FromLua for BString { } } - unsafe fn from_stack(idx: c_int, lua: &LuaInner) -> Result { + unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { let state = lua.state(); match ffi::lua_type(state, idx) { ffi::LUA_TSTRING => { @@ -553,7 +553,7 @@ impl IntoLua for &BStr { } #[inline] -unsafe fn push_bytes_into_stack(this: T, lua: &LuaInner) -> Result<()> +unsafe fn push_bytes_into_stack(this: T, lua: &RawLua) -> Result<()> where T: IntoLua + AsRef<[u8]>, { @@ -584,7 +584,7 @@ macro_rules! lua_convert_int { } #[inline] - unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { match cast(self) { Some(i) => ffi::lua_pushinteger(lua.state(), i), None => ffi::lua_pushnumber(lua.state(), self as ffi::lua_Number), @@ -881,7 +881,7 @@ impl IntoLua for Option { } #[inline] - unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { match self { Some(val) => val.push_into_stack(lua)?, None => ffi::lua_pushnil(lua.state()), @@ -900,7 +900,7 @@ impl FromLua for Option { } #[inline] - unsafe fn from_stack(idx: c_int, lua: &LuaInner) -> Result { + unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { if ffi::lua_isnil(lua.state(), idx) != 0 { Ok(None) } else { diff --git a/src/function.rs b/src/function.rs index 30bcd44..c4ff281 100644 --- a/src/function.rs +++ b/src/function.rs @@ -5,7 +5,7 @@ use std::ptr; use std::slice; use crate::error::{Error, Result}; -use crate::lua::Lua; +use crate::state::Lua; use crate::table::Table; use crate::types::{Callback, MaybeSend, ValueRef}; use crate::util::{ @@ -161,11 +161,13 @@ impl Function { R: FromLuaMulti, { let lua = self.0.lua.lock(); - let thread_res = lua.create_recycled_thread(self).map(|th| { - let mut th = th.into_async(args); - th.set_recyclable(true); - th - }); + let thread_res = unsafe { + lua.create_recycled_thread(self).map(|th| { + let mut th = th.into_async(args); + th.set_recyclable(true); + th + }) + }; async move { thread_res?.await } } diff --git a/src/hook.rs b/src/hook.rs index 72f8b88..6179f09 100644 --- a/src/hook.rs +++ b/src/hook.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; use std::cell::UnsafeCell; -use std::mem::ManuallyDrop; +use std::ops::Deref; #[cfg(not(feature = "luau"))] use std::ops::{BitOr, BitOrAssign}; use std::os::raw::c_int; @@ -8,7 +8,7 @@ use std::os::raw::c_int; use ffi::lua_Debug; use parking_lot::ReentrantMutexGuard; -use crate::lua::{Lua, LuaInner}; +use crate::state::RawLua; use crate::util::{linenumber_to_usize, ptr_to_lossy_str, ptr_to_str}; /// Contains information about currently executing Lua code. @@ -20,38 +20,46 @@ use crate::util::{linenumber_to_usize, ptr_to_lossy_str, ptr_to_str}; /// /// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#lua_Debug /// [`Lua::set_hook`]: crate::Lua::set_hook -pub struct Debug<'lua> { - lua: ManuallyDrop>, +pub struct Debug<'a> { + lua: EitherLua<'a>, ar: ActivationRecord, #[cfg(feature = "luau")] level: c_int, } -impl<'lua> Drop for Debug<'lua> { - fn drop(&mut self) { - if let ActivationRecord::Owned(_) = self.ar { - unsafe { ManuallyDrop::drop(&mut self.lua) } +enum EitherLua<'a> { + Owned(ReentrantMutexGuard<'a, RawLua>), + Borrowed(&'a RawLua), +} + +impl Deref for EitherLua<'_> { + type Target = RawLua; + + fn deref(&self) -> &Self::Target { + match self { + EitherLua::Owned(guard) => &*guard, + EitherLua::Borrowed(lua) => lua, } } } -impl<'lua> Debug<'lua> { +impl<'a> Debug<'a> { // We assume the lock is held when this function is called. #[cfg(not(feature = "luau"))] - pub(crate) fn new(lua: &'lua Lua, ar: *mut lua_Debug) -> Self { + pub(crate) fn new(lua: &'a RawLua, ar: *mut lua_Debug) -> Self { Debug { - lua: unsafe { lua.guard_unchecked() }, + lua: EitherLua::Borrowed(lua), ar: ActivationRecord::Borrowed(ar), } } pub(crate) fn new_owned( - guard: ReentrantMutexGuard<'lua, LuaInner>, + guard: ReentrantMutexGuard<'a, RawLua>, _level: c_int, ar: lua_Debug, ) -> Self { Debug { - lua: ManuallyDrop::new(guard), + lua: EitherLua::Owned(guard), ar: ActivationRecord::Owned(UnsafeCell::new(ar)), #[cfg(feature = "luau")] level: _level, diff --git a/src/lib.rs b/src/lib.rs index 22cf853..d56321c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,12 +84,12 @@ mod conversion; mod error; mod function; mod hook; -mod lua; #[cfg(feature = "luau")] mod luau; mod memory; mod multi; // mod scope; +mod state; mod stdlib; mod string; mod table; @@ -107,7 +107,7 @@ pub use crate::chunk::{AsChunk, Chunk, ChunkMode}; pub use crate::error::{Error, ErrorContext, ExternalError, ExternalResult, Result}; pub use crate::function::{Function, FunctionInfo}; pub use crate::hook::{Debug, DebugEvent, DebugNames, DebugSource, DebugStack}; -pub use crate::lua::{GCMode, Lua, LuaOptions}; +pub use crate::state::{GCMode, Lua, LuaOptions}; pub use crate::multi::Variadic; // pub use crate::scope::Scope; pub use crate::stdlib::StdLib; diff --git a/src/lua.rs b/src/lua.rs deleted file mode 100644 index 8baefea..0000000 --- a/src/lua.rs +++ /dev/null @@ -1,3804 +0,0 @@ -use std::any::TypeId; -use std::cell::{Cell, RefCell, UnsafeCell}; -// use std::collections::VecDeque; -use std::ffi::{CStr, CString}; -use std::fmt; -use std::marker::PhantomData; -use std::mem::{self, ManuallyDrop, MaybeUninit}; -use std::ops::Deref; -use std::os::raw::{c_char, c_int, c_void}; -use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe, Location}; -use std::ptr; -use std::result::Result as StdResult; -use std::sync::{Arc, Weak}; - -use parking_lot::{Mutex, ReentrantMutex, ReentrantMutexGuard}; -use rustc_hash::FxHashMap; - -use crate::chunk::{AsChunk, Chunk, ChunkMode}; -use crate::error::{Error, Result}; -use crate::function::Function; -use crate::hook::Debug; -use crate::memory::{MemoryState, ALLOCATOR}; -// use crate::scope::Scope; -use crate::stdlib::StdLib; -use crate::string::String; -use crate::table::Table; -use crate::thread::Thread; -use crate::types::{ - AppData, AppDataRef, AppDataRefMut, ArcReentrantMutexGuard, Callback, CallbackUpvalue, - DestructedUserdata, Integer, LightUserData, MaybeSend, Number, RegistryKey, SubtypeId, - ValueRef, -}; -use crate::userdata::{ - AnyUserData, MetaMethod, UserData, UserDataProxy, UserDataRef, UserDataRegistry, - UserDataVariant, -}; -use crate::util::{ - self, assert_stack, check_stack, error_traceback, get_destructed_userdata_metatable, - get_gc_metatable, get_gc_userdata, get_main_state, get_userdata, init_error_registry, - init_gc_metatable, init_userdata_metatable, pop_error, push_gc_userdata, push_string, - push_table, rawset_field, safe_pcall, safe_xpcall, short_type_name, StackGuard, WrappedFailure, -}; -use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil, Value}; - -#[cfg(not(feature = "lua54"))] -use crate::util::push_userdata; -#[cfg(feature = "lua54")] -use crate::{types::WarnCallback, userdata::USER_VALUE_MAXSLOT, util::push_userdata_uv}; - -#[cfg(not(feature = "luau"))] -use crate::{hook::HookTriggers, types::HookCallback}; - -#[cfg(feature = "luau")] -use crate::types::InterruptCallback; -#[cfg(any(feature = "luau", doc))] -use crate::{ - chunk::Compiler, - types::{Vector, VmState}, -}; - -#[cfg(feature = "async")] -use { - crate::types::{AsyncCallback, AsyncCallbackUpvalue, AsyncPollUpvalue}, - futures_util::task::noop_waker_ref, - std::future::{self, Future}, - std::ptr::NonNull, - std::task::{Context, Poll, Waker}, -}; - -#[cfg(feature = "serialize")] -use serde::Serialize; - -/// Top level Lua struct which represents an instance of Lua VM. -#[derive(Clone)] -#[repr(transparent)] -pub struct Lua(Arc>); - -#[derive(Clone)] -#[repr(transparent)] -pub(crate) struct WeakLua(Weak>); - -pub(crate) struct LuaGuard(ArcReentrantMutexGuard); - -/// An inner Lua struct which holds a raw Lua state. -pub struct LuaInner { - // The state is dynamic and depends on context - state: Cell<*mut ffi::lua_State>, - main_state: *mut ffi::lua_State, - extra: Arc>, -} - -// Data associated with the Lua. -pub(crate) struct ExtraData { - // Same layout as `Lua` - inner: MaybeUninit>>, - weak: MaybeUninit>>, - - registered_userdata: FxHashMap, - registered_userdata_mt: FxHashMap<*const c_void, Option>, - last_checked_userdata_mt: (*const c_void, Option), - - // When Lua instance dropped, setting `None` would prevent collecting `RegistryKey`s - registry_unref_list: Arc>>>, - - // Container to store arbitrary data (extensions) - app_data: AppData, - - safe: bool, - libs: StdLib, - #[cfg(feature = "module")] - skip_memory_check: bool, - - // Auxiliary thread to store references - ref_thread: *mut ffi::lua_State, - ref_stack_size: c_int, - ref_stack_top: c_int, - ref_free: Vec, - - // Pool of `WrappedFailure` enums in the ref thread (as userdata) - wrapped_failure_pool: Vec, - // Pool of `MultiValue` containers - // multivalue_pool: Vec>, - // Pool of `Thread`s (coroutines) for async execution - #[cfg(feature = "async")] - thread_pool: Vec, - - // Address of `WrappedFailure` metatable - wrapped_failure_mt_ptr: *const c_void, - - // Waker for polling futures - #[cfg(feature = "async")] - waker: NonNull, - - #[cfg(not(feature = "luau"))] - hook_callback: Option, - #[cfg(not(feature = "luau"))] - hook_thread: *mut ffi::lua_State, - #[cfg(feature = "lua54")] - warn_callback: Option, - #[cfg(feature = "luau")] - interrupt_callback: Option, - - #[cfg(feature = "luau")] - sandboxed: bool, - #[cfg(feature = "luau")] - compiler: Option, - #[cfg(feature = "luau-jit")] - enable_jit: bool, -} - -/// Mode of the Lua garbage collector (GC). -/// -/// In Lua 5.4 GC can work in two modes: incremental and generational. -/// Previous Lua versions support only incremental GC. -/// -/// More information can be found in the Lua [documentation]. -/// -/// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5 -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum GCMode { - Incremental, - /// Requires `feature = "lua54"` - #[cfg(feature = "lua54")] - #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] - Generational, -} - -/// Controls Lua interpreter behavior such as Rust panics handling. -#[derive(Clone, Debug)] -#[non_exhaustive] -pub struct LuaOptions { - /// Catch Rust panics when using [`pcall`]/[`xpcall`]. - /// - /// If disabled, wraps these functions and automatically resumes panic if found. - /// Also in Lua 5.1 adds ability to provide arguments to [`xpcall`] similar to Lua >= 5.2. - /// - /// If enabled, keeps [`pcall`]/[`xpcall`] unmodified. - /// Panics are still automatically resumed if returned to the Rust side. - /// - /// Default: **true** - /// - /// [`pcall`]: https://www.lua.org/manual/5.4/manual.html#pdf-pcall - /// [`xpcall`]: https://www.lua.org/manual/5.4/manual.html#pdf-xpcall - pub catch_rust_panics: bool, - - /// Max size of thread (coroutine) object pool used to execute asynchronous functions. - /// - /// It works on Lua 5.4 and Luau, where [`lua_resetthread`] function - /// is available and allows to reuse old coroutines after resetting their state. - /// - /// Default: **0** (disabled) - /// - /// [`lua_resetthread`]: https://www.lua.org/manual/5.4/manual.html#lua_resetthread - #[cfg(feature = "async")] - #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - pub thread_pool_size: usize, -} - -impl Default for LuaOptions { - fn default() -> Self { - LuaOptions::new() - } -} - -impl LuaOptions { - /// Returns a new instance of `LuaOptions` with default parameters. - pub const fn new() -> Self { - LuaOptions { - catch_rust_panics: true, - #[cfg(feature = "async")] - thread_pool_size: 0, - } - } - - /// Sets [`catch_rust_panics`] option. - /// - /// [`catch_rust_panics`]: #structfield.catch_rust_panics - #[must_use] - pub const fn catch_rust_panics(mut self, enabled: bool) -> Self { - self.catch_rust_panics = enabled; - self - } - - /// Sets [`thread_pool_size`] option. - /// - /// [`thread_pool_size`]: #structfield.thread_pool_size - #[cfg(feature = "async")] - #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - #[must_use] - pub const fn thread_pool_size(mut self, size: usize) -> Self { - self.thread_pool_size = size; - self - } -} - -#[cfg(feature = "async")] -pub(crate) static ASYNC_POLL_PENDING: u8 = 0; -pub(crate) static EXTRA_REGISTRY_KEY: u8 = 0; - -const WRAPPED_FAILURE_POOL_SIZE: usize = 64; -// const MULTIVALUE_POOL_SIZE: usize = 64; -const REF_STACK_RESERVE: c_int = 1; - -/// Requires `feature = "send"` -#[cfg(feature = "send")] -#[cfg_attr(docsrs, doc(cfg(feature = "send")))] -unsafe impl Send for Lua {} - -#[cfg(not(feature = "module"))] -impl Drop for Lua { - fn drop(&mut self) { - let _ = self.gc_collect(); - } -} - -#[cfg(not(feature = "module"))] -impl Drop for LuaInner { - fn drop(&mut self) { - unsafe { - let mem_state = MemoryState::get(self.main_state); - - ffi::lua_close(self.main_state); - - // Deallocate MemoryState - if !mem_state.is_null() { - drop(Box::from_raw(mem_state)); - } - } - } -} - -impl Drop for ExtraData { - fn drop(&mut self) { - #[cfg(feature = "module")] - unsafe { - self.inner.assume_init_drop(); - } - unsafe { self.weak.assume_init_drop() }; - *self.registry_unref_list.lock() = None; - } -} - -impl fmt::Debug for Lua { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Lua({:p})", self.lock().state()) - } -} - -impl Default for Lua { - #[inline] - fn default() -> Self { - Lua::new() - } -} - -impl Lua { - /// Creates a new Lua state and loads the **safe** subset of the standard libraries. - /// - /// # Safety - /// The created Lua state would have _some_ safety guarantees and would not allow to load unsafe - /// standard libraries or C modules. - /// - /// See [`StdLib`] documentation for a list of unsafe modules that cannot be loaded. - /// - /// [`StdLib`]: crate::StdLib - pub fn new() -> Lua { - mlua_expect!( - Self::new_with(StdLib::ALL_SAFE, LuaOptions::default()), - "Cannot create new safe Lua state" - ) - } - - /// Creates a new Lua state and loads all the standard libraries. - /// - /// # Safety - /// The created Lua state would not have safety guarantees and would allow to load C modules. - pub unsafe fn unsafe_new() -> Lua { - Self::unsafe_new_with(StdLib::ALL, LuaOptions::default()) - } - - /// Creates a new Lua state and loads the specified safe subset of the standard libraries. - /// - /// Use the [`StdLib`] flags to specify the libraries you want to load. - /// - /// # Safety - /// The created Lua state would have _some_ safety guarantees and would not allow to load unsafe - /// standard libraries or C modules. - /// - /// See [`StdLib`] documentation for a list of unsafe modules that cannot be loaded. - /// - /// [`StdLib`]: crate::StdLib - pub fn new_with(libs: StdLib, options: LuaOptions) -> Result { - #[cfg(not(feature = "luau"))] - if libs.contains(StdLib::DEBUG) { - return Err(Error::SafetyError( - "The unsafe `debug` module can't be loaded using safe `new_with`".to_string(), - )); - } - #[cfg(feature = "luajit")] - if libs.contains(StdLib::FFI) { - return Err(Error::SafetyError( - "The unsafe `ffi` module can't be loaded using safe `new_with`".to_string(), - )); - } - - let lua = unsafe { Self::inner_new(libs, options) }; - - if libs.contains(StdLib::PACKAGE) { - mlua_expect!(lua.disable_c_modules(), "Error during disabling C modules"); - } - unsafe { - let rawlua = lua.lock(); - (*rawlua.extra.get()).safe = true; - } - - Ok(lua) - } - - /// Creates a new Lua state and loads the specified subset of the standard libraries. - /// - /// Use the [`StdLib`] flags to specify the libraries you want to load. - /// - /// # Safety - /// The created Lua state will not have safety guarantees and allow to load C modules. - /// - /// [`StdLib`]: crate::StdLib - pub unsafe fn unsafe_new_with(libs: StdLib, options: LuaOptions) -> Lua { - // Workaround to avoid stripping a few unused Lua symbols that could be imported - // by C modules in unsafe mode - let mut _symbols: Vec<*const extern "C-unwind" fn()> = - vec![ffi::lua_isuserdata as _, ffi::lua_tocfunction as _]; - - #[cfg(not(feature = "luau"))] - _symbols.extend_from_slice(&[ - ffi::lua_atpanic as _, - ffi::luaL_loadstring as _, - ffi::luaL_openlibs as _, - ]); - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] - { - _symbols.push(ffi::lua_getglobal as _); - _symbols.push(ffi::lua_setglobal as _); - _symbols.push(ffi::luaL_setfuncs as _); - } - - Self::inner_new(libs, options) - } - - /// Creates a new Lua state with required `libs` and `options` - unsafe fn inner_new(libs: StdLib, options: LuaOptions) -> Lua { - let mem_state: *mut MemoryState = Box::into_raw(Box::default()); - let mut state = ffi::lua_newstate(ALLOCATOR, mem_state as *mut c_void); - // If state is null then switch to Lua internal allocator - if state.is_null() { - drop(Box::from_raw(mem_state)); - state = ffi::luaL_newstate(); - } - assert!(!state.is_null(), "Failed to instantiate Lua VM"); - - ffi::luaL_requiref(state, cstr!("_G"), ffi::luaopen_base, 1); - ffi::lua_pop(state, 1); - - // Init Luau code generator (jit) - #[cfg(feature = "luau-jit")] - if ffi::luau_codegen_supported() != 0 { - ffi::luau_codegen_create(state); - } - - let lua = Lua::init_from_ptr(state); - let extra = lua.lock().extra.get(); - - mlua_expect!( - load_from_std_lib(state, libs), - "Error during loading standard libraries" - ); - (*extra).libs |= libs; - - if !options.catch_rust_panics { - mlua_expect!( - (|| -> Result<()> { - let _sg = StackGuard::new(state); - - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] - ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS); - #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] - ffi::lua_pushvalue(state, ffi::LUA_GLOBALSINDEX); - - ffi::lua_pushcfunction(state, safe_pcall); - rawset_field(state, -2, "pcall")?; - - ffi::lua_pushcfunction(state, safe_xpcall); - rawset_field(state, -2, "xpcall")?; - - Ok(()) - })(), - "Error during applying option `catch_rust_panics`" - ) - } - - #[cfg(feature = "async")] - if options.thread_pool_size > 0 { - (*extra).thread_pool.reserve_exact(options.thread_pool_size); - } - - #[cfg(feature = "luau")] - mlua_expect!(lua.configure_luau(), "Error configuring Luau"); - - lua - } - - /// Constructs a new Lua instance from an existing raw state. - /// - /// Once called, a returned Lua state is cached in the registry and can be retrieved - /// by calling this function again. - #[allow(clippy::missing_safety_doc, clippy::arc_with_non_send_sync)] - pub unsafe fn init_from_ptr(state: *mut ffi::lua_State) -> Lua { - assert!(!state.is_null(), "Lua state is NULL"); - if let Some(lua) = Lua::try_from_ptr(state) { - return lua; - } - - let main_state = get_main_state(state).unwrap_or(state); - let main_state_top = ffi::lua_gettop(main_state); - - mlua_expect!( - (|state| { - init_error_registry(state)?; - - // Create the internal metatables and place them in the registry - // to prevent them from being garbage collected. - - init_gc_metatable::>>(state, None)?; - init_gc_metatable::(state, None)?; - init_gc_metatable::(state, None)?; - #[cfg(feature = "async")] - { - init_gc_metatable::(state, None)?; - init_gc_metatable::(state, None)?; - init_gc_metatable::(state, None)?; - init_gc_metatable::>(state, None)?; - } - - // Init serde metatables - #[cfg(feature = "serialize")] - crate::serde::init_metatables(state)?; - - Ok::<_, Error>(()) - })(main_state), - "Error during Lua construction", - ); - - // Create ref stack thread and place it in the registry to prevent it from being garbage - // collected. - let ref_thread = mlua_expect!( - protect_lua!(main_state, 0, 0, |state| { - let thread = ffi::lua_newthread(state); - ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX); - thread - }), - "Error while creating ref thread", - ); - - let wrapped_failure_mt_ptr = { - get_gc_metatable::(main_state); - let ptr = ffi::lua_topointer(main_state, -1); - ffi::lua_pop(main_state, 1); - ptr - }; - - // Store `error_traceback` function on the ref stack - #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] - { - ffi::lua_pushcfunction(ref_thread, error_traceback); - assert_eq!(ffi::lua_gettop(ref_thread), ExtraData::ERROR_TRACEBACK_IDX); - } - - // Create ExtraData - let extra = Arc::new(UnsafeCell::new(ExtraData { - inner: MaybeUninit::uninit(), - weak: MaybeUninit::uninit(), - registered_userdata: FxHashMap::default(), - registered_userdata_mt: FxHashMap::default(), - last_checked_userdata_mt: (ptr::null(), None), - registry_unref_list: Arc::new(Mutex::new(Some(Vec::new()))), - app_data: AppData::default(), - safe: false, - libs: StdLib::NONE, - #[cfg(feature = "module")] - skip_memory_check: false, - ref_thread, - // We need some reserved stack space to move values in and out of the ref stack. - ref_stack_size: ffi::LUA_MINSTACK - REF_STACK_RESERVE, - ref_stack_top: ffi::lua_gettop(ref_thread), - ref_free: Vec::new(), - wrapped_failure_pool: Vec::with_capacity(WRAPPED_FAILURE_POOL_SIZE), - // multivalue_pool: Vec::with_capacity(MULTIVALUE_POOL_SIZE), - #[cfg(feature = "async")] - thread_pool: Vec::new(), - wrapped_failure_mt_ptr, - #[cfg(feature = "async")] - waker: NonNull::from(noop_waker_ref()), - #[cfg(not(feature = "luau"))] - hook_callback: None, - #[cfg(not(feature = "luau"))] - hook_thread: ptr::null_mut(), - #[cfg(feature = "lua54")] - warn_callback: None, - #[cfg(feature = "luau")] - interrupt_callback: None, - #[cfg(feature = "luau")] - sandboxed: false, - #[cfg(feature = "luau")] - compiler: None, - #[cfg(feature = "luau-jit")] - enable_jit: true, - })); - - // Store it in the registry - mlua_expect!( - set_extra_data(main_state, &extra), - "Error while storing extra data" - ); - - // Register `DestructedUserdata` type - get_destructed_userdata_metatable(main_state); - let destructed_mt_ptr = ffi::lua_topointer(main_state, -1); - let destructed_ud_typeid = TypeId::of::(); - (*extra.get()) - .registered_userdata_mt - .insert(destructed_mt_ptr, Some(destructed_ud_typeid)); - ffi::lua_pop(main_state, 1); - - mlua_debug_assert!( - ffi::lua_gettop(main_state) == main_state_top, - "stack leak during creation" - ); - assert_stack(main_state, ffi::LUA_MINSTACK); - - let inner = Arc::new(ReentrantMutex::new(LuaInner { - state: Cell::new(state), - main_state, - extra: Arc::clone(&extra), - })); - - (*extra.get()).inner.write(Arc::clone(&inner)); - #[cfg(not(feature = "module"))] - Arc::decrement_strong_count(Arc::as_ptr(&inner)); - (*extra.get()).weak.write(Arc::downgrade(&inner)); - - Lua(inner) - } - - /// Loads the specified subset of the standard libraries into an existing Lua state. - /// - /// Use the [`StdLib`] flags to specify the libraries you want to load. - /// - /// [`StdLib`]: crate::StdLib - pub fn load_from_std_lib(&self, libs: StdLib) -> Result<()> { - let lua = self.lock(); - let is_safe = unsafe { (*lua.extra.get()).safe }; - - #[cfg(not(feature = "luau"))] - if is_safe && libs.contains(StdLib::DEBUG) { - return Err(Error::SafetyError( - "the unsafe `debug` module can't be loaded in safe mode".to_string(), - )); - } - #[cfg(feature = "luajit")] - if is_safe && libs.contains(StdLib::FFI) { - return Err(Error::SafetyError( - "the unsafe `ffi` module can't be loaded in safe mode".to_string(), - )); - } - - let res = unsafe { load_from_std_lib(lua.main_state, libs) }; - - // If `package` library loaded into a safe lua state then disable C modules - let curr_libs = unsafe { (*lua.extra.get()).libs }; - if is_safe && (curr_libs ^ (curr_libs | libs)).contains(StdLib::PACKAGE) { - mlua_expect!(self.disable_c_modules(), "Error during disabling C modules"); - } - unsafe { (*lua.extra.get()).libs |= libs }; - - res - } - - /// Loads module `modname` into an existing Lua state using the specified entrypoint - /// function. - /// - /// Internally calls the Lua function `func` with the string `modname` as an argument, - /// sets the call result to `package.loaded[modname]` and returns copy of the result. - /// - /// If `package.loaded[modname]` value is not nil, returns copy of the value without - /// calling the function. - /// - /// If the function does not return a non-nil value then this method assigns true to - /// `package.loaded[modname]`. - /// - /// Behavior is similar to Lua's [`require`] function. - /// - /// [`require`]: https://www.lua.org/manual/5.4/manual.html#pdf-require - pub fn load_from_function(&self, modname: &str, func: Function) -> Result - where - T: FromLua, - { - let lua = self.lock(); - let state = lua.state(); - let loaded = unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 2)?; - protect_lua!(state, 0, 1, fn(state) { - ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED")); - })?; - Table(lua.pop_ref()) - }; - - let modname = self.create_string(modname)?; - let value = match loaded.raw_get(&modname)? { - Value::Nil => { - let result = match func.call(&modname)? { - Value::Nil => Value::Boolean(true), - res => res, - }; - loaded.raw_set(modname, &result)?; - result - } - res => res, - }; - T::from_lua(value, self) - } - - /// Unloads module `modname`. - /// - /// Removes module from the [`package.loaded`] table which allows to load it again. - /// It does not support unloading binary Lua modules since they are internally cached and can be - /// unloaded only by closing Lua state. - /// - /// [`package.loaded`]: https://www.lua.org/manual/5.4/manual.html#pdf-package.loaded - pub fn unload(&self, modname: &str) -> Result<()> { - let lua = self.lock(); - let state = lua.state(); - let loaded = unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 2)?; - protect_lua!(state, 0, 1, fn(state) { - ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED")); - })?; - Table(lua.pop_ref()) - }; - - let modname = self.create_string(modname)?; - loaded.raw_remove(modname)?; - 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. - /// - /// 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)] - #[cfg(not(tarpaulin_include))] - pub unsafe fn entrypoint(self, state: *mut ffi::lua_State, func: F) -> c_int - where - A: FromLuaMulti, - R: IntoLua, - F: Fn(&Lua, A) -> Result + MaybeSend + 'static, - { - let extra = self.lock().extra.get(); - // `self` is no longer needed and must be dropped at this point to avoid possible memory leak - // in case of possible longjmp (lua_error) below - drop(self); - - callback_error_ext(state, extra, move |nargs| { - let lua = (*extra).lua(); - let rawlua = lua.lock(); - let _guard = StateGuard::new(&rawlua, state); - let args = A::from_stack_args(nargs, 1, None, &rawlua)?; - func(lua, args)?.push_into_stack(&rawlua)?; - Ok(1) - }) - } - - // A simple module entrypoint without arguments - #[doc(hidden)] - #[cfg(not(tarpaulin_include))] - pub unsafe fn entrypoint1(self, state: *mut ffi::lua_State, func: F) -> c_int - where - R: IntoLua, - F: Fn(&Lua) -> Result + MaybeSend + 'static, - { - self.entrypoint(state, move |lua, _: ()| func(lua)) - } - - /// Skips memory checks for some operations. - #[doc(hidden)] - #[cfg(feature = "module")] - pub fn skip_memory_check(&self, skip: bool) { - unsafe { (*self.extra.get()).skip_memory_check = skip }; - } - - /// 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>("var")?, None); - /// # Ok(()) - /// # } - /// ``` - /// - /// Requires `feature = "luau"` - #[cfg(any(feature = "luau", docsrs))] - #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] - pub fn sandbox(&self, enabled: bool) -> Result<()> { - let lua = self.lock(); - unsafe { - if (*lua.extra.get()).sandboxed != enabled { - let state = lua.main_state; - 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` - ffi::lua_xpush(lua.ref_thread(), state, ffi::LUA_GLOBALSINDEX); - ffi::lua_replace(state, ffi::LUA_GLOBALSINDEX); - ffi::luaL_sandbox(state, 0); - } - })?; - (*lua.extra.get()).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` - /// parameter, see [`HookTriggers`] for more details. - /// - /// The provided hook function can error, and this error will be propagated through the Lua code - /// that was executing at the time the hook was triggered. This can be used to implement a - /// limited form of execution limits by setting [`HookTriggers.every_nth_instruction`] and - /// erroring once an instruction limit has been reached. - /// - /// This method sets a hook function for the current thread of this Lua instance. - /// If you want to set a hook function for another thread (coroutine), use [`Thread::set_hook()`] instead. - /// - /// Please note you cannot have more than one hook function set at a time for this Lua instance. - /// - /// # Example - /// - /// Shows each line number of code being executed by the Lua interpreter. - /// - /// ``` - /// # use mlua::{Lua, HookTriggers, Result}; - /// # fn main() -> Result<()> { - /// let lua = Lua::new(); - /// lua.set_hook(HookTriggers::EVERY_LINE, |_lua, debug| { - /// println!("line {}", debug.curr_line()); - /// Ok(()) - /// }); - /// - /// lua.load(r#" - /// local x = 2 + 3 - /// local y = x * 63 - /// local z = string.len(x..", "..y) - /// "#).exec() - /// # } - /// ``` - /// - /// [`HookTriggers`]: crate::HookTriggers - /// [`HookTriggers.every_nth_instruction`]: crate::HookTriggers::every_nth_instruction - #[cfg(not(feature = "luau"))] - #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] - pub fn set_hook(&self, triggers: HookTriggers, callback: F) - where - F: Fn(&Lua, Debug) -> Result<()> + MaybeSend + 'static, - { - let lua = self.lock(); - unsafe { lua.set_thread_hook(lua.state(), triggers, callback) }; - } - - /// Removes any hook previously set by [`Lua::set_hook()`] or [`Thread::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) { - let lua = self.lock(); - unsafe { - let state = lua.state(); - ffi::lua_sethook(state, None, 0, 0); - match get_main_state(lua.main_state) { - Some(main_state) if !ptr::eq(state, main_state) => { - // If main_state is different from state, remove hook from it too - ffi::lua_sethook(main_state, None, 0, 0); - } - _ => {} - }; - (*lua.extra.get()).hook_callback = None; - (*lua.extra.get()).hook_thread = ptr::null_mut(); - } - } - - /// 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 |_| { - /// 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(any(feature = "luau", docsrs))] - #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] - pub fn set_interrupt(&self, callback: F) - where - F: Fn(&Lua) -> Result + MaybeSend + 'static, - { - unsafe extern "C-unwind" fn interrupt_proc(state: *mut ffi::lua_State, gc: c_int) { - if gc >= 0 { - // We don't support GC interrupts since they cannot survive Lua exceptions - return; - } - let extra = extra_data(state); - 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 - } - let lua = (*extra).lua(); - let rawlua = lua.lock(); - let _guard = StateGuard::new(&rawlua, state); - interrupt_cb(lua) - }); - match result { - VmState::Continue => {} - VmState::Yield => { - ffi::lua_yield(state, 0); - } - } - } - - let lua = self.lock(); - unsafe { - (*lua.extra.get()).interrupt_callback = Some(Arc::new(callback)); - (*ffi::lua_callbacks(lua.main_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(any(feature = "luau", docsrs))] - #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] - pub fn remove_interrupt(&self) { - let lua = self.lock(); - unsafe { - (*lua.extra.get()).interrupt_callback = None; - (*ffi::lua_callbacks(lua.main_state)).interrupt = None; - } - } - - /// Sets the warning function to be used by Lua to emit warnings. - /// - /// Requires `feature = "lua54"` - #[cfg(feature = "lua54")] - #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] - pub fn set_warning_function(&self, callback: F) - where - F: Fn(&Lua, &str, bool) -> Result<()> + MaybeSend + 'static, - { - unsafe extern "C-unwind" fn warn_proc(ud: *mut c_void, msg: *const c_char, tocont: c_int) { - let extra = ud as *mut ExtraData; - let lua = (*extra).lua(); - let rawlua = lua.lock(); - callback_error_ext(rawlua.state(), extra, |_| { - let cb = mlua_expect!( - (*extra).warn_callback.as_ref(), - "no warning callback set in warn_proc" - ); - let msg = std::string::String::from_utf8_lossy(CStr::from_ptr(msg).to_bytes()); - cb(lua, &msg, tocont != 0) - }); - } - - let lua = self.lock(); - let state = lua.main_state; - unsafe { - (*lua.extra.get()).warn_callback = Some(Box::new(callback)); - ffi::lua_setwarnf(state, Some(warn_proc), lua.extra.get() as *mut c_void); - } - } - - /// Removes warning function previously set by `set_warning_function`. - /// - /// This function has no effect if a warning function was not previously set. - /// - /// Requires `feature = "lua54"` - #[cfg(feature = "lua54")] - #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] - pub fn remove_warning_function(&self) { - let lua = self.lock(); - unsafe { - (*lua.extra.get()).warn_callback = None; - ffi::lua_setwarnf(lua.main_state, None, ptr::null_mut()); - } - } - - /// Emits a warning with the given message. - /// - /// A message in a call with `incomplete` set to `true` should be continued in - /// another call to this function. - /// - /// Requires `feature = "lua54"` - #[cfg(feature = "lua54")] - #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] - pub fn warning(&self, msg: impl AsRef, incomplete: bool) { - let msg = msg.as_ref(); - let mut bytes = vec![0; msg.len() + 1]; - bytes[..msg.len()].copy_from_slice(msg.as_bytes()); - let real_len = bytes.iter().position(|&c| c == 0).unwrap(); - bytes.truncate(real_len); - let lua = self.lock(); - unsafe { - ffi::lua_warning( - lua.state(), - bytes.as_ptr() as *const c_char, - incomplete as c_int, - ); - } - } - - /// Gets information about the interpreter runtime stack. - /// - /// This function returns [`Debug`] structure that can be used to get information about the function - /// executing at a given level. Level `0` is the current running function, whereas level `n+1` is the - /// function that has called level `n` (except for tail calls, which do not count in the stack). - /// - /// [`Debug`]: crate::hook::Debug - pub fn inspect_stack(&self, level: usize) -> Option { - let lua = self.lock(); - unsafe { - let mut ar: ffi::lua_Debug = mem::zeroed(); - let level = level as c_int; - #[cfg(not(feature = "luau"))] - if ffi::lua_getstack(lua.state(), level, &mut ar) == 0 { - return None; - } - #[cfg(feature = "luau")] - if ffi::lua_getinfo(lua.state(), level, cstr!(""), &mut ar) == 0 { - return None; - } - Some(Debug::new_owned(lua, level, ar)) - } - } - - /// Returns the amount of memory (in bytes) currently used inside this Lua state. - pub fn used_memory(&self) -> usize { - let lua = self.lock(); - unsafe { - match MemoryState::get(lua.main_state) { - mem_state if !mem_state.is_null() => (*mem_state).used_memory(), - _ => { - // Get data from the Lua GC - let used_kbytes = ffi::lua_gc(lua.main_state, ffi::LUA_GCCOUNT, 0); - let used_kbytes_rem = ffi::lua_gc(lua.main_state, ffi::LUA_GCCOUNTB, 0); - (used_kbytes as usize) * 1024 + (used_kbytes_rem as usize) - } - } - } - } - - /// Sets a memory limit (in bytes) on this Lua state. - /// - /// Once an allocation occurs that would pass this memory limit, - /// a `Error::MemoryError` is generated instead. - /// Returns previous limit (zero means no limit). - /// - /// Does not work in module mode where Lua state is managed externally. - pub fn set_memory_limit(&self, limit: usize) -> Result { - let lua = self.lock(); - unsafe { - match MemoryState::get(lua.main_state) { - mem_state if !mem_state.is_null() => Ok((*mem_state).set_memory_limit(limit)), - _ => Err(Error::MemoryLimitNotAvailable), - } - } - } - - /// Returns true if the garbage collector is currently running automatically. - /// - /// Requires `feature = "lua54/lua53/lua52/luau"` - #[cfg(any( - feature = "lua54", - feature = "lua53", - feature = "lua52", - feature = "luau" - ))] - pub fn gc_is_running(&self) -> bool { - let lua = self.lock(); - unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCISRUNNING, 0) != 0 } - } - - /// Stop the Lua GC from running - pub fn gc_stop(&self) { - let lua = self.lock(); - unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCSTOP, 0) }; - } - - /// Restarts the Lua GC if it is not running - pub fn gc_restart(&self) { - let lua = self.lock(); - unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCRESTART, 0) }; - } - - /// Perform a full garbage-collection cycle. - /// - /// It may be necessary to call this function twice to collect all currently unreachable - /// objects. Once to finish the current gc cycle, and once to start and finish the next cycle. - pub fn gc_collect(&self) -> Result<()> { - let lua = self.lock(); - unsafe { - check_stack(lua.main_state, 2)?; - protect_lua!(lua.main_state, 0, 0, fn(state) ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0)) - } - } - - /// Steps the garbage collector one indivisible step. - /// - /// Returns true if this has finished a collection cycle. - pub fn gc_step(&self) -> Result { - self.gc_step_kbytes(0) - } - - /// Steps the garbage collector as though memory had been allocated. - /// - /// if `kbytes` is 0, then this is the same as calling `gc_step`. Returns true if this step has - /// finished a collection cycle. - pub fn gc_step_kbytes(&self, kbytes: c_int) -> Result { - let lua = self.lock(); - unsafe { - check_stack(lua.main_state, 3)?; - protect_lua!(lua.main_state, 0, 0, |state| { - ffi::lua_gc(state, ffi::LUA_GCSTEP, kbytes) != 0 - }) - } - } - - /// Sets the 'pause' value of the collector. - /// - /// Returns the previous value of 'pause'. More information can be found in the Lua - /// [documentation]. - /// - /// For Luau this parameter sets GC goal - /// - /// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5 - pub fn gc_set_pause(&self, pause: c_int) -> c_int { - let lua = self.lock(); - unsafe { - #[cfg(not(feature = "luau"))] - return ffi::lua_gc(lua.main_state, ffi::LUA_GCSETPAUSE, pause); - #[cfg(feature = "luau")] - return ffi::lua_gc(lua.main_state, ffi::LUA_GCSETGOAL, pause); - } - } - - /// Sets the 'step multiplier' value of the collector. - /// - /// Returns the previous value of the 'step multiplier'. More information can be found in the - /// Lua [documentation]. - /// - /// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5 - pub fn gc_set_step_multiplier(&self, step_multiplier: c_int) -> c_int { - let lua = self.lock(); - unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCSETSTEPMUL, step_multiplier) } - } - - /// Changes the collector to incremental mode with the given parameters. - /// - /// Returns the previous mode (always `GCMode::Incremental` in Lua < 5.4). - /// More information can be found in the Lua [documentation]. - /// - /// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5.1 - pub fn gc_inc(&self, pause: c_int, step_multiplier: c_int, step_size: c_int) -> GCMode { - let lua = self.lock(); - let state = lua.main_state; - - #[cfg(any( - feature = "lua53", - feature = "lua52", - feature = "lua51", - feature = "luajit", - feature = "luau" - ))] - unsafe { - if pause > 0 { - #[cfg(not(feature = "luau"))] - ffi::lua_gc(state, ffi::LUA_GCSETPAUSE, pause); - #[cfg(feature = "luau")] - ffi::lua_gc(state, ffi::LUA_GCSETGOAL, pause); - } - - if step_multiplier > 0 { - ffi::lua_gc(state, ffi::LUA_GCSETSTEPMUL, step_multiplier); - } - - #[cfg(feature = "luau")] - if step_size > 0 { - ffi::lua_gc(state, ffi::LUA_GCSETSTEPSIZE, step_size); - } - #[cfg(not(feature = "luau"))] - let _ = step_size; // Ignored - - GCMode::Incremental - } - - #[cfg(feature = "lua54")] - let prev_mode = - unsafe { ffi::lua_gc(state, ffi::LUA_GCINC, pause, step_multiplier, step_size) }; - #[cfg(feature = "lua54")] - match prev_mode { - ffi::LUA_GCINC => GCMode::Incremental, - ffi::LUA_GCGEN => GCMode::Generational, - _ => unreachable!(), - } - } - - /// Changes the collector to generational mode with the given parameters. - /// - /// Returns the previous mode. More information about the generational GC - /// can be found in the Lua 5.4 [documentation][lua_doc]. - /// - /// Requires `feature = "lua54"` - /// - /// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#2.5.2 - #[cfg(feature = "lua54")] - #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] - pub fn gc_gen(&self, minor_multiplier: c_int, major_multiplier: c_int) -> GCMode { - let lua = self.lock(); - let state = lua.main_state; - let prev_mode = - unsafe { ffi::lua_gc(state, ffi::LUA_GCGEN, minor_multiplier, major_multiplier) }; - match prev_mode { - ffi::LUA_GCGEN => GCMode::Generational, - ffi::LUA_GCINC => GCMode::Incremental, - _ => unreachable!(), - } - } - - /// Sets a default Luau compiler (with custom options). - /// - /// This compiler will be used by default to load all Lua chunks - /// including via `require` function. - /// - /// See [`Compiler`] for details and possible options. - /// - /// Requires `feature = "luau"` - #[cfg(any(feature = "luau", doc))] - #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] - pub fn set_compiler(&self, compiler: Compiler) { - let lua = self.lock(); - unsafe { (*lua.extra.get()).compiler = Some(compiler) }; - } - - /// Toggles JIT compilation mode for new chunks of code. - /// - /// By default JIT is enabled. Changing this option does not have any effect on - /// already loaded functions. - #[cfg(any(feature = "luau-jit", doc))] - #[cfg_attr(docsrs, doc(cfg(feature = "luau-jit")))] - pub fn enable_jit(&self, enable: bool) { - unsafe { (*self.extra.get()).enable_jit = enable }; - } - - /// Sets Luau feature flag (global setting). - /// - /// See https://github.com/luau-lang/luau/blob/master/CONTRIBUTING.md#feature-flags for details. - #[cfg(feature = "luau")] - #[doc(hidden)] - #[allow(clippy::result_unit_err)] - pub fn set_fflag(name: &str, enabled: bool) -> StdResult<(), ()> { - if let Ok(name) = CString::new(name) { - if unsafe { ffi::luau_setfflag(name.as_ptr(), enabled as c_int) != 0 } { - return Ok(()); - } - } - Err(()) - } - - /// Returns Lua source code as a `Chunk` builder type. - /// - /// In order to actually compile or run the resulting code, you must call [`Chunk::exec`] or - /// similar on the returned builder. Code is not even parsed until one of these methods is - /// called. - /// - /// [`Chunk::exec`]: crate::Chunk::exec - #[track_caller] - pub fn load<'a>(&self, chunk: impl AsChunk<'a>) -> Chunk<'a> { - let caller = Location::caller(); - Chunk { - lua: self.weak(), - name: chunk.name().unwrap_or_else(|| caller.to_string()), - env: chunk.environment(self), - mode: chunk.mode(), - source: chunk.source(), - #[cfg(feature = "luau")] - compiler: unsafe { (*self.lock().extra.get()).compiler.clone() }, - } - } - - /// Create and return an interned Lua string. Lua strings can be arbitrary [u8] data including - /// embedded nulls, so in addition to `&str` and `&String`, you can also pass plain `&[u8]` - /// here. - pub fn create_string(&self, s: impl AsRef<[u8]>) -> Result { - let lua = self.lock(); - let state = lua.state(); - unsafe { - if lua.unlikely_memory_error() { - push_string(lua.ref_thread(), s.as_ref(), false)?; - return Ok(String(lua.pop_ref_thread())); - } - - let _sg = StackGuard::new(state); - check_stack(state, 3)?; - push_string(state, s.as_ref(), true)?; - Ok(String(lua.pop_ref())) - } - } - - /// Create and return a Luau [buffer] object from a byte slice of data. - /// - /// Requires `feature = "luau"` - /// - /// [buffer]: https://luau-lang.org/library#buffer-library - #[cfg(feature = "luau")] - pub fn create_buffer(&self, buf: impl AsRef<[u8]>) -> Result { - let lua = self.lock(); - let state = lua.state(); - unsafe { - if lua.unlikely_memory_error() { - crate::util::push_buffer(lua.ref_thread(), buf.as_ref(), false)?; - return Ok(AnyUserData(lua.pop_ref_thread(), SubtypeId::Buffer)); - } - - let _sg = StackGuard::new(state); - check_stack(state, 4)?; - crate::util::push_buffer(state, buf.as_ref(), true)?; - Ok(AnyUserData(lua.pop_ref(), SubtypeId::Buffer)) - } - } - - /// Creates and returns a new empty table. - pub fn create_table(&self) -> Result { - self.create_table_with_capacity(0, 0) - } - - /// Creates and returns a new empty table, with the specified capacity. - /// `narr` is a hint for how many elements the table will have as a sequence; - /// `nrec` is a hint for how many other elements the table will have. - /// Lua may use these hints to preallocate memory for the new table. - pub fn create_table_with_capacity(&self, narr: usize, nrec: usize) -> Result
{ - let lua = self.lock(); - let state = lua.state(); - unsafe { - if lua.unlikely_memory_error() { - push_table(lua.ref_thread(), narr, nrec, false)?; - return Ok(Table(lua.pop_ref_thread())); - } - - let _sg = StackGuard::new(state); - check_stack(state, 3)?; - push_table(state, narr, nrec, true)?; - Ok(Table(lua.pop_ref())) - } - } - - /// Creates a table and fills it with values from an iterator. - pub fn create_table_from(&self, iter: I) -> Result
- where - K: IntoLua, - V: IntoLua, - I: IntoIterator, - { - let lua = self.lock(); - let state = lua.state(); - unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 6)?; - - let iter = iter.into_iter(); - let lower_bound = iter.size_hint().0; - let protect = !lua.unlikely_memory_error(); - push_table(state, 0, lower_bound, protect)?; - for (k, v) in iter { - lua.push(k)?; - lua.push(v)?; - if protect { - protect_lua!(state, 3, 1, fn(state) ffi::lua_rawset(state, -3))?; - } else { - ffi::lua_rawset(state, -3); - } - } - - Ok(Table(lua.pop_ref())) - } - } - - /// Creates a table from an iterator of values, using `1..` as the keys. - pub fn create_sequence_from(&self, iter: I) -> Result
- where - T: IntoLua, - I: IntoIterator, - { - let lua = self.lock(); - let state = lua.state(); - unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 5)?; - - let iter = iter.into_iter(); - let lower_bound = iter.size_hint().0; - let protect = !lua.unlikely_memory_error(); - push_table(state, lower_bound, 0, protect)?; - for (i, v) in iter.enumerate() { - lua.push(v)?; - if protect { - protect_lua!(state, 2, 1, |state| { - ffi::lua_rawseti(state, -2, (i + 1) as Integer); - })?; - } else { - ffi::lua_rawseti(state, -2, (i + 1) as Integer); - } - } - - Ok(Table(lua.pop_ref())) - } - } - - /// Wraps a Rust function or closure, creating a callable Lua function handle to it. - /// - /// The function's return value is always a `Result`: If the function returns `Err`, the error - /// is raised as a Lua error, which can be caught using `(x)pcall` or bubble up to the Rust code - /// that invoked the Lua code. This allows using the `?` operator to propagate errors through - /// intermediate Lua code. - /// - /// If the function returns `Ok`, the contained value will be converted to one or more Lua - /// values. For details on Rust-to-Lua conversions, refer to the [`IntoLua`] and [`IntoLuaMulti`] - /// traits. - /// - /// # Examples - /// - /// Create a function which prints its argument: - /// - /// ``` - /// # use mlua::{Lua, Result}; - /// # fn main() -> Result<()> { - /// # let lua = Lua::new(); - /// let greet = lua.create_function(|_, name: String| { - /// println!("Hello, {}!", name); - /// Ok(()) - /// }); - /// # let _ = greet; // used - /// # Ok(()) - /// # } - /// ``` - /// - /// Use tuples to accept multiple arguments: - /// - /// ``` - /// # use mlua::{Lua, Result}; - /// # fn main() -> Result<()> { - /// # let lua = Lua::new(); - /// let print_person = lua.create_function(|_, (name, age): (String, u8)| { - /// println!("{} is {} years old!", name, age); - /// Ok(()) - /// }); - /// # let _ = print_person; // used - /// # Ok(()) - /// # } - /// ``` - /// - /// [`IntoLua`]: crate::IntoLua - /// [`IntoLuaMulti`]: crate::IntoLuaMulti - pub fn create_function(&self, func: F) -> Result - where - A: FromLuaMulti, - R: IntoLuaMulti, - F: Fn(&Lua, A) -> Result + MaybeSend + 'static, - { - let lua = self.lock(); - lua.create_callback(Box::new(move |lua, nargs| unsafe { - let args = A::from_stack_args(nargs, 1, None, lua)?; - func(lua.lua(), args)?.push_into_stack_multi(lua) - })) - } - - /// Wraps a Rust mutable closure, creating a callable Lua function handle to it. - /// - /// This is a version of [`create_function`] that accepts a FnMut argument. Refer to - /// [`create_function`] for more information about the implementation. - /// - /// [`create_function`]: #method.create_function - pub fn create_function_mut(&self, func: F) -> Result - where - A: FromLuaMulti, - R: IntoLuaMulti, - F: FnMut(&Lua, A) -> Result + MaybeSend + 'static, - { - let func = RefCell::new(func); - self.create_function(move |lua, args| { - (*func - .try_borrow_mut() - .map_err(|_| Error::RecursiveMutCallback)?)(lua, args) - }) - } - - /// Wraps a C function, creating a callable Lua function handle to it. - /// - /// # Safety - /// This function is unsafe because provides a way to execute unsafe C function. - pub unsafe fn create_c_function(&self, func: ffi::lua_CFunction) -> Result { - let lua = self.lock(); - let state = lua.state(); - check_stack(state, 1)?; - ffi::lua_pushcfunction(state, func); - Ok(Function(lua.pop_ref())) - } - - /// Wraps a Rust async function or closure, creating a callable Lua function handle to it. - /// - /// While executing the function Rust will poll Future and if the result is not ready, call - /// `yield()` passing internal representation of a `Poll::Pending` value. - /// - /// The function must be called inside Lua coroutine ([`Thread`]) to be able to suspend its execution. - /// An executor should be used to poll [`AsyncThread`] and mlua will take a provided Waker - /// in that case. Otherwise noop waker will be used if try to call the function outside of Rust - /// executors. - /// - /// The family of `call_async()` functions takes care about creating [`Thread`]. - /// - /// Requires `feature = "async"` - /// - /// # Examples - /// - /// Non blocking sleep: - /// - /// ``` - /// use std::time::Duration; - /// use mlua::{Lua, Result}; - /// - /// async fn sleep(_lua: &Lua, n: u64) -> Result<&'static str> { - /// tokio::time::sleep(Duration::from_millis(n)).await; - /// Ok("done") - /// } - /// - /// #[tokio::main] - /// async fn main() -> Result<()> { - /// let lua = Lua::new(); - /// lua.globals().set("sleep", lua.create_async_function(sleep)?)?; - /// let res: String = lua.load("return sleep(...)").call_async(100).await?; // Sleep 100ms - /// assert_eq!(res, "done"); - /// Ok(()) - /// } - /// ``` - /// - /// [`Thread`]: crate::Thread - /// [`AsyncThread`]: crate::AsyncThread - #[cfg(feature = "async")] - #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - pub fn create_async_function<'lua, 'a, F, A, FR, R>(&'lua self, func: F) -> Result - where - 'lua: 'a, - F: Fn(&'a Lua, A) -> FR + MaybeSend + 'static, - A: FromLuaMulti, - FR: Future> + 'a, - R: IntoLuaMulti, - { - let lua = self.lock(); - lua.create_async_callback(Box::new(move |rawlua, args| unsafe { - // let rawlua = mem::transmute::<&LuaInner, &LuaInner>(rawlua); - let lua = rawlua.lua(); - let args = match A::from_lua_args(args, 1, None, lua) { - Ok(args) => args, - Err(e) => return Box::pin(future::ready(Err(e))), - }; - let fut = func(lua, args); - Box::pin(async move { fut.await?.push_into_stack_multi(rawlua) }) - })) - } - - /// Wraps a Lua function into a new thread (or coroutine). - /// - /// Equivalent to `coroutine.create`. - pub fn create_thread(&self, func: Function) -> Result { - self.lock().create_thread_inner(&func) - } - - /// Creates a Lua userdata object from a custom userdata type. - /// - /// All userdata instances of the same type `T` shares the same metatable. - #[inline] - pub fn create_userdata(&self, data: T) -> Result - where - T: UserData + MaybeSend + 'static, - { - let lua = self.lock(); - unsafe { lua.make_userdata(UserDataVariant::new(data)) } - } - - /// Creates a Lua userdata object from a custom serializable userdata type. - /// - /// Requires `feature = "serialize"` - #[cfg(feature = "serialize")] - #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] - #[inline] - pub fn create_ser_userdata(&self, data: T) -> Result - where - T: UserData + Serialize + MaybeSend + 'static, - { - let lua = self.lock(); - unsafe { lua.make_userdata(UserDataVariant::new_ser(data)) } - } - - /// Creates a Lua userdata object from a custom Rust type. - /// - /// You can register the type using [`Lua::register_userdata_type()`] to add fields or methods - /// _before_ calling this method. - /// Otherwise, the userdata object will have an empty metatable. - /// - /// All userdata instances of the same type `T` shares the same metatable. - #[inline] - pub fn create_any_userdata(&self, data: T) -> Result - where - T: MaybeSend + 'static, - { - let lua = self.lock(); - unsafe { lua.make_any_userdata(UserDataVariant::new(data)) } - } - - /// Creates a Lua userdata object from a custom serializable Rust type. - /// - /// See [`Lua::create_any_userdata()`] for more details. - /// - /// Requires `feature = "serialize"` - #[cfg(feature = "serialize")] - #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] - #[inline] - pub fn create_ser_any_userdata(&self, data: T) -> Result - where - T: Serialize + MaybeSend + 'static, - { - let lua = self.lock(); - unsafe { lua.make_any_userdata(UserDataVariant::new_ser(data)) } - } - - /// Registers a custom Rust type in Lua to use in userdata objects. - /// - /// This methods provides a way to add fields or methods to userdata objects of a type `T`. - pub fn register_userdata_type( - &self, - f: impl FnOnce(&mut UserDataRegistry), - ) -> Result<()> { - let mut registry = UserDataRegistry::new(); - f(&mut registry); - - let lua = self.lock(); - unsafe { - // Deregister the type if it already registered - let type_id = TypeId::of::(); - if let Some(&table_id) = (*lua.extra.get()).registered_userdata.get(&type_id) { - ffi::luaL_unref(lua.state(), ffi::LUA_REGISTRYINDEX, table_id); - } - - // Register the type - lua.register_userdata_metatable(registry)?; - } - Ok(()) - } - - /// Create a Lua userdata "proxy" object from a custom userdata type. - /// - /// Proxy object is an empty userdata object that has `T` metatable attached. - /// The main purpose of this object is to provide access to static fields and functions - /// without creating an instance of type `T`. - /// - /// You can get or set uservalues on this object but you cannot borrow any Rust type. - /// - /// # Examples - /// - /// ``` - /// # use mlua::{Lua, Result, UserData, UserDataFields, UserDataMethods}; - /// # fn main() -> Result<()> { - /// # let lua = Lua::new(); - /// struct MyUserData(i32); - /// - /// impl UserData for MyUserData { - /// fn add_fields<'a, F: UserDataFields<'a, Self>>(fields: &mut F) { - /// fields.add_field_method_get("val", |_, this| Ok(this.0)); - /// } - /// - /// fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { - /// methods.add_function("new", |_, value: i32| Ok(MyUserData(value))); - /// } - /// } - /// - /// lua.globals().set("MyUserData", lua.create_proxy::()?)?; - /// - /// lua.load("assert(MyUserData.new(321).val == 321)").exec()?; - /// # Ok(()) - /// # } - /// ``` - #[inline] - pub fn create_proxy(&self) -> Result - where - T: UserData + 'static, - { - let lua = self.lock(); - unsafe { lua.make_userdata(UserDataVariant::new(UserDataProxy::(PhantomData))) } - } - - /// Sets the metatable for a Luau builtin vector type. - #[cfg(any(all(feature = "luau", feature = "unstable"), doc))] - #[cfg_attr(docsrs, doc(cfg(all(feature = "luau", feature = "unstable"))))] - pub fn set_vector_metatable(&self, metatable: Option
) { - let lua = self.lock(); - unsafe { - let state = lua.state(); - let _sg = StackGuard::new(state); - assert_stack(state, 2); - - #[cfg(not(feature = "luau-vector4"))] - ffi::lua_pushvector(state, 0., 0., 0.); - #[cfg(feature = "luau-vector4")] - ffi::lua_pushvector(state, 0., 0., 0., 0.); - match metatable { - Some(metatable) => lua.push_ref(&metatable.0), - None => ffi::lua_pushnil(state), - }; - ffi::lua_setmetatable(state, -2); - } - } - - /// Returns a handle to the global environment. - pub fn globals(&self) -> Table { - let lua = self.lock(); - let state = lua.state(); - unsafe { - let _sg = StackGuard::new(state); - assert_stack(state, 1); - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] - ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS); - #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] - ffi::lua_pushvalue(state, ffi::LUA_GLOBALSINDEX); - Table(lua.pop_ref()) - } - } - - /// Returns a handle to the active `Thread`. For calls to `Lua` this will be the main Lua thread, - /// for parameters given to a callback, this will be whatever Lua thread called the callback. - pub fn current_thread(&self) -> Thread { - let lua = self.lock(); - let state = lua.state(); - unsafe { - let _sg = StackGuard::new(state); - assert_stack(state, 1); - ffi::lua_pushthread(state); - Thread::new(&lua, lua.pop_ref()) - } - } - - /// Calls the given function with a `Scope` parameter, giving the function the ability to create - /// userdata and callbacks from rust types that are !Send or non-'static. - /// - /// The lifetime of any function or userdata created through `Scope` lasts only until the - /// completion of this method call, on completion all such created values are automatically - /// dropped and Lua references to them are invalidated. If a script accesses a value created - /// through `Scope` outside of this method, a Lua error will result. Since we can ensure the - /// lifetime of values created through `Scope`, and we know that `Lua` cannot be sent to another - /// thread while `Scope` is live, it is safe to allow !Send datatypes and whose lifetimes only - /// outlive the scope lifetime. - /// - /// Inside the scope callback, all handles created through Scope will share the same unique 'lua - /// lifetime of the parent `Lua`. This allows scoped and non-scoped values to be mixed in - /// API calls, which is very useful (e.g. passing a scoped userdata to a non-scoped function). - /// However, this also enables handles to scoped values to be trivially leaked from the given - /// callback. This is not dangerous, though! After the callback returns, all scoped values are - /// invalidated, which means that though references may exist, the Rust types backing them have - /// dropped. `Function` types will error when called, and `AnyUserData` will be typeless. It - /// would be impossible to prevent handles to scoped values from escaping anyway, since you - /// would always be able to smuggle them through Lua state. - // pub fn scope<'lua, 'scope, R>( - // &'lua self, - // f: impl FnOnce(&Scope<'lua, 'scope>) -> Result, - // ) -> Result - // where - // 'lua: 'scope, - // { - // f(&Scope::new(self)) - // } - - /// Attempts to coerce a Lua value into a String in a manner consistent with Lua's internal - /// behavior. - /// - /// To succeed, the value must be a string (in which case this is a no-op), an integer, or a - /// number. - pub fn coerce_string(&self, v: Value) -> Result> { - Ok(match v { - Value::String(s) => Some(s), - v => unsafe { - let lua = self.lock(); - let state = lua.state(); - let _sg = StackGuard::new(state); - check_stack(state, 4)?; - - lua.push_value(&v)?; - let res = if lua.unlikely_memory_error() { - ffi::lua_tolstring(state, -1, ptr::null_mut()) - } else { - protect_lua!(state, 1, 1, |state| { - ffi::lua_tolstring(state, -1, ptr::null_mut()) - })? - }; - if !res.is_null() { - Some(String(lua.pop_ref())) - } else { - None - } - }, - }) - } - - /// Attempts to coerce a Lua value into an integer in a manner consistent with Lua's internal - /// behavior. - /// - /// To succeed, the value must be an integer, a floating point number that has an exact - /// representation as an integer, or a string that can be converted to an integer. Refer to the - /// Lua manual for details. - pub fn coerce_integer(&self, v: Value) -> Result> { - Ok(match v { - Value::Integer(i) => Some(i), - v => unsafe { - let lua = self.lock(); - let state = lua.state(); - let _sg = StackGuard::new(state); - check_stack(state, 2)?; - - lua.push_value(&v)?; - let mut isint = 0; - let i = ffi::lua_tointegerx(state, -1, &mut isint); - if isint == 0 { - None - } else { - Some(i) - } - }, - }) - } - - /// Attempts to coerce a Lua value into a Number in a manner consistent with Lua's internal - /// behavior. - /// - /// To succeed, the value must be a number or a string that can be converted to a number. Refer - /// to the Lua manual for details. - pub fn coerce_number(&self, v: Value) -> Result> { - Ok(match v { - Value::Number(n) => Some(n), - v => unsafe { - let lua = self.lock(); - let state = lua.state(); - let _sg = StackGuard::new(state); - check_stack(state, 2)?; - - lua.push_value(&v)?; - let mut isnum = 0; - let n = ffi::lua_tonumberx(state, -1, &mut isnum); - if isnum == 0 { - None - } else { - Some(n) - } - }, - }) - } - - /// Converts a value that implements `IntoLua` into a `Value` instance. - pub fn pack(&self, t: T) -> Result { - t.into_lua(self) - } - - /// Converts a `Value` instance into a value that implements `FromLua`. - pub fn unpack(&self, value: Value) -> Result { - T::from_lua(value, self) - } - - /// Converts a value that implements `IntoLuaMulti` into a `MultiValue` instance. - pub fn pack_multi(&self, t: T) -> Result { - t.into_lua_multi(self) - } - - /// Converts a `MultiValue` instance into a value that implements `FromLuaMulti`. - pub fn unpack_multi(&self, value: MultiValue) -> Result { - T::from_lua_multi(value, self) - } - - /// Set a value in the Lua registry based on a string name. - /// - /// This value will be available to rust from all `Lua` instances which share the same main - /// state. - pub fn set_named_registry_value(&self, name: &str, t: T) -> Result<()> - where - T: IntoLua, - { - let lua = self.lock(); - let state = lua.state(); - unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 5)?; - - lua.push(t)?; - rawset_field(state, ffi::LUA_REGISTRYINDEX, name) - } - } - - /// Get a value from the Lua registry based on a string name. - /// - /// Any Lua instance which shares the underlying main state may call this method to - /// get a value previously set by [`set_named_registry_value`]. - /// - /// [`set_named_registry_value`]: #method.set_named_registry_value - pub fn named_registry_value(&self, name: &str) -> Result - where - T: FromLua, - { - let lua = self.lock(); - let state = lua.state(); - unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 3)?; - - let protect = !lua.unlikely_memory_error(); - push_string(state, name.as_bytes(), protect)?; - ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX); - - T::from_stack(-1, &lua) - } - } - - /// Removes a named value in the Lua registry. - /// - /// Equivalent to calling [`set_named_registry_value`] with a value of Nil. - /// - /// [`set_named_registry_value`]: #method.set_named_registry_value - pub fn unset_named_registry_value(&self, name: &str) -> Result<()> { - self.set_named_registry_value(name, Nil) - } - - /// Place a value in the Lua registry with an auto-generated key. - /// - /// This value will be available to Rust from all `Lua` instances which share the same main - /// state. - /// - /// Be warned, garbage collection of values held inside the registry is not automatic, see - /// [`RegistryKey`] for more details. - /// However, dropped [`RegistryKey`]s automatically reused to store new values. - /// - /// [`RegistryKey`]: crate::RegistryKey - pub fn create_registry_value(&self, t: T) -> Result { - let lua = self.lock(); - let state = lua.state(); - unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 4)?; - - lua.push(t)?; - - let unref_list = (*lua.extra.get()).registry_unref_list.clone(); - - // Check if the value is nil (no need to store it in the registry) - if ffi::lua_isnil(state, -1) != 0 { - return Ok(RegistryKey::new(ffi::LUA_REFNIL, unref_list)); - } - - // Try to reuse previously allocated slot - let free_registry_id = unref_list.lock().as_mut().and_then(|x| x.pop()); - if let Some(registry_id) = free_registry_id { - // It must be safe to replace the value without triggering memory error - ffi::lua_rawseti(state, ffi::LUA_REGISTRYINDEX, registry_id as Integer); - return Ok(RegistryKey::new(registry_id, unref_list)); - } - - // Allocate a new RegistryKey slot - let registry_id = if lua.unlikely_memory_error() { - ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX) - } else { - protect_lua!(state, 1, 0, |state| { - ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX) - })? - }; - Ok(RegistryKey::new(registry_id, unref_list)) - } - } - - /// Get a value from the Lua registry by its `RegistryKey` - /// - /// Any Lua instance which shares the underlying main state may call this method to get a value - /// previously placed by [`create_registry_value`]. - /// - /// [`create_registry_value`]: #method.create_registry_value - pub fn registry_value(&self, key: &RegistryKey) -> Result { - let lua = self.lock(); - if !lua.owns_registry_value(key) { - return Err(Error::MismatchedRegistryKey); - } - - let state = lua.state(); - match key.id() { - ffi::LUA_REFNIL => T::from_lua(Value::Nil, self), - registry_id => unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 1)?; - - ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, registry_id as Integer); - T::from_stack(-1, &lua) - }, - } - } - - /// Removes a value from the Lua registry. - /// - /// You may call this function to manually remove a value placed in the registry with - /// [`create_registry_value`]. In addition to manual `RegistryKey` removal, you can also call - /// [`expire_registry_values`] to automatically remove values from the registry whose - /// `RegistryKey`s have been dropped. - /// - /// [`create_registry_value`]: #method.create_registry_value - /// [`expire_registry_values`]: #method.expire_registry_values - pub fn remove_registry_value(&self, key: RegistryKey) -> Result<()> { - let lua = self.lock(); - if !lua.owns_registry_value(&key) { - return Err(Error::MismatchedRegistryKey); - } - - unsafe { - ffi::luaL_unref(lua.state(), ffi::LUA_REGISTRYINDEX, key.take()); - } - Ok(()) - } - - /// Replaces a value in the Lua registry by its `RegistryKey`. - /// - /// See [`create_registry_value`] for more details. - /// - /// [`create_registry_value`]: #method.create_registry_value - pub fn replace_registry_value(&self, key: &RegistryKey, t: T) -> Result<()> { - let lua = self.lock(); - if !lua.owns_registry_value(key) { - return Err(Error::MismatchedRegistryKey); - } - - let t = t.into_lua(self)?; - - let state = lua.state(); - unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 2)?; - - match (t, key.id()) { - (Value::Nil, ffi::LUA_REFNIL) => { - // Do nothing, no need to replace nil with nil - } - (Value::Nil, registry_id) => { - // Remove the value - ffi::luaL_unref(state, ffi::LUA_REGISTRYINDEX, registry_id); - key.set_id(ffi::LUA_REFNIL); - } - (value, ffi::LUA_REFNIL) => { - // Allocate a new `RegistryKey` - let new_key = self.create_registry_value(value)?; - key.set_id(new_key.take()); - } - (value, registry_id) => { - // It must be safe to replace the value without triggering memory error - lua.push_value(&value)?; - ffi::lua_rawseti(state, ffi::LUA_REGISTRYINDEX, registry_id as Integer); - } - } - } - Ok(()) - } - - /// Returns true if the given `RegistryKey` was created by a `Lua` which shares the underlying - /// main state with this `Lua` instance. - /// - /// Other than this, methods that accept a `RegistryKey` will return - /// `Error::MismatchedRegistryKey` if passed a `RegistryKey` that was not created with a - /// matching `Lua` state. - pub fn owns_registry_value(&self, key: &RegistryKey) -> bool { - self.lock().owns_registry_value(key) - } - - /// Remove any registry values whose `RegistryKey`s have all been dropped. - /// - /// Unlike normal handle values, `RegistryKey`s do not automatically remove themselves on Drop, - /// but you can call this method to remove any unreachable registry values not manually removed - /// by `Lua::remove_registry_value`. - pub fn expire_registry_values(&self) { - let lua = self.lock(); - let state = lua.state(); - unsafe { - let mut unref_list = (*lua.extra.get()).registry_unref_list.lock(); - let unref_list = mem::replace(&mut *unref_list, Some(Vec::new())); - for id in mlua_expect!(unref_list, "unref list not set") { - ffi::luaL_unref(state, ffi::LUA_REGISTRYINDEX, id); - } - } - } - - /// Sets or replaces an application data object of type `T`. - /// - /// Application data could be accessed at any time by using [`Lua::app_data_ref()`] or [`Lua::app_data_mut()`] - /// methods where `T` is the data type. - /// - /// # Panics - /// - /// Panics if the app data container is currently borrowed. - /// - /// # Examples - /// - /// ``` - /// use mlua::{Lua, Result}; - /// - /// fn hello(lua: &Lua, _: ()) -> Result<()> { - /// let mut s = lua.app_data_mut::<&str>().unwrap(); - /// assert_eq!(*s, "hello"); - /// *s = "world"; - /// Ok(()) - /// } - /// - /// fn main() -> Result<()> { - /// let lua = Lua::new(); - /// lua.set_app_data("hello"); - /// lua.create_function(hello)?.call(())?; - /// let s = lua.app_data_ref::<&str>().unwrap(); - /// assert_eq!(*s, "world"); - /// Ok(()) - /// } - /// ``` - #[track_caller] - pub fn set_app_data(&self, data: T) -> Option { - let lua = self.lock(); - let extra = unsafe { &*lua.extra.get() }; - extra.app_data.insert(data) - } - - /// Tries to set or replace an application data object of type `T`. - /// - /// Returns: - /// - `Ok(Some(old_data))` if the data object of type `T` was successfully replaced. - /// - `Ok(None)` if the data object of type `T` was successfully inserted. - /// - `Err(data)` if the data object of type `T` was not inserted because the container is currently borrowed. - /// - /// See [`Lua::set_app_data()`] for examples. - pub fn try_set_app_data(&self, data: T) -> StdResult, T> { - let lua = self.lock(); - let extra = unsafe { &*lua.extra.get() }; - extra.app_data.try_insert(data) - } - - /// Gets a reference to an application data object stored by [`Lua::set_app_data()`] of type `T`. - /// - /// # Panics - /// - /// Panics if the data object of type `T` is currently mutably borrowed. Multiple immutable reads - /// can be taken out at the same time. - #[track_caller] - pub fn app_data_ref(&self) -> Option> { - let guard = self.lock_arc(); - let extra = unsafe { &*guard.extra.get() }; - extra.app_data.borrow(Some(guard)) - } - - /// Gets a mutable reference to an application data object stored by [`Lua::set_app_data()`] of type `T`. - /// - /// # Panics - /// - /// Panics if the data object of type `T` is currently borrowed. - #[track_caller] - pub fn app_data_mut(&self) -> Option> { - let guard = self.lock_arc(); - let extra = unsafe { &*guard.extra.get() }; - extra.app_data.borrow_mut(Some(guard)) - } - - /// Removes an application data of type `T`. - /// - /// # Panics - /// - /// Panics if the app data container is currently borrowed. - #[track_caller] - pub fn remove_app_data(&self) -> Option { - let lua = self.lock(); - let extra = unsafe { &*lua.extra.get() }; - extra.app_data.remove() - } - - // FIXME - // /// Pushes a value that implements `IntoLua` onto the Lua stack. - // /// - // /// Uses 2 stack spaces, does not call checkstack. - // #[doc(hidden)] - // #[inline(always)] - // pub unsafe fn push(&self, value: impl IntoLua) -> Result<()> { - // // value.push_into_stack(self) - // } - - /// Returns internal `Poll::Pending` constant used for executing async callbacks. - #[cfg(feature = "async")] - #[doc(hidden)] - #[inline] - pub fn poll_pending() -> LightUserData { - LightUserData(&ASYNC_POLL_PENDING as *const u8 as *mut c_void) - } - - // Luau version located in `luau/mod.rs` - #[cfg(not(feature = "luau"))] - fn disable_c_modules(&self) -> Result<()> { - let package: Table = self.globals().get("package")?; - - package.set( - "loadlib", - self.create_function(|_, ()| -> Result<()> { - Err(Error::SafetyError( - "package.loadlib is disabled in safe mode".to_string(), - )) - })?, - )?; - - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] - let searchers: Table = package.get("searchers")?; - #[cfg(any(feature = "lua51", feature = "luajit"))] - let searchers: Table = package.get("loaders")?; - - let loader = self.create_function(|_, ()| Ok("\n\tcan't load C modules in safe mode"))?; - - // The third and fourth searchers looks for a loader as a C library - searchers.raw_set(3, loader)?; - searchers.raw_remove(4)?; - - Ok(()) - } - - pub(crate) unsafe fn try_from_ptr(state: *mut ffi::lua_State) -> Option { - let extra = extra_data(state); - if extra.is_null() { - return None; - } - Some(Lua(Arc::clone((*extra).inner.assume_init_ref()))) - } - - #[inline(always)] - pub(crate) fn lock(&self) -> ReentrantMutexGuard { - self.0.lock() - } - - #[inline(always)] - pub(crate) fn lock_arc(&self) -> LuaGuard { - LuaGuard(self.0.lock_arc()) - } - - #[inline(always)] - pub(crate) unsafe fn guard_unchecked(&self) -> ManuallyDrop> { - ManuallyDrop::new(self.0.make_guard_unchecked()) - } - - #[inline(always)] - pub(crate) fn weak(&self) -> WeakLua { - WeakLua(Arc::downgrade(&self.0)) - } -} - -impl LuaInner { - #[inline(always)] - pub(crate) fn lua(&self) -> &Lua { - unsafe { (*self.extra.get()).lua() } - } - - #[inline(always)] - pub(crate) fn weak(&self) -> &WeakLua { - unsafe { (*self.extra.get()).weak() } - } - - #[inline(always)] - pub(crate) fn state(&self) -> *mut ffi::lua_State { - self.state.get() - } - - #[cfg(feature = "luau")] - #[inline(always)] - pub(crate) fn main_state(&self) -> *mut ffi::lua_State { - self.main_state - } - - #[inline(always)] - pub(crate) fn ref_thread(&self) -> *mut ffi::lua_State { - unsafe { (*self.extra.get()).ref_thread } - } - - /// See [`Lua::try_set_app_data`] - pub(crate) fn try_set_app_data( - &self, - data: T, - ) -> StdResult, T> { - let extra = unsafe { &*self.extra.get() }; - extra.app_data.try_insert(data) - } - - /// See [`Lua::app_data_ref`] - #[track_caller] - pub(crate) fn app_data_ref(&self) -> Option> { - let extra = unsafe { &*self.extra.get() }; - extra.app_data.borrow(None) - } - - /// See [`Lua::app_data_mut`] - #[track_caller] - pub(crate) fn app_data_mut(&self) -> Option> { - let extra = unsafe { &*self.extra.get() }; - extra.app_data.borrow_mut(None) - } - - /// See [`Lua::create_registry_value`] - pub(crate) fn owns_registry_value(&self, key: &RegistryKey) -> bool { - let registry_unref_list = unsafe { &(*self.extra.get()).registry_unref_list }; - Arc::ptr_eq(&key.unref_list, registry_unref_list) - } - - pub(crate) fn load_chunk( - &self, - name: Option<&CStr>, - env: Option
, - mode: Option, - source: &[u8], - ) -> Result { - let state = self.state(); - unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 1)?; - - let mode_str = match mode { - Some(ChunkMode::Binary) => cstr!("b"), - Some(ChunkMode::Text) => cstr!("t"), - None => cstr!("bt"), - }; - - match ffi::luaL_loadbufferx( - state, - source.as_ptr() as *const c_char, - source.len(), - name.map(|n| n.as_ptr()).unwrap_or_else(ptr::null), - mode_str, - ) { - ffi::LUA_OK => { - if let Some(env) = env { - self.push_ref(&env.0); - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] - ffi::lua_setupvalue(state, -2, 1); - #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] - ffi::lua_setfenv(state, -2); - } - - #[cfg(feature = "luau-jit")] - if (*self.extra.get()).enable_jit && ffi::luau_codegen_supported() != 0 { - ffi::luau_codegen_compile(state, -1); - } - - Ok(Function(self.pop_ref())) - } - err => Err(pop_error(state, err)), - } - } - } - - /// Sets a 'hook' function for a thread (coroutine). - #[cfg(not(feature = "luau"))] - pub(crate) unsafe fn set_thread_hook( - &self, - state: *mut ffi::lua_State, - triggers: HookTriggers, - callback: F, - ) where - F: Fn(&Lua, Debug) -> Result<()> + MaybeSend + 'static, - { - unsafe extern "C-unwind" fn hook_proc(state: *mut ffi::lua_State, ar: *mut ffi::lua_Debug) { - let extra = extra_data(state); - if (*extra).hook_thread != state { - // Hook was destined for a different thread, ignore - ffi::lua_sethook(state, None, 0, 0); - return; - } - callback_error_ext(state, extra, move |_| { - let hook_cb = (*extra).hook_callback.clone(); - let hook_cb = mlua_expect!(hook_cb, "no hook callback set in hook_proc"); - if Arc::strong_count(&hook_cb) > 2 { - return Ok(()); // Don't allow recursion - } - let lua = (*extra).lua(); - let rawlua = lua.lock(); - let _guard = StateGuard::new(&rawlua, state); - let debug = Debug::new(lua, ar); - hook_cb(lua, debug) - }) - } - - (*self.extra.get()).hook_callback = Some(Arc::new(callback)); - (*self.extra.get()).hook_thread = state; // Mark for what thread the hook is set - ffi::lua_sethook(state, Some(hook_proc), triggers.mask(), triggers.count()); - } - - /// Wraps a Lua function into a new thread (or coroutine). - /// - /// Takes function by reference. - fn create_thread_inner(&self, func: &Function) -> Result { - let state = self.state(); - unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 3)?; - - let thread_state = if self.unlikely_memory_error() { - ffi::lua_newthread(state) - } else { - protect_lua!(state, 0, 1, |state| ffi::lua_newthread(state))? - }; - self.push_ref(&func.0); - ffi::lua_xmove(state, thread_state, 1); - - Ok(Thread::new(&self, self.pop_ref())) - } - } - - /// Wraps a Lua function into a new or recycled thread (coroutine). - #[cfg(feature = "async")] - pub(crate) fn create_recycled_thread(&self, func: &Function) -> Result { - #[cfg(any(feature = "lua54", feature = "luau"))] - unsafe { - let state = self.state(); - let _sg = StackGuard::new(state); - check_stack(state, 1)?; - - if let Some(index) = (*self.extra.get()).thread_pool.pop() { - let thread_state = ffi::lua_tothread(self.ref_thread(), index); - self.push_ref(&func.0); - ffi::lua_xmove(state, thread_state, 1); - - #[cfg(feature = "luau")] - { - // Inherit `LUA_GLOBALSINDEX` from the caller - ffi::lua_xpush(state, thread_state, ffi::LUA_GLOBALSINDEX); - ffi::lua_replace(thread_state, ffi::LUA_GLOBALSINDEX); - } - - return Ok(Thread::new(self, ValueRef::new(self, index))); - } - }; - self.create_thread_inner(func) - } - - /// Resets thread (coroutine) and returns to the pool for later use. - #[cfg(feature = "async")] - #[cfg(any(feature = "lua54", feature = "luau"))] - pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) -> bool { - let extra = &mut *self.extra.get(); - if extra.thread_pool.len() < extra.thread_pool.capacity() { - let thread_state = ffi::lua_tothread(extra.ref_thread, thread.0.index); - #[cfg(all(feature = "lua54", not(feature = "vendored")))] - let status = ffi::lua_resetthread(thread_state); - #[cfg(all(feature = "lua54", feature = "vendored"))] - let status = ffi::lua_closethread(thread_state, self.state()); - #[cfg(feature = "lua54")] - if status != ffi::LUA_OK { - // Error object is on top, drop it - ffi::lua_settop(thread_state, 0); - } - #[cfg(feature = "luau")] - ffi::lua_resetthread(thread_state); - extra.thread_pool.push(thread.0.index); - thread.0.drop = false; - return true; - } - false - } - - // FIXME - // #[inline] - // pub(crate) fn pop_multivalue_from_pool(&self) -> Option> { - // let extra = unsafe { &mut *self.extra.get() }; - // extra.multivalue_pool.pop() - // } - - // FIXME - // #[inline] - // pub(crate) fn push_multivalue_to_pool(&self, mut multivalue: VecDeque) { - // let extra = unsafe { &mut *self.extra.get() }; - // if extra.multivalue_pool.len() < MULTIVALUE_POOL_SIZE { - // multivalue.clear(); - // extra - // .multivalue_pool - // .push(unsafe { mem::transmute(multivalue) }); - // } - // } - - /// Pushes a value that implements `IntoLua` onto the Lua stack. - /// - /// Uses 2 stack spaces, does not call checkstack. - #[doc(hidden)] - #[inline(always)] - pub unsafe fn push(&self, value: impl IntoLua) -> Result<()> { - value.push_into_stack(self) - } - - /// Pushes a `Value` (by reference) onto the Lua stack. - /// - /// Uses 2 stack spaces, does not call `checkstack`. - pub(crate) unsafe fn push_value(&self, value: &Value) -> Result<()> { - let state = self.state(); - match value { - Value::Nil => ffi::lua_pushnil(state), - Value::Boolean(b) => ffi::lua_pushboolean(state, *b as c_int), - Value::LightUserData(ud) => ffi::lua_pushlightuserdata(state, ud.0), - Value::Integer(i) => ffi::lua_pushinteger(state, *i), - Value::Number(n) => ffi::lua_pushnumber(state, *n), - #[cfg(feature = "luau")] - Value::Vector(v) => { - #[cfg(not(feature = "luau-vector4"))] - ffi::lua_pushvector(state, v.x(), v.y(), v.z()); - #[cfg(feature = "luau-vector4")] - ffi::lua_pushvector(state, v.x(), v.y(), v.z(), v.w()); - } - Value::String(s) => self.push_ref(&s.0), - Value::Table(t) => self.push_ref(&t.0), - Value::Function(f) => self.push_ref(&f.0), - Value::Thread(t) => self.push_ref(&t.0), - Value::UserData(ud) => self.push_ref(&ud.0), - Value::Error(err) => { - let protect = !self.unlikely_memory_error(); - push_gc_userdata(state, WrappedFailure::Error(*err.clone()), protect)?; - } - } - Ok(()) - } - - /// Pops a value from the Lua stack. - /// - /// Uses 2 stack spaces, does not call checkstack. - #[doc(hidden)] - pub(crate) unsafe fn pop_value(&self) -> Value { - let state = self.state(); - match ffi::lua_type(state, -1) { - ffi::LUA_TNIL => { - ffi::lua_pop(state, 1); - Nil - } - - ffi::LUA_TBOOLEAN => { - let b = Value::Boolean(ffi::lua_toboolean(state, -1) != 0); - ffi::lua_pop(state, 1); - b - } - - ffi::LUA_TLIGHTUSERDATA => { - let ud = Value::LightUserData(LightUserData(ffi::lua_touserdata(state, -1))); - ffi::lua_pop(state, 1); - ud - } - - #[cfg(any(feature = "lua54", feature = "lua53"))] - ffi::LUA_TNUMBER => { - let v = if ffi::lua_isinteger(state, -1) != 0 { - Value::Integer(ffi::lua_tointeger(state, -1)) - } else { - Value::Number(ffi::lua_tonumber(state, -1)) - }; - ffi::lua_pop(state, 1); - v - } - - #[cfg(any( - feature = "lua52", - feature = "lua51", - feature = "luajit", - feature = "luau" - ))] - ffi::LUA_TNUMBER => { - let n = ffi::lua_tonumber(state, -1); - ffi::lua_pop(state, 1); - match num_traits::cast(n) { - Some(i) if (n - (i as Number)).abs() < Number::EPSILON => Value::Integer(i), - _ => Value::Number(n), - } - } - - #[cfg(feature = "luau")] - ffi::LUA_TVECTOR => { - let v = ffi::lua_tovector(state, -1); - mlua_debug_assert!(!v.is_null(), "vector is null"); - #[cfg(not(feature = "luau-vector4"))] - let vec = Value::Vector(Vector([*v, *v.add(1), *v.add(2)])); - #[cfg(feature = "luau-vector4")] - let vec = Value::Vector(Vector([*v, *v.add(1), *v.add(2), *v.add(3)])); - ffi::lua_pop(state, 1); - vec - } - - ffi::LUA_TSTRING => Value::String(String(self.pop_ref())), - - ffi::LUA_TTABLE => Value::Table(Table(self.pop_ref())), - - ffi::LUA_TFUNCTION => Value::Function(Function(self.pop_ref())), - - ffi::LUA_TUSERDATA => { - let wrapped_failure_mt_ptr = (*self.extra.get()).wrapped_failure_mt_ptr; - // We must prevent interaction with userdata types other than UserData OR a WrappedError. - // WrappedPanics are automatically resumed. - match get_gc_userdata::(state, -1, wrapped_failure_mt_ptr).as_mut() - { - Some(WrappedFailure::Error(err)) => { - let err = err.clone(); - ffi::lua_pop(state, 1); - Value::Error(Box::new(err)) - } - Some(WrappedFailure::Panic(panic)) => { - if let Some(panic) = panic.take() { - ffi::lua_pop(state, 1); - resume_unwind(panic); - } - // Previously resumed panic? - ffi::lua_pop(state, 1); - Nil - } - _ => Value::UserData(AnyUserData(self.pop_ref(), SubtypeId::None)), - } - } - - ffi::LUA_TTHREAD => Value::Thread(Thread::new(self, self.pop_ref())), - - #[cfg(feature = "luau")] - ffi::LUA_TBUFFER => { - // Buffer is represented as a userdata type - Value::UserData(AnyUserData(self.pop_ref(), SubtypeId::Buffer)) - } - - #[cfg(feature = "luajit")] - ffi::LUA_TCDATA => { - // CDATA is represented as a userdata type - Value::UserData(AnyUserData(self.pop_ref(), SubtypeId::CData)) - } - - _ => mlua_panic!("LUA_TNONE in pop_value"), - } - } - - /// Returns value at given stack index without popping it. - /// - /// Uses 2 stack spaces, does not call checkstack. - pub(crate) unsafe fn stack_value(&self, idx: c_int) -> Value { - let state = self.state(); - match ffi::lua_type(state, idx) { - ffi::LUA_TNIL => Nil, - - ffi::LUA_TBOOLEAN => Value::Boolean(ffi::lua_toboolean(state, idx) != 0), - - ffi::LUA_TLIGHTUSERDATA => { - Value::LightUserData(LightUserData(ffi::lua_touserdata(state, idx))) - } - - #[cfg(any(feature = "lua54", feature = "lua53"))] - ffi::LUA_TNUMBER => { - if ffi::lua_isinteger(state, idx) != 0 { - Value::Integer(ffi::lua_tointeger(state, idx)) - } else { - Value::Number(ffi::lua_tonumber(state, idx)) - } - } - - #[cfg(any( - feature = "lua52", - feature = "lua51", - feature = "luajit", - feature = "luau" - ))] - ffi::LUA_TNUMBER => { - let n = ffi::lua_tonumber(state, idx); - match num_traits::cast(n) { - Some(i) if (n - (i as Number)).abs() < Number::EPSILON => Value::Integer(i), - _ => Value::Number(n), - } - } - - #[cfg(feature = "luau")] - ffi::LUA_TVECTOR => { - let v = ffi::lua_tovector(state, idx); - mlua_debug_assert!(!v.is_null(), "vector is null"); - #[cfg(not(feature = "luau-vector4"))] - return Value::Vector(Vector([*v, *v.add(1), *v.add(2)])); - #[cfg(feature = "luau-vector4")] - return Value::Vector(Vector([*v, *v.add(1), *v.add(2), *v.add(3)])); - } - - ffi::LUA_TSTRING => { - ffi::lua_xpush(state, self.ref_thread(), idx); - Value::String(String(self.pop_ref_thread())) - } - - ffi::LUA_TTABLE => { - ffi::lua_xpush(state, self.ref_thread(), idx); - Value::Table(Table(self.pop_ref_thread())) - } - - ffi::LUA_TFUNCTION => { - ffi::lua_xpush(state, self.ref_thread(), idx); - Value::Function(Function(self.pop_ref_thread())) - } - - ffi::LUA_TUSERDATA => { - let wrapped_failure_mt_ptr = (*self.extra.get()).wrapped_failure_mt_ptr; - // We must prevent interaction with userdata types other than UserData OR a WrappedError. - // WrappedPanics are automatically resumed. - match get_gc_userdata::(state, idx, wrapped_failure_mt_ptr).as_mut() - { - Some(WrappedFailure::Error(err)) => Value::Error(Box::new(err.clone())), - Some(WrappedFailure::Panic(panic)) => { - if let Some(panic) = panic.take() { - resume_unwind(panic); - } - // Previously resumed panic? - Value::Nil - } - _ => { - ffi::lua_xpush(state, self.ref_thread(), idx); - Value::UserData(AnyUserData(self.pop_ref_thread(), SubtypeId::None)) - } - } - } - - ffi::LUA_TTHREAD => { - ffi::lua_xpush(state, self.ref_thread(), idx); - Value::Thread(Thread::new(self, self.pop_ref_thread())) - } - - #[cfg(feature = "luau")] - ffi::LUA_TBUFFER => { - // Buffer is represented as a userdata type - ffi::lua_xpush(state, self.ref_thread(), idx); - Value::UserData(AnyUserData(self.pop_ref_thread(), SubtypeId::Buffer)) - } - - #[cfg(feature = "luajit")] - ffi::LUA_TCDATA => { - // CData is represented as a userdata type - ffi::lua_xpush(state, self.ref_thread(), idx); - Value::UserData(AnyUserData(self.pop_ref_thread(), SubtypeId::CData)) - } - - _ => mlua_panic!("LUA_TNONE in pop_value"), - } - } - - // Pushes a ValueRef value onto the stack, uses 1 stack space, does not call checkstack - pub(crate) fn push_ref(&self, vref: &ValueRef) { - assert!( - self.weak() == &vref.lua, - "Lua instance passed Value created from a different main Lua state" - ); - unsafe { ffi::lua_xpush(self.ref_thread(), self.state(), vref.index) }; - } - - // Pops the topmost element of the stack and stores a reference to it. This pins the object, - // preventing garbage collection until the returned `ValueRef` is dropped. - // - // References are stored in the stack of a specially created auxiliary thread that exists only - // to store reference values. This is much faster than storing these in the registry, and also - // much more flexible and requires less bookkeeping than storing them directly in the currently - // used stack. The implementation is somewhat biased towards the use case of a relatively small - // number of short term references being created, and `RegistryKey` being used for long term - // references. - pub(crate) unsafe fn pop_ref(&self) -> ValueRef { - ffi::lua_xmove(self.state(), self.ref_thread(), 1); - let index = ref_stack_pop(self.extra.get()); - ValueRef::new(self, index) - } - - // Same as `pop_ref` but assumes the value is already on the reference thread - pub(crate) unsafe fn pop_ref_thread(&self) -> ValueRef { - let index = ref_stack_pop(self.extra.get()); - ValueRef::new(self, index) - } - - pub(crate) fn clone_ref(&self, vref: &ValueRef) -> ValueRef { - unsafe { - ffi::lua_pushvalue(self.ref_thread(), vref.index); - let index = ref_stack_pop(self.extra.get()); - ValueRef::new(self, index) - } - } - - pub(crate) fn drop_ref(&self, vref: &ValueRef) { - unsafe { - let ref_thread = self.ref_thread(); - ffi::lua_pushnil(ref_thread); - ffi::lua_replace(ref_thread, vref.index); - (*self.extra.get()).ref_free.push(vref.index); - } - } - - #[inline] - pub(crate) unsafe fn push_error_traceback(&self) { - let state = self.state(); - #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] - ffi::lua_xpush(self.ref_thread(), state, ExtraData::ERROR_TRACEBACK_IDX); - // Lua 5.2+ support light C functions that does not require extra allocations - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] - ffi::lua_pushcfunction(state, error_traceback); - } - - #[inline] - pub(crate) unsafe fn unlikely_memory_error(&self) -> bool { - // MemoryInfo is empty in module mode so we cannot predict memory limits - match MemoryState::get(self.main_state) { - mem_state if !mem_state.is_null() => (*mem_state).memory_limit() == 0, - #[cfg(feature = "module")] - _ => (*self.extra.get()).skip_memory_check, // Check the special flag (only for module mode) - #[cfg(not(feature = "module"))] - _ => false, - } - } - - pub(crate) unsafe fn make_userdata(&self, data: UserDataVariant) -> Result - where - T: UserData + 'static, - { - self.make_userdata_with_metatable(data, || { - // Check if userdata/metatable is already registered - let type_id = TypeId::of::(); - if let Some(&table_id) = (*self.extra.get()).registered_userdata.get(&type_id) { - return Ok(table_id as Integer); - } - - // Create new metatable from UserData definition - let mut registry = UserDataRegistry::new(); - T::register(&mut registry); - - self.register_userdata_metatable(registry) - }) - } - - pub(crate) unsafe fn make_any_userdata( - &self, - data: UserDataVariant, - ) -> Result - where - T: 'static, - { - self.make_userdata_with_metatable(data, || { - // Check if userdata/metatable is already registered - let type_id = TypeId::of::(); - if let Some(&table_id) = (*self.extra.get()).registered_userdata.get(&type_id) { - return Ok(table_id as Integer); - } - - // Create empty metatable - let registry = UserDataRegistry::new(); - self.register_userdata_metatable::(registry) - }) - } - - unsafe fn make_userdata_with_metatable( - &self, - data: UserDataVariant, - get_metatable_id: impl FnOnce() -> Result, - ) -> Result { - let state = self.state(); - let _sg = StackGuard::new(state); - check_stack(state, 3)?; - - // We push metatable first to ensure having correct metatable with `__gc` method - ffi::lua_pushnil(state); - ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, get_metatable_id()?); - let protect = !self.unlikely_memory_error(); - #[cfg(not(feature = "lua54"))] - push_userdata(state, data, protect)?; - #[cfg(feature = "lua54")] - push_userdata_uv(state, data, USER_VALUE_MAXSLOT as c_int, protect)?; - ffi::lua_replace(state, -3); - ffi::lua_setmetatable(state, -2); - - // Set empty environment for Lua 5.1 - #[cfg(any(feature = "lua51", feature = "luajit"))] - if protect { - protect_lua!(state, 1, 1, fn(state) { - ffi::lua_newtable(state); - ffi::lua_setuservalue(state, -2); - })?; - } else { - ffi::lua_newtable(state); - ffi::lua_setuservalue(state, -2); - } - - Ok(AnyUserData(self.pop_ref(), SubtypeId::None)) - } - - unsafe fn register_userdata_metatable( - &self, - mut registry: UserDataRegistry, - ) -> Result { - let state = self.state(); - let _sg = StackGuard::new(state); - check_stack(state, 13)?; - - // Prepare metatable, add meta methods first and then meta fields - let metatable_nrec = registry.meta_methods.len() + registry.meta_fields.len(); - #[cfg(feature = "async")] - let metatable_nrec = metatable_nrec + registry.async_meta_methods.len(); - push_table(state, 0, metatable_nrec, true)?; - for (k, m) in registry.meta_methods { - self.push(self.create_callback(m)?)?; - rawset_field(state, -2, MetaMethod::validate(&k)?)?; - } - #[cfg(feature = "async")] - for (k, m) in registry.async_meta_methods { - self.push(self.create_async_callback(m)?)?; - rawset_field(state, -2, MetaMethod::validate(&k)?)?; - } - let mut has_name = false; - for (k, f) in registry.meta_fields { - has_name = has_name || k == MetaMethod::Type; - let inner = mem::transmute::<&LuaInner, &LuaInner>(self); - mlua_assert!(f(inner, 0)? == 1, "field function must return one value"); - rawset_field(state, -2, MetaMethod::validate(&k)?)?; - } - // Set `__name/__type` if not provided - if !has_name { - let type_name = short_type_name::(); - push_string(state, type_name.as_bytes(), !self.unlikely_memory_error())?; - rawset_field(state, -2, MetaMethod::Type.name())?; - } - let metatable_index = ffi::lua_absindex(state, -1); - - let mut extra_tables_count = 0; - - let fields_nrec = registry.fields.len(); - if fields_nrec > 0 { - // If __index is a table then update it inplace - let index_type = ffi::lua_getfield(state, metatable_index, cstr!("__index")); - match index_type { - ffi::LUA_TNIL | ffi::LUA_TTABLE => { - if index_type == ffi::LUA_TNIL { - // Create a new table - ffi::lua_pop(state, 1); - push_table(state, 0, fields_nrec, true)?; - } - for (k, f) in registry.fields { - let inner = mem::transmute::<&LuaInner, &LuaInner>(self); - mlua_assert!(f(inner, 0)? == 1, "field function must return one value"); - rawset_field(state, -2, &k)?; - } - rawset_field(state, metatable_index, "__index")?; - } - _ => { - ffi::lua_pop(state, 1); - // Propagate fields to the field getters - for (k, f) in registry.fields { - registry.field_getters.push((k, f)) - } - } - } - } - - let mut field_getters_index = None; - let field_getters_nrec = registry.field_getters.len(); - if field_getters_nrec > 0 { - push_table(state, 0, field_getters_nrec, true)?; - for (k, m) in registry.field_getters { - self.push(self.create_callback(m)?)?; - rawset_field(state, -2, &k)?; - } - field_getters_index = Some(ffi::lua_absindex(state, -1)); - extra_tables_count += 1; - } - - let mut field_setters_index = None; - let field_setters_nrec = registry.field_setters.len(); - if field_setters_nrec > 0 { - push_table(state, 0, field_setters_nrec, true)?; - for (k, m) in registry.field_setters { - self.push(self.create_callback(m)?)?; - rawset_field(state, -2, &k)?; - } - field_setters_index = Some(ffi::lua_absindex(state, -1)); - extra_tables_count += 1; - } - - let mut methods_index = None; - let methods_nrec = registry.methods.len(); - #[cfg(feature = "async")] - let methods_nrec = methods_nrec + registry.async_methods.len(); - if methods_nrec > 0 { - // If __index is a table then update it inplace - let index_type = ffi::lua_getfield(state, metatable_index, cstr!("__index")); - match index_type { - ffi::LUA_TTABLE => {} // Update the existing table - _ => { - // Create a new table - ffi::lua_pop(state, 1); - push_table(state, 0, methods_nrec, true)?; - } - } - for (k, m) in registry.methods { - self.push(self.create_callback(m)?)?; - rawset_field(state, -2, &k)?; - } - #[cfg(feature = "async")] - for (k, m) in registry.async_methods { - self.push(self.create_async_callback(m)?)?; - rawset_field(state, -2, &k)?; - } - match index_type { - ffi::LUA_TTABLE => { - ffi::lua_pop(state, 1); // All done - } - ffi::LUA_TNIL => { - rawset_field(state, metatable_index, "__index")?; // Set the new table as __index - } - _ => { - methods_index = Some(ffi::lua_absindex(state, -1)); - extra_tables_count += 1; - } - } - } - - #[cfg(feature = "luau")] - let extra_init = None; - #[cfg(not(feature = "luau"))] - let extra_init: Option Result<()>> = Some(|state| { - ffi::lua_pushcfunction(state, util::userdata_destructor::>); - rawset_field(state, -2, "__gc") - }); - - init_userdata_metatable( - state, - metatable_index, - field_getters_index, - field_setters_index, - methods_index, - extra_init, - )?; - - // Pop extra tables to get metatable on top of the stack - ffi::lua_pop(state, extra_tables_count); - - let mt_ptr = ffi::lua_topointer(state, -1); - let id = protect_lua!(state, 1, 0, |state| { - ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX) - })?; - - let type_id = TypeId::of::(); - (*self.extra.get()).registered_userdata.insert(type_id, id); - (*self.extra.get()) - .registered_userdata_mt - .insert(mt_ptr, Some(type_id)); - - Ok(id as Integer) - } - - #[inline] - pub(crate) unsafe fn register_raw_userdata_metatable( - &self, - ptr: *const c_void, - type_id: Option, - ) { - (*self.extra.get()) - .registered_userdata_mt - .insert(ptr, type_id); - } - - #[inline] - pub(crate) unsafe fn deregister_raw_userdata_metatable(&self, ptr: *const c_void) { - (*self.extra.get()).registered_userdata_mt.remove(&ptr); - if (*self.extra.get()).last_checked_userdata_mt.0 == ptr { - (*self.extra.get()).last_checked_userdata_mt = (ptr::null(), None); - } - } - - #[inline(always)] - pub(crate) unsafe fn get_userdata_ref(&self, idx: c_int) -> Result> { - let guard = self.lua().lock_arc(); - (*get_userdata::>(self.state(), idx)).try_make_ref(guard) - } - - // Returns `TypeId` for the userdata ref, checking that it's registered and not destructed. - // - // Returns `None` if the userdata is registered but non-static. - pub(crate) unsafe fn get_userdata_ref_type_id( - &self, - vref: &ValueRef, - ) -> Result> { - self.get_userdata_type_id_inner(self.ref_thread(), vref.index) - } - - // Same as `get_userdata_ref_type_id` but assumes the userdata is already on the stack. - pub(crate) unsafe fn get_userdata_type_id(&self, idx: c_int) -> Result> { - self.get_userdata_type_id_inner(self.state(), idx) - } - - unsafe fn get_userdata_type_id_inner( - &self, - state: *mut ffi::lua_State, - idx: c_int, - ) -> Result> { - if ffi::lua_getmetatable(state, idx) == 0 { - return Err(Error::UserDataTypeMismatch); - } - let mt_ptr = ffi::lua_topointer(state, -1); - ffi::lua_pop(state, 1); - - // Fast path to skip looking up the metatable in the map - let (last_mt, last_type_id) = (*self.extra.get()).last_checked_userdata_mt; - if last_mt == mt_ptr { - return Ok(last_type_id); - } - - match (*self.extra.get()).registered_userdata_mt.get(&mt_ptr) { - Some(&type_id) if type_id == Some(TypeId::of::()) => { - Err(Error::UserDataDestructed) - } - Some(&type_id) => { - (*self.extra.get()).last_checked_userdata_mt = (mt_ptr, type_id); - Ok(type_id) - } - None => Err(Error::UserDataTypeMismatch), - } - } - - // Pushes a ValueRef (userdata) value onto the stack, returning their `TypeId`. - // Uses 1 stack space, does not call checkstack. - pub(crate) unsafe fn push_userdata_ref(&self, vref: &ValueRef) -> Result> { - let type_id = self.get_userdata_type_id_inner(self.ref_thread(), vref.index)?; - self.push_ref(vref); - Ok(type_id) - } - - // Creates a Function out of a Callback containing a 'static Fn. - pub(crate) fn create_callback(&self, func: Callback) -> Result { - unsafe extern "C-unwind" fn call_callback(state: *mut ffi::lua_State) -> c_int { - // Normal functions can be scoped and therefore destroyed, - // so we need to check that the first upvalue is valid - let (upvalue, extra) = match ffi::lua_type(state, ffi::lua_upvalueindex(1)) { - ffi::LUA_TUSERDATA => { - let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); - (upvalue, (*upvalue).extra.get()) - } - _ => (ptr::null_mut(), ptr::null_mut()), - }; - callback_error_ext(state, extra, |nargs| { - // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) - if upvalue.is_null() { - return Err(Error::CallbackDestructed); - } - - let lua = (*extra).lua().lock(); - let _guard = StateGuard::new(&lua, state); - let func = &*(*upvalue).data; - - func(mem::transmute::<&LuaInner, &LuaInner>(&lua), nargs) - }) - } - - let state = self.state(); - unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 4)?; - - let func = mem::transmute(func); - let extra = Arc::clone(&self.extra); - let protect = !self.unlikely_memory_error(); - push_gc_userdata(state, CallbackUpvalue { data: func, extra }, protect)?; - if protect { - protect_lua!(state, 1, 1, fn(state) { - ffi::lua_pushcclosure(state, call_callback, 1); - })?; - } else { - ffi::lua_pushcclosure(state, call_callback, 1); - } - - Ok(Function(self.pop_ref())) - } - } - - #[cfg(feature = "async")] - pub(crate) fn create_async_callback(&self, func: AsyncCallback) -> Result { - #[cfg(any( - feature = "lua54", - feature = "lua53", - feature = "lua52", - feature = "luau" - ))] - unsafe { - if !(*self.extra.get()).libs.contains(StdLib::COROUTINE) { - load_from_std_lib(self.main_state, StdLib::COROUTINE)?; - (*self.extra.get()).libs |= StdLib::COROUTINE; - } - } - - unsafe extern "C-unwind" fn call_callback(state: *mut ffi::lua_State) -> c_int { - // Async functions cannot be scoped and therefore destroyed, - // so the first upvalue is always valid - let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); - let extra = (*upvalue).extra.get(); - callback_error_ext(state, extra, |nargs| { - // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) - let lua = (*extra).lua(); - // The lock must be already held as the callback is executed - let rawlua = lua.guard_unchecked(); - let rawlua = mem::transmute::<&LuaInner, &LuaInner>(&rawlua); - let _guard = StateGuard::new(rawlua, state); - - let args = MultiValue::from_stack_multi(nargs, rawlua)?; - let func = &*(*upvalue).data; - let fut = func(rawlua, args); - let extra = Arc::clone(&(*upvalue).extra); - let protect = !rawlua.unlikely_memory_error(); - push_gc_userdata(state, AsyncPollUpvalue { data: fut, extra }, protect)?; - if protect { - protect_lua!(state, 1, 1, fn(state) { - ffi::lua_pushcclosure(state, poll_future, 1); - })?; - } else { - ffi::lua_pushcclosure(state, poll_future, 1); - } - - Ok(1) - }) - } - - unsafe extern "C-unwind" fn poll_future(state: *mut ffi::lua_State) -> c_int { - let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); - let extra = (*upvalue).extra.get(); - callback_error_ext(state, extra, |_| { - // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) - let lua = (*extra).lua(); - // The lock must be already held as the future is polled - let rawlua = lua.guard_unchecked(); - let _guard = StateGuard::new(&rawlua, state); - - let fut = &mut (*upvalue).data; - let mut ctx = Context::from_waker(rawlua.waker()); - match fut.as_mut().poll(&mut ctx) { - Poll::Pending => { - ffi::lua_pushnil(state); - ffi::lua_pushlightuserdata(state, Lua::poll_pending().0); - Ok(2) - } - Poll::Ready(nresults) => { - match nresults? { - nresults @ 0..=2 => { - // Fast path for up to 2 results without creating a table - ffi::lua_pushinteger(state, nresults as _); - if nresults > 0 { - ffi::lua_insert(state, -nresults - 1); - } - Ok(nresults + 1) - } - nresults => { - let results = MultiValue::from_stack_multi(nresults, &rawlua)?; - ffi::lua_pushinteger(state, nresults as _); - rawlua.push(lua.create_sequence_from(results)?)?; - Ok(2) - } - } - } - } - }) - } - - let state = self.state(); - let get_poll = unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 4)?; - - let func = mem::transmute(func); - let extra = Arc::clone(&self.extra); - let protect = !self.unlikely_memory_error(); - let upvalue = AsyncCallbackUpvalue { data: func, extra }; - push_gc_userdata(state, upvalue, protect)?; - if protect { - protect_lua!(state, 1, 1, fn(state) { - ffi::lua_pushcclosure(state, call_callback, 1); - })?; - } else { - ffi::lua_pushcclosure(state, call_callback, 1); - } - - Function(self.pop_ref()) - }; - - unsafe extern "C-unwind" fn unpack(state: *mut ffi::lua_State) -> c_int { - let len = ffi::lua_tointeger(state, 2); - ffi::luaL_checkstack(state, len as c_int, ptr::null()); - for i in 1..=len { - ffi::lua_rawgeti(state, 1, i); - } - len as c_int - } - - let lua = self.lua(); - let coroutine = lua.globals().get::<_, Table>("coroutine")?; - - let env = lua.create_table_with_capacity(0, 3)?; - env.set("get_poll", get_poll)?; - // Cache `yield` function - env.set("yield", coroutine.get::<_, Function>("yield")?)?; - unsafe { - env.set("unpack", lua.create_c_function(unpack)?)?; - } - - lua.load( - r#" - local poll = get_poll(...) - while true do - local nres, res, res2 = poll() - if nres ~= nil then - if nres == 0 then - return - elseif nres == 1 then - return res - elseif nres == 2 then - return res, res2 - else - return unpack(res, nres) - end - end - yield(res) -- `res` is a "pending" value - end - "#, - ) - .try_cache() - .set_name("__mlua_async_poll") - .set_environment(env) - .into_function() - } - - #[cfg(feature = "async")] - #[inline] - pub(crate) unsafe fn waker(&self) -> &Waker { - (*self.extra.get()).waker.as_ref() - } - - #[cfg(feature = "async")] - #[inline] - pub(crate) unsafe fn set_waker(&self, waker: NonNull) -> NonNull { - mem::replace(&mut (*self.extra.get()).waker, waker) - } -} - -impl WeakLua { - #[track_caller] - #[inline(always)] - pub(crate) fn lock(&self) -> LuaGuard { - LuaGuard::new(self.0.upgrade().unwrap()) - } - - #[inline(always)] - pub(crate) fn try_lock(&self) -> Option { - Some(LuaGuard::new(self.0.upgrade()?)) - } -} - -impl PartialEq for WeakLua { - fn eq(&self, other: &Self) -> bool { - Weak::ptr_eq(&self.0, &other.0) - } -} - -impl Eq for WeakLua {} - -impl LuaGuard { - pub(crate) fn new(handle: Arc>) -> Self { - Self(handle.lock_arc()) - } -} - -impl Deref for LuaGuard { - type Target = LuaInner; - - fn deref(&self) -> &Self::Target { - &*self.0 - } -} - -impl ExtraData { - // Index of `error_traceback` function in auxiliary thread stack - #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] - const ERROR_TRACEBACK_IDX: c_int = 1; - - #[inline(always)] - const fn lua(&self) -> &Lua { - unsafe { mem::transmute(self.inner.assume_init_ref()) } - } - - #[inline(always)] - const fn weak(&self) -> &WeakLua { - unsafe { mem::transmute(self.weak.assume_init_ref()) } - } -} - -struct StateGuard<'a>(&'a LuaInner, *mut ffi::lua_State); - -impl<'a> StateGuard<'a> { - fn new(inner: &'a LuaInner, mut state: *mut ffi::lua_State) -> Self { - state = inner.state.replace(state); - Self(inner, state) - } -} - -impl<'a> Drop for StateGuard<'a> { - fn drop(&mut self) { - self.0.state.set(self.1); - } -} - -unsafe fn extra_data(state: *mut ffi::lua_State) -> *mut ExtraData { - #[cfg(feature = "luau")] - if cfg!(not(feature = "module")) { - // In the main app we can use `lua_callbacks` to access ExtraData - return (*ffi::lua_callbacks(state)).userdata as *mut _; - } - - 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 { - // `ExtraData` can be null only when Lua state is foreign. - // This case in used in `Lua::try_from_ptr()`. - ffi::lua_pop(state, 1); - return ptr::null_mut(); - } - let extra_ptr = ffi::lua_touserdata(state, -1) as *mut Arc>; - ffi::lua_pop(state, 1); - (*extra_ptr).get() -} - -unsafe fn set_extra_data( - state: *mut ffi::lua_State, - extra: &Arc>, -) -> Result<()> { - #[cfg(feature = "luau")] - if cfg!(not(feature = "module")) { - (*ffi::lua_callbacks(state)).userdata = extra.get() as *mut _; - return Ok(()); - } - - push_gc_userdata(state, Arc::clone(extra), true)?; - protect_lua!(state, 1, 0, fn(state) { - let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void; - ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, extra_key); - }) -} - -// Creates required entries in the metatable cache (see `util::METATABLE_CACHE`) -pub(crate) fn init_metatable_cache(cache: &mut FxHashMap) { - cache.insert(TypeId::of::>>(), 0); - cache.insert(TypeId::of::(), 0); - cache.insert(TypeId::of::(), 0); - - #[cfg(feature = "async")] - { - cache.insert(TypeId::of::(), 0); - cache.insert(TypeId::of::(), 0); - cache.insert(TypeId::of::(), 0); - cache.insert(TypeId::of::>(), 0); - } -} - -// An optimized version of `callback_error` that does not allocate `WrappedFailure` userdata -// and instead reuses unsed values from previous calls (or allocates new). -unsafe fn callback_error_ext(state: *mut ffi::lua_State, mut extra: *mut ExtraData, f: F) -> R -where - F: FnOnce(c_int) -> Result, -{ - if extra.is_null() { - extra = extra_data(state); - } - - let nargs = ffi::lua_gettop(state); - - enum PreallocatedFailure { - New(*mut WrappedFailure), - Existing(i32), - } - - impl PreallocatedFailure { - unsafe fn reserve(state: *mut ffi::lua_State, extra: *mut ExtraData) -> Self { - match (*extra).wrapped_failure_pool.pop() { - Some(index) => PreallocatedFailure::Existing(index), - None => { - // We need to check stack for Luau in case when callback is called from interrupt - // See https://github.com/Roblox/luau/issues/446 and mlua #142 and #153 - #[cfg(feature = "luau")] - ffi::lua_rawcheckstack(state, 2); - // Place it to the beginning of the stack - let ud = WrappedFailure::new_userdata(state); - ffi::lua_insert(state, 1); - PreallocatedFailure::New(ud) - } - } - } - - unsafe fn r#use( - &self, - state: *mut ffi::lua_State, - extra: *mut ExtraData, - ) -> *mut WrappedFailure { - let ref_thread = (*extra).ref_thread; - match *self { - PreallocatedFailure::New(ud) => { - ffi::lua_settop(state, 1); - ud - } - PreallocatedFailure::Existing(index) => { - ffi::lua_settop(state, 0); - #[cfg(feature = "luau")] - ffi::lua_rawcheckstack(state, 2); - ffi::lua_pushvalue(ref_thread, index); - ffi::lua_xmove(ref_thread, state, 1); - ffi::lua_pushnil(ref_thread); - ffi::lua_replace(ref_thread, index); - (*extra).ref_free.push(index); - ffi::lua_touserdata(state, -1) as *mut WrappedFailure - } - } - } - - unsafe fn release(self, state: *mut ffi::lua_State, extra: *mut ExtraData) { - let ref_thread = (*extra).ref_thread; - match self { - PreallocatedFailure::New(_) => { - if (*extra).wrapped_failure_pool.len() < WRAPPED_FAILURE_POOL_SIZE { - ffi::lua_rotate(state, 1, -1); - ffi::lua_xmove(state, ref_thread, 1); - let index = ref_stack_pop(extra); - (*extra).wrapped_failure_pool.push(index); - } else { - ffi::lua_remove(state, 1); - } - } - PreallocatedFailure::Existing(index) => { - if (*extra).wrapped_failure_pool.len() < WRAPPED_FAILURE_POOL_SIZE { - (*extra).wrapped_failure_pool.push(index); - } else { - ffi::lua_pushnil(ref_thread); - ffi::lua_replace(ref_thread, index); - (*extra).ref_free.push(index); - } - } - } - } - } - - // We cannot shadow Rust errors with Lua ones, so we need to reserve pre-allocated memory - // to store a wrapped failure (error or panic) *before* we proceed. - let prealloc_failure = PreallocatedFailure::reserve(state, extra); - - match catch_unwind(AssertUnwindSafe(|| f(nargs))) { - Ok(Ok(r)) => { - // Return unused `WrappedFailure` to the pool - prealloc_failure.release(state, extra); - r - } - Ok(Err(err)) => { - let wrapped_error = prealloc_failure.r#use(state, extra); - - // Build `CallbackError` with traceback - let traceback = if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 { - ffi::luaL_traceback(state, state, ptr::null(), 0); - let traceback = util::to_string(state, -1); - ffi::lua_pop(state, 1); - traceback - } else { - "".to_string() - }; - let cause = Arc::new(err); - ptr::write( - wrapped_error, - WrappedFailure::Error(Error::CallbackError { traceback, cause }), - ); - get_gc_metatable::(state); - ffi::lua_setmetatable(state, -2); - - ffi::lua_error(state) - } - Err(p) => { - let wrapped_panic = prealloc_failure.r#use(state, extra); - ptr::write(wrapped_panic, WrappedFailure::Panic(Some(p))); - get_gc_metatable::(state); - ffi::lua_setmetatable(state, -2); - ffi::lua_error(state) - } - } -} - -// Uses 3 stack spaces -unsafe fn load_from_std_lib(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> { - #[inline(always)] - pub unsafe fn requiref( - state: *mut ffi::lua_State, - modname: &str, - openf: ffi::lua_CFunction, - glb: c_int, - ) -> Result<()> { - let modname = mlua_expect!(CString::new(modname), "modname contains nil byte"); - protect_lua!(state, 0, 1, |state| { - ffi::luaL_requiref(state, modname.as_ptr() as *const c_char, openf, glb) - }) - } - - #[cfg(feature = "luajit")] - struct GcGuard(*mut ffi::lua_State); - - #[cfg(feature = "luajit")] - impl GcGuard { - fn new(state: *mut ffi::lua_State) -> Self { - // Stop collector during library initialization - unsafe { ffi::lua_gc(state, ffi::LUA_GCSTOP, 0) }; - GcGuard(state) - } - } - - #[cfg(feature = "luajit")] - impl Drop for GcGuard { - fn drop(&mut self) { - unsafe { ffi::lua_gc(self.0, ffi::LUA_GCRESTART, -1) }; - } - } - - // Stop collector during library initialization - #[cfg(feature = "luajit")] - let _gc_guard = GcGuard::new(state); - - #[cfg(any( - feature = "lua54", - feature = "lua53", - feature = "lua52", - feature = "luau" - ))] - { - if libs.contains(StdLib::COROUTINE) { - requiref(state, ffi::LUA_COLIBNAME, ffi::luaopen_coroutine, 1)?; - ffi::lua_pop(state, 1); - } - } - - if libs.contains(StdLib::TABLE) { - requiref(state, ffi::LUA_TABLIBNAME, ffi::luaopen_table, 1)?; - ffi::lua_pop(state, 1); - } - - #[cfg(not(feature = "luau"))] - if libs.contains(StdLib::IO) { - requiref(state, ffi::LUA_IOLIBNAME, ffi::luaopen_io, 1)?; - ffi::lua_pop(state, 1); - } - - if libs.contains(StdLib::OS) { - requiref(state, ffi::LUA_OSLIBNAME, ffi::luaopen_os, 1)?; - ffi::lua_pop(state, 1); - } - - if libs.contains(StdLib::STRING) { - requiref(state, ffi::LUA_STRLIBNAME, ffi::luaopen_string, 1)?; - ffi::lua_pop(state, 1); - } - - #[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] - { - if libs.contains(StdLib::UTF8) { - requiref(state, ffi::LUA_UTF8LIBNAME, ffi::luaopen_utf8, 1)?; - ffi::lua_pop(state, 1); - } - } - - #[cfg(any(feature = "lua52", feature = "luau"))] - { - if libs.contains(StdLib::BIT) { - requiref(state, ffi::LUA_BITLIBNAME, ffi::luaopen_bit32, 1)?; - ffi::lua_pop(state, 1); - } - } - - #[cfg(feature = "luajit")] - { - if libs.contains(StdLib::BIT) { - requiref(state, ffi::LUA_BITLIBNAME, ffi::luaopen_bit, 1)?; - ffi::lua_pop(state, 1); - } - } - - #[cfg(feature = "luau")] - if libs.contains(StdLib::BUFFER) { - requiref(state, ffi::LUA_BUFFERLIBNAME, ffi::luaopen_buffer, 1)?; - ffi::lua_pop(state, 1); - } - - if libs.contains(StdLib::MATH) { - requiref(state, ffi::LUA_MATHLIBNAME, ffi::luaopen_math, 1)?; - ffi::lua_pop(state, 1); - } - - if libs.contains(StdLib::DEBUG) { - requiref(state, ffi::LUA_DBLIBNAME, ffi::luaopen_debug, 1)?; - ffi::lua_pop(state, 1); - } - - #[cfg(not(feature = "luau"))] - if libs.contains(StdLib::PACKAGE) { - requiref(state, ffi::LUA_LOADLIBNAME, ffi::luaopen_package, 1)?; - ffi::lua_pop(state, 1); - } - #[cfg(feature = "luau")] - if libs.contains(StdLib::PACKAGE) { - let lua: &Lua = mem::transmute((*extra_data(state)).inner.assume_init_ref()); - crate::luau::register_package_module(lua)?; - } - - #[cfg(feature = "luajit")] - { - if libs.contains(StdLib::JIT) { - requiref(state, ffi::LUA_JITLIBNAME, ffi::luaopen_jit, 1)?; - ffi::lua_pop(state, 1); - } - - if libs.contains(StdLib::FFI) { - requiref(state, ffi::LUA_FFILIBNAME, ffi::luaopen_ffi, 1)?; - ffi::lua_pop(state, 1); - } - } - - Ok(()) -} - -unsafe fn ref_stack_pop(extra: *mut ExtraData) -> c_int { - let extra = &mut *extra; - if let Some(free) = extra.ref_free.pop() { - ffi::lua_replace(extra.ref_thread, free); - return free; - } - - // Try to grow max stack size - if extra.ref_stack_top >= extra.ref_stack_size { - let mut inc = extra.ref_stack_size; // Try to double stack size - while inc > 0 && ffi::lua_checkstack(extra.ref_thread, inc) == 0 { - inc /= 2; - } - if inc == 0 { - // Pop item on top of the stack to avoid stack leaking and successfully run destructors - // during unwinding. - ffi::lua_pop(extra.ref_thread, 1); - let top = extra.ref_stack_top; - // It is a user error to create enough references to exhaust the Lua max stack size for - // the ref thread. - panic!( - "cannot create a Lua reference, out of auxiliary stack space (used {top} slots)" - ); - } - extra.ref_stack_size += inc; - } - extra.ref_stack_top += 1; - extra.ref_stack_top -} - -#[cfg(test)] -mod assertions { - use super::*; - - // Lua has lots of interior mutability, should not be RefUnwindSafe - static_assertions::assert_not_impl_any!(Lua: std::panic::RefUnwindSafe); - - #[cfg(not(feature = "send"))] - static_assertions::assert_not_impl_any!(Lua: Send); - #[cfg(feature = "send")] - static_assertions::assert_impl_all!(Lua: Send); -} diff --git a/src/luau/mod.rs b/src/luau/mod.rs index 1d18515..139860b 100644 --- a/src/luau/mod.rs +++ b/src/luau/mod.rs @@ -2,7 +2,7 @@ use std::ffi::CStr; use std::os::raw::{c_float, c_int}; use crate::error::Result; -use crate::lua::Lua; +use crate::state::Lua; // Since Luau has some missing standard functions, we re-implement them here diff --git a/src/luau/package.rs b/src/luau/package.rs index 880f2e8..cf3fe0a 100644 --- a/src/luau/package.rs +++ b/src/luau/package.rs @@ -7,7 +7,7 @@ use std::{env, fs}; use crate::chunk::ChunkMode; use crate::error::Result; -use crate::lua::Lua; +use crate::state::Lua; use crate::table::Table; use crate::types::RegistryKey; use crate::value::{IntoLua, Value}; diff --git a/src/multi.rs b/src/multi.rs index e56aeb9..317c14e 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -5,7 +5,8 @@ use std::os::raw::c_int; use std::result::Result as StdResult; use crate::error::Result; -use crate::lua::{Lua, LuaInner}; +use crate::state::Lua; +use crate::state::RawLua; use crate::util::check_stack; use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil}; @@ -21,7 +22,7 @@ impl IntoLuaMulti for StdResult { } #[inline] - unsafe fn push_into_stack_multi(self, lua: &LuaInner) -> Result { + unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result { match self { Ok(val) => (val,).push_into_stack_multi(lua), Err(err) => (Nil, err).push_into_stack_multi(lua), @@ -39,7 +40,7 @@ impl IntoLuaMulti for StdResult<(), E> { } #[inline] - unsafe fn push_into_stack_multi(self, lua: &LuaInner) -> Result { + unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result { match self { Ok(_) => Ok(0), Err(err) => (Nil, err).push_into_stack_multi(lua), @@ -56,7 +57,7 @@ impl IntoLuaMulti for T { } #[inline] - unsafe fn push_into_stack_multi(self, lua: &LuaInner) -> Result { + unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result { self.push_into_stack(lua)?; Ok(1) } @@ -74,7 +75,7 @@ impl FromLuaMulti for T { } #[inline] - unsafe fn from_stack_multi(nvals: c_int, lua: &LuaInner) -> Result { + unsafe fn from_stack_multi(nvals: c_int, lua: &RawLua) -> Result { if nvals == 0 { return T::from_lua(Nil, lua.lua()); } @@ -86,7 +87,7 @@ impl FromLuaMulti for T { nargs: c_int, i: usize, to: Option<&str>, - lua: &LuaInner, + lua: &RawLua, ) -> Result { if nargs == 0 { return T::from_lua_arg(Nil, i, to, lua.lua()); @@ -209,7 +210,7 @@ macro_rules! impl_tuple { } #[inline] - unsafe fn push_into_stack_multi(self, _lua: &LuaInner) -> Result { + unsafe fn push_into_stack_multi(self, _lua: &RawLua) -> Result { Ok(0) } } @@ -221,7 +222,7 @@ macro_rules! impl_tuple { } #[inline] - unsafe fn from_stack_multi(nvals: c_int, lua: &LuaInner) -> Result { + unsafe fn from_stack_multi(nvals: c_int, lua: &RawLua) -> Result { if nvals > 0 { ffi::lua_pop(lua.state(), nvals); } @@ -247,7 +248,7 @@ macro_rules! impl_tuple { #[allow(non_snake_case)] #[inline] - unsafe fn push_into_stack_multi(self, lua: &LuaInner) -> Result { + unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result { let ($($name,)* $last,) = self; let mut nresults = 0; $( @@ -288,7 +289,7 @@ macro_rules! impl_tuple { #[allow(unused_mut, non_snake_case)] #[inline] - unsafe fn from_stack_multi(mut nvals: c_int, lua: &LuaInner) -> Result { + unsafe fn from_stack_multi(mut nvals: c_int, lua: &RawLua) -> Result { $( let $name = if nvals > 0 { nvals -= 1; @@ -303,7 +304,7 @@ macro_rules! impl_tuple { #[allow(unused_mut, non_snake_case)] #[inline] - unsafe fn from_stack_args(mut nargs: c_int, mut i: usize, to: Option<&str>, lua: &LuaInner) -> Result { + unsafe fn from_stack_args(mut nargs: c_int, mut i: usize, to: Option<&str>, lua: &RawLua) -> Result { $( let $name = if nargs > 0 { nargs -= 1; diff --git a/src/serde/mod.rs b/src/serde/mod.rs index 9495281..46c2d91 100644 --- a/src/serde/mod.rs +++ b/src/serde/mod.rs @@ -5,7 +5,7 @@ use std::os::raw::c_void; use serde::{de::DeserializeOwned, ser::Serialize}; use crate::error::Result; -use crate::lua::Lua; +use crate::state::Lua; use crate::private::Sealed; use crate::table::Table; use crate::util::check_stack; diff --git a/src/serde/ser.rs b/src/serde/ser.rs index df3a16c..315308c 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -2,7 +2,7 @@ use serde::{ser, Serialize}; use super::LuaSerdeExt; use crate::error::{Error, Result}; -use crate::lua::Lua; +use crate::state::Lua; use crate::table::Table; use crate::value::{IntoLua, Value}; diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..6373ce3 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,1936 @@ +use std::any::TypeId; +use std::cell::RefCell; +// use std::collections::VecDeque; +use std::fmt; +use std::marker::PhantomData; +use std::ops::Deref; +use std::os::raw::{c_int, c_void}; +use std::panic::Location; +use std::result::Result as StdResult; +use std::sync::{Arc, Weak}; +use std::{mem, ptr}; + +use parking_lot::{ReentrantMutex, ReentrantMutexGuard}; + +use crate::chunk::{AsChunk, Chunk}; +use crate::error::{Error, Result}; +use crate::function::Function; +use crate::hook::Debug; +use crate::memory::MemoryState; +// use crate::scope::Scope; +use crate::stdlib::StdLib; +use crate::string::String; +use crate::table::Table; +use crate::thread::Thread; +use crate::types::{ + AppDataRef, AppDataRefMut, ArcReentrantMutexGuard, Integer, LightUserData, MaybeSend, Number, + RegistryKey, +}; +use crate::userdata::{AnyUserData, UserData, UserDataProxy, UserDataRegistry, UserDataVariant}; +use crate::util::{assert_stack, check_stack, push_string, push_table, rawset_field, StackGuard}; +use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil, Value}; + +#[cfg(not(feature = "luau"))] +use crate::hook::HookTriggers; + +#[cfg(any(feature = "luau", doc))] +use crate::{chunk::Compiler, types::VmState}; + +#[cfg(feature = "async")] +use std::future::{self, Future}; + +#[cfg(feature = "serialize")] +use serde::Serialize; + +pub(crate) use extra::ExtraData; +pub use raw::RawLua; +use util::{callback_error_ext, StateGuard}; + +/// Top level Lua struct which represents an instance of Lua VM. +#[derive(Clone)] +#[repr(transparent)] +pub struct Lua(Arc>); + +#[derive(Clone)] +#[repr(transparent)] +pub(crate) struct WeakLua(Weak>); + +pub(crate) struct LuaGuard(ArcReentrantMutexGuard); + +/// Mode of the Lua garbage collector (GC). +/// +/// In Lua 5.4 GC can work in two modes: incremental and generational. +/// Previous Lua versions support only incremental GC. +/// +/// More information can be found in the Lua [documentation]. +/// +/// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5 +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum GCMode { + Incremental, + /// Requires `feature = "lua54"` + #[cfg(feature = "lua54")] + #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] + Generational, +} + +/// Controls Lua interpreter behavior such as Rust panics handling. +#[derive(Clone, Debug)] +#[non_exhaustive] +pub struct LuaOptions { + /// Catch Rust panics when using [`pcall`]/[`xpcall`]. + /// + /// If disabled, wraps these functions and automatically resumes panic if found. + /// Also in Lua 5.1 adds ability to provide arguments to [`xpcall`] similar to Lua >= 5.2. + /// + /// If enabled, keeps [`pcall`]/[`xpcall`] unmodified. + /// Panics are still automatically resumed if returned to the Rust side. + /// + /// Default: **true** + /// + /// [`pcall`]: https://www.lua.org/manual/5.4/manual.html#pdf-pcall + /// [`xpcall`]: https://www.lua.org/manual/5.4/manual.html#pdf-xpcall + pub catch_rust_panics: bool, + + /// Max size of thread (coroutine) object pool used to execute asynchronous functions. + /// + /// It works on Lua 5.4 and Luau, where [`lua_resetthread`] function + /// is available and allows to reuse old coroutines after resetting their state. + /// + /// Default: **0** (disabled) + /// + /// [`lua_resetthread`]: https://www.lua.org/manual/5.4/manual.html#lua_resetthread + #[cfg(feature = "async")] + #[cfg_attr(docsrs, doc(cfg(feature = "async")))] + pub thread_pool_size: usize, +} + +impl Default for LuaOptions { + fn default() -> Self { + LuaOptions::new() + } +} + +impl LuaOptions { + /// Returns a new instance of `LuaOptions` with default parameters. + pub const fn new() -> Self { + LuaOptions { + catch_rust_panics: true, + #[cfg(feature = "async")] + thread_pool_size: 0, + } + } + + /// Sets [`catch_rust_panics`] option. + /// + /// [`catch_rust_panics`]: #structfield.catch_rust_panics + #[must_use] + pub const fn catch_rust_panics(mut self, enabled: bool) -> Self { + self.catch_rust_panics = enabled; + self + } + + /// Sets [`thread_pool_size`] option. + /// + /// [`thread_pool_size`]: #structfield.thread_pool_size + #[cfg(feature = "async")] + #[cfg_attr(docsrs, doc(cfg(feature = "async")))] + #[must_use] + pub const fn thread_pool_size(mut self, size: usize) -> Self { + self.thread_pool_size = size; + self + } +} + +/// Requires `feature = "send"` +#[cfg(feature = "send")] +#[cfg_attr(docsrs, doc(cfg(feature = "send")))] +unsafe impl Send for Lua {} + +#[cfg(not(feature = "module"))] +impl Drop for Lua { + fn drop(&mut self) { + let _ = self.gc_collect(); + } +} + +impl fmt::Debug for Lua { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Lua({:p})", self.lock().state()) + } +} + +impl Default for Lua { + #[inline] + fn default() -> Self { + Lua::new() + } +} + +impl Lua { + /// Creates a new Lua state and loads the **safe** subset of the standard libraries. + /// + /// # Safety + /// The created Lua state would have _some_ safety guarantees and would not allow to load unsafe + /// standard libraries or C modules. + /// + /// See [`StdLib`] documentation for a list of unsafe modules that cannot be loaded. + /// + /// [`StdLib`]: crate::StdLib + pub fn new() -> Lua { + mlua_expect!( + Self::new_with(StdLib::ALL_SAFE, LuaOptions::default()), + "Cannot create a Lua state" + ) + } + + /// Creates a new Lua state and loads all the standard libraries. + /// + /// # Safety + /// The created Lua state would not have safety guarantees and would allow to load C modules. + pub unsafe fn unsafe_new() -> Lua { + Self::unsafe_new_with(StdLib::ALL, LuaOptions::default()) + } + + /// Creates a new Lua state and loads the specified safe subset of the standard libraries. + /// + /// Use the [`StdLib`] flags to specify the libraries you want to load. + /// + /// # Safety + /// The created Lua state would have _some_ safety guarantees and would not allow to load unsafe + /// standard libraries or C modules. + /// + /// See [`StdLib`] documentation for a list of unsafe modules that cannot be loaded. + /// + /// [`StdLib`]: crate::StdLib + pub fn new_with(libs: StdLib, options: LuaOptions) -> Result { + #[cfg(not(feature = "luau"))] + if libs.contains(StdLib::DEBUG) { + return Err(Error::SafetyError( + "The unsafe `debug` module can't be loaded using safe `new_with`".to_string(), + )); + } + #[cfg(feature = "luajit")] + if libs.contains(StdLib::FFI) { + return Err(Error::SafetyError( + "The unsafe `ffi` module can't be loaded using safe `new_with`".to_string(), + )); + } + + let lua = unsafe { Self::inner_new(libs, options) }; + + if libs.contains(StdLib::PACKAGE) { + mlua_expect!(lua.disable_c_modules(), "Error disabling C modules"); + } + unsafe { lua.lock().set_safe() }; + + Ok(lua) + } + + /// Creates a new Lua state and loads the specified subset of the standard libraries. + /// + /// Use the [`StdLib`] flags to specify the libraries you want to load. + /// + /// # Safety + /// The created Lua state will not have safety guarantees and allow to load C modules. + /// + /// [`StdLib`]: crate::StdLib + pub unsafe fn unsafe_new_with(libs: StdLib, options: LuaOptions) -> Lua { + // Workaround to avoid stripping a few unused Lua symbols that could be imported + // by C modules in unsafe mode + let mut _symbols: Vec<*const extern "C-unwind" fn()> = + vec![ffi::lua_isuserdata as _, ffi::lua_tocfunction as _]; + + #[cfg(not(feature = "luau"))] + _symbols.extend_from_slice(&[ + ffi::lua_atpanic as _, + ffi::luaL_loadstring as _, + ffi::luaL_openlibs as _, + ]); + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + { + _symbols.push(ffi::lua_getglobal as _); + _symbols.push(ffi::lua_setglobal as _); + _symbols.push(ffi::luaL_setfuncs as _); + } + + Self::inner_new(libs, options) + } + + /// Creates a new Lua state with required `libs` and `options` + unsafe fn inner_new(libs: StdLib, options: LuaOptions) -> Lua { + let lua = Lua(RawLua::new(libs, options)); + + #[cfg(feature = "luau")] + mlua_expect!(lua.configure_luau(), "Error configuring Luau"); + + lua + } + + /// Constructs a new Lua instance from an existing raw state. + /// + /// Once called, a returned Lua state is cached in the registry and can be retrieved + /// by calling this function again. + #[allow(clippy::missing_safety_doc)] + #[inline] + pub unsafe fn init_from_ptr(state: *mut ffi::lua_State) -> Lua { + Lua(RawLua::init_from_ptr(state)) + } + + /// FIXME: Deprecated load_from_std_lib + + /// Loads the specified subset of the standard libraries into an existing Lua state. + /// + /// Use the [`StdLib`] flags to specify the libraries you want to load. + pub fn load_std_libs(&self, libs: StdLib) -> Result<()> { + unsafe { self.lock().load_std_libs(libs) } + } + + /// Loads module `modname` into an existing Lua state using the specified entrypoint + /// function. + /// + /// Internally calls the Lua function `func` with the string `modname` as an argument, + /// sets the call result to `package.loaded[modname]` and returns copy of the result. + /// + /// If `package.loaded[modname]` value is not nil, returns copy of the value without + /// calling the function. + /// + /// If the function does not return a non-nil value then this method assigns true to + /// `package.loaded[modname]`. + /// + /// Behavior is similar to Lua's [`require`] function. + /// + /// [`require`]: https://www.lua.org/manual/5.4/manual.html#pdf-require + pub fn load_from_function(&self, modname: &str, func: Function) -> Result + where + T: FromLua, + { + let lua = self.lock(); + let state = lua.state(); + let loaded = unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 2)?; + protect_lua!(state, 0, 1, fn(state) { + ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED")); + })?; + Table(lua.pop_ref()) + }; + + let modname = unsafe { lua.create_string(modname)? }; + let value = match loaded.raw_get(&modname)? { + Value::Nil => { + let result = match func.call(&modname)? { + Value::Nil => Value::Boolean(true), + res => res, + }; + loaded.raw_set(modname, &result)?; + result + } + res => res, + }; + T::from_lua(value, self) + } + + /// Unloads module `modname`. + /// + /// Removes module from the [`package.loaded`] table which allows to load it again. + /// It does not support unloading binary Lua modules since they are internally cached and can be + /// unloaded only by closing Lua state. + /// + /// [`package.loaded`]: https://www.lua.org/manual/5.4/manual.html#pdf-package.loaded + pub fn unload(&self, modname: &str) -> Result<()> { + let lua = self.lock(); + let state = lua.state(); + let loaded = unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 2)?; + protect_lua!(state, 0, 1, fn(state) { + ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED")); + })?; + Table(lua.pop_ref()) + }; + + loaded.raw_set(modname, Nil) + } + + /// 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. + /// + /// 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 + /// + /// FIXME: remove + #[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. + /// + /// FIXME: remove + #[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)] + #[cfg(not(tarpaulin_include))] + pub unsafe fn entrypoint(self, state: *mut ffi::lua_State, func: F) -> c_int + where + F: Fn(&Lua, A) -> Result + MaybeSend + 'static, + A: FromLuaMulti, + R: IntoLua, + { + let extra = self.lock().extra.get(); + // `self` is no longer needed and must be dropped at this point to avoid possible memory leak + // in case of possible longjmp (lua_error) below + drop(self); + + callback_error_ext(state, extra, move |nargs| { + let lua = (*extra).lua(); + let rawlua = lua.lock(); + let _guard = StateGuard::new(&rawlua, state); + let args = A::from_stack_args(nargs, 1, None, &rawlua)?; + func(lua, args)?.push_into_stack(&rawlua)?; + Ok(1) + }) + } + + // A simple module entrypoint without arguments + #[doc(hidden)] + #[cfg(not(tarpaulin_include))] + pub unsafe fn entrypoint1(self, state: *mut ffi::lua_State, func: F) -> c_int + where + R: IntoLua, + F: Fn(&Lua) -> Result + MaybeSend + 'static, + { + self.entrypoint(state, move |lua, _: ()| func(lua)) + } + + /// Skips memory checks for some operations. + #[doc(hidden)] + #[cfg(feature = "module")] + pub fn skip_memory_check(&self, skip: bool) { + unsafe { (*self.extra.get()).skip_memory_check = skip }; + } + + /// 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>("var")?, None); + /// # Ok(()) + /// # } + /// ``` + /// + /// Requires `feature = "luau"` + #[cfg(any(feature = "luau", docsrs))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + pub fn sandbox(&self, enabled: bool) -> Result<()> { + let lua = self.lock(); + unsafe { + if (*lua.extra.get()).sandboxed != enabled { + let state = lua.main_state; + 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` + ffi::lua_xpush(lua.ref_thread(), state, ffi::LUA_GLOBALSINDEX); + ffi::lua_replace(state, ffi::LUA_GLOBALSINDEX); + ffi::luaL_sandbox(state, 0); + } + })?; + (*lua.extra.get()).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` + /// parameter, see [`HookTriggers`] for more details. + /// + /// The provided hook function can error, and this error will be propagated through the Lua code + /// that was executing at the time the hook was triggered. This can be used to implement a + /// limited form of execution limits by setting [`HookTriggers.every_nth_instruction`] and + /// erroring once an instruction limit has been reached. + /// + /// This method sets a hook function for the current thread of this Lua instance. + /// If you want to set a hook function for another thread (coroutine), use [`Thread::set_hook()`] instead. + /// + /// Please note you cannot have more than one hook function set at a time for this Lua instance. + /// + /// # Example + /// + /// Shows each line number of code being executed by the Lua interpreter. + /// + /// ``` + /// # use mlua::{Lua, HookTriggers, Result}; + /// # fn main() -> Result<()> { + /// let lua = Lua::new(); + /// lua.set_hook(HookTriggers::EVERY_LINE, |_lua, debug| { + /// println!("line {}", debug.curr_line()); + /// Ok(()) + /// }); + /// + /// lua.load(r#" + /// local x = 2 + 3 + /// local y = x * 63 + /// local z = string.len(x..", "..y) + /// "#).exec() + /// # } + /// ``` + /// + /// [`HookTriggers`]: crate::HookTriggers + /// [`HookTriggers.every_nth_instruction`]: crate::HookTriggers::every_nth_instruction + #[cfg(not(feature = "luau"))] + #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] + pub fn set_hook(&self, triggers: HookTriggers, callback: F) + where + F: Fn(&Lua, Debug) -> Result<()> + MaybeSend + 'static, + { + let lua = self.lock(); + unsafe { lua.set_thread_hook(lua.state(), triggers, callback) }; + } + + /// Removes any hook previously set by [`Lua::set_hook()`] or [`Thread::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) { + let lua = self.lock(); + unsafe { + let state = lua.state(); + ffi::lua_sethook(state, None, 0, 0); + match crate::util::get_main_state(lua.main_state) { + Some(main_state) if !ptr::eq(state, main_state) => { + // If main_state is different from state, remove hook from it too + ffi::lua_sethook(main_state, None, 0, 0); + } + _ => {} + }; + (*lua.extra.get()).hook_callback = None; + (*lua.extra.get()).hook_thread = ptr::null_mut(); + } + } + + /// 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 |_| { + /// 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(any(feature = "luau", docsrs))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + pub fn set_interrupt(&self, callback: F) + where + F: Fn(&Lua) -> Result + MaybeSend + 'static, + { + unsafe extern "C-unwind" fn interrupt_proc(state: *mut ffi::lua_State, gc: c_int) { + if gc >= 0 { + // We don't support GC interrupts since they cannot survive Lua exceptions + return; + } + let extra = ExtraData::get(state); + 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 + } + let _guard = StateGuard::new((*extra).raw_lua(), state); + interrupt_cb((*extra).lua()) + }); + match result { + VmState::Continue => {} + VmState::Yield => { + ffi::lua_yield(state, 0); + } + } + } + + // Set interrupt callback + let lua = self.lock(); + unsafe { + (*lua.extra.get()).interrupt_callback = Some(Arc::new(callback)); + (*ffi::lua_callbacks(lua.main_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(any(feature = "luau", docsrs))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + pub fn remove_interrupt(&self) { + let lua = self.lock(); + unsafe { + (*lua.extra.get()).interrupt_callback = None; + (*ffi::lua_callbacks(lua.main_state)).interrupt = None; + } + } + + /// Sets the warning function to be used by Lua to emit warnings. + /// + /// Requires `feature = "lua54"` + #[cfg(feature = "lua54")] + #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] + pub fn set_warning_function(&self, callback: F) + where + F: Fn(&Lua, &str, bool) -> Result<()> + MaybeSend + 'static, + { + use std::ffi::CStr; + use std::os::raw::c_char; + use std::string::String as StdString; + + unsafe extern "C-unwind" fn warn_proc(ud: *mut c_void, msg: *const c_char, tocont: c_int) { + let extra = ud as *mut ExtraData; + callback_error_ext((*extra).raw_lua().state(), extra, |_| { + let cb = mlua_expect!( + (*extra).warn_callback.as_ref(), + "no warning callback set in warn_proc" + ); + let msg = StdString::from_utf8_lossy(CStr::from_ptr(msg).to_bytes()); + cb((*extra).lua(), &msg, tocont != 0) + }); + } + + let lua = self.lock(); + let state = lua.main_state; + unsafe { + (*lua.extra.get()).warn_callback = Some(Box::new(callback)); + ffi::lua_setwarnf(state, Some(warn_proc), lua.extra.get() as *mut c_void); + } + } + + /// Removes warning function previously set by `set_warning_function`. + /// + /// This function has no effect if a warning function was not previously set. + /// + /// Requires `feature = "lua54"` + #[cfg(feature = "lua54")] + #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] + pub fn remove_warning_function(&self) { + let lua = self.lock(); + unsafe { + (*lua.extra.get()).warn_callback = None; + ffi::lua_setwarnf(lua.main_state, None, ptr::null_mut()); + } + } + + /// Emits a warning with the given message. + /// + /// A message in a call with `incomplete` set to `true` should be continued in + /// another call to this function. + /// + /// Requires `feature = "lua54"` + #[cfg(feature = "lua54")] + #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] + pub fn warning(&self, msg: impl AsRef, incomplete: bool) { + let msg = msg.as_ref(); + let mut bytes = vec![0; msg.len() + 1]; + bytes[..msg.len()].copy_from_slice(msg.as_bytes()); + let real_len = bytes.iter().position(|&c| c == 0).unwrap(); + bytes.truncate(real_len); + let lua = self.lock(); + unsafe { + ffi::lua_warning(lua.state(), bytes.as_ptr() as *const _, incomplete as c_int); + } + } + + /// Gets information about the interpreter runtime stack. + /// + /// This function returns [`Debug`] structure that can be used to get information about the function + /// executing at a given level. Level `0` is the current running function, whereas level `n+1` is the + /// function that has called level `n` (except for tail calls, which do not count in the stack). + /// + /// [`Debug`]: crate::hook::Debug + pub fn inspect_stack(&self, level: usize) -> Option { + let lua = self.lock(); + unsafe { + let mut ar: ffi::lua_Debug = mem::zeroed(); + let level = level as c_int; + #[cfg(not(feature = "luau"))] + if ffi::lua_getstack(lua.state(), level, &mut ar) == 0 { + return None; + } + #[cfg(feature = "luau")] + if ffi::lua_getinfo(lua.state(), level, cstr!(""), &mut ar) == 0 { + return None; + } + Some(Debug::new_owned(lua, level, ar)) + } + } + + /// Returns the amount of memory (in bytes) currently used inside this Lua state. + pub fn used_memory(&self) -> usize { + let lua = self.lock(); + unsafe { + match MemoryState::get(lua.main_state) { + mem_state if !mem_state.is_null() => (*mem_state).used_memory(), + _ => { + // Get data from the Lua GC + let used_kbytes = ffi::lua_gc(lua.main_state, ffi::LUA_GCCOUNT, 0); + let used_kbytes_rem = ffi::lua_gc(lua.main_state, ffi::LUA_GCCOUNTB, 0); + (used_kbytes as usize) * 1024 + (used_kbytes_rem as usize) + } + } + } + } + + /// Sets a memory limit (in bytes) on this Lua state. + /// + /// Once an allocation occurs that would pass this memory limit, + /// a `Error::MemoryError` is generated instead. + /// Returns previous limit (zero means no limit). + /// + /// Does not work in module mode where Lua state is managed externally. + pub fn set_memory_limit(&self, limit: usize) -> Result { + let lua = self.lock(); + unsafe { + match MemoryState::get(lua.main_state) { + mem_state if !mem_state.is_null() => Ok((*mem_state).set_memory_limit(limit)), + _ => Err(Error::MemoryLimitNotAvailable), + } + } + } + + /// Returns true if the garbage collector is currently running automatically. + /// + /// Requires `feature = "lua54/lua53/lua52/luau"` + #[cfg(any( + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luau" + ))] + pub fn gc_is_running(&self) -> bool { + let lua = self.lock(); + unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCISRUNNING, 0) != 0 } + } + + /// Stop the Lua GC from running + pub fn gc_stop(&self) { + let lua = self.lock(); + unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCSTOP, 0) }; + } + + /// Restarts the Lua GC if it is not running + pub fn gc_restart(&self) { + let lua = self.lock(); + unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCRESTART, 0) }; + } + + /// Perform a full garbage-collection cycle. + /// + /// It may be necessary to call this function twice to collect all currently unreachable + /// objects. Once to finish the current gc cycle, and once to start and finish the next cycle. + pub fn gc_collect(&self) -> Result<()> { + let lua = self.lock(); + unsafe { + check_stack(lua.main_state, 2)?; + protect_lua!(lua.main_state, 0, 0, fn(state) ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0)) + } + } + + /// Steps the garbage collector one indivisible step. + /// + /// Returns true if this has finished a collection cycle. + pub fn gc_step(&self) -> Result { + self.gc_step_kbytes(0) + } + + /// Steps the garbage collector as though memory had been allocated. + /// + /// if `kbytes` is 0, then this is the same as calling `gc_step`. Returns true if this step has + /// finished a collection cycle. + pub fn gc_step_kbytes(&self, kbytes: c_int) -> Result { + let lua = self.lock(); + unsafe { + check_stack(lua.main_state, 3)?; + protect_lua!(lua.main_state, 0, 0, |state| { + ffi::lua_gc(state, ffi::LUA_GCSTEP, kbytes) != 0 + }) + } + } + + /// Sets the 'pause' value of the collector. + /// + /// Returns the previous value of 'pause'. More information can be found in the Lua + /// [documentation]. + /// + /// For Luau this parameter sets GC goal + /// + /// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5 + pub fn gc_set_pause(&self, pause: c_int) -> c_int { + let lua = self.lock(); + unsafe { + #[cfg(not(feature = "luau"))] + return ffi::lua_gc(lua.main_state, ffi::LUA_GCSETPAUSE, pause); + #[cfg(feature = "luau")] + return ffi::lua_gc(lua.main_state, ffi::LUA_GCSETGOAL, pause); + } + } + + /// Sets the 'step multiplier' value of the collector. + /// + /// Returns the previous value of the 'step multiplier'. More information can be found in the + /// Lua [documentation]. + /// + /// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5 + pub fn gc_set_step_multiplier(&self, step_multiplier: c_int) -> c_int { + let lua = self.lock(); + unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCSETSTEPMUL, step_multiplier) } + } + + /// Changes the collector to incremental mode with the given parameters. + /// + /// Returns the previous mode (always `GCMode::Incremental` in Lua < 5.4). + /// More information can be found in the Lua [documentation]. + /// + /// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5.1 + pub fn gc_inc(&self, pause: c_int, step_multiplier: c_int, step_size: c_int) -> GCMode { + let lua = self.lock(); + let state = lua.main_state; + + #[cfg(any( + feature = "lua53", + feature = "lua52", + feature = "lua51", + feature = "luajit", + feature = "luau" + ))] + unsafe { + if pause > 0 { + #[cfg(not(feature = "luau"))] + ffi::lua_gc(state, ffi::LUA_GCSETPAUSE, pause); + #[cfg(feature = "luau")] + ffi::lua_gc(state, ffi::LUA_GCSETGOAL, pause); + } + + if step_multiplier > 0 { + ffi::lua_gc(state, ffi::LUA_GCSETSTEPMUL, step_multiplier); + } + + #[cfg(feature = "luau")] + if step_size > 0 { + ffi::lua_gc(state, ffi::LUA_GCSETSTEPSIZE, step_size); + } + #[cfg(not(feature = "luau"))] + let _ = step_size; // Ignored + + GCMode::Incremental + } + + #[cfg(feature = "lua54")] + let prev_mode = + unsafe { ffi::lua_gc(state, ffi::LUA_GCINC, pause, step_multiplier, step_size) }; + #[cfg(feature = "lua54")] + match prev_mode { + ffi::LUA_GCINC => GCMode::Incremental, + ffi::LUA_GCGEN => GCMode::Generational, + _ => unreachable!(), + } + } + + /// Changes the collector to generational mode with the given parameters. + /// + /// Returns the previous mode. More information about the generational GC + /// can be found in the Lua 5.4 [documentation][lua_doc]. + /// + /// Requires `feature = "lua54"` + /// + /// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#2.5.2 + #[cfg(feature = "lua54")] + #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] + pub fn gc_gen(&self, minor_multiplier: c_int, major_multiplier: c_int) -> GCMode { + let lua = self.lock(); + let state = lua.main_state; + let prev_mode = + unsafe { ffi::lua_gc(state, ffi::LUA_GCGEN, minor_multiplier, major_multiplier) }; + match prev_mode { + ffi::LUA_GCGEN => GCMode::Generational, + ffi::LUA_GCINC => GCMode::Incremental, + _ => unreachable!(), + } + } + + /// Sets a default Luau compiler (with custom options). + /// + /// This compiler will be used by default to load all Lua chunks + /// including via `require` function. + /// + /// See [`Compiler`] for details and possible options. + /// + /// Requires `feature = "luau"` + #[cfg(any(feature = "luau", doc))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + pub fn set_compiler(&self, compiler: Compiler) { + let lua = self.lock(); + unsafe { (*lua.extra.get()).compiler = Some(compiler) }; + } + + /// Toggles JIT compilation mode for new chunks of code. + /// + /// By default JIT is enabled. Changing this option does not have any effect on + /// already loaded functions. + #[cfg(any(feature = "luau-jit", doc))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau-jit")))] + pub fn enable_jit(&self, enable: bool) { + unsafe { (*self.extra.get()).enable_jit = enable }; + } + + /// Sets Luau feature flag (global setting). + /// + /// See https://github.com/luau-lang/luau/blob/master/CONTRIBUTING.md#feature-flags for details. + #[cfg(feature = "luau")] + #[doc(hidden)] + #[allow(clippy::result_unit_err)] + pub fn set_fflag(name: &str, enabled: bool) -> StdResult<(), ()> { + if let Ok(name) = std::ffi::CString::new(name) { + if unsafe { ffi::luau_setfflag(name.as_ptr(), enabled as c_int) != 0 } { + return Ok(()); + } + } + Err(()) + } + + /// Returns Lua source code as a `Chunk` builder type. + /// + /// In order to actually compile or run the resulting code, you must call [`Chunk::exec`] or + /// similar on the returned builder. Code is not even parsed until one of these methods is + /// called. + /// + /// [`Chunk::exec`]: crate::Chunk::exec + #[track_caller] + pub fn load<'a>(&self, chunk: impl AsChunk<'a>) -> Chunk<'a> { + let caller = Location::caller(); + Chunk { + lua: self.weak(), + name: chunk.name().unwrap_or_else(|| caller.to_string()), + env: chunk.environment(self), + mode: chunk.mode(), + source: chunk.source(), + #[cfg(feature = "luau")] + compiler: unsafe { (*self.lock().extra.get()).compiler.clone() }, + } + } + + /// Create and return an interned Lua string. Lua strings can be arbitrary `[u8]` data including + /// embedded nulls, so in addition to `&str` and `&String`, you can also pass plain `&[u8]` + /// here. + #[inline] + pub fn create_string(&self, s: impl AsRef<[u8]>) -> Result { + unsafe { self.lock().create_string(s) } + } + + /// Create and return a Luau [buffer] object from a byte slice of data. + /// + /// Requires `feature = "luau"` + /// + /// [buffer]: https://luau-lang.org/library#buffer-library + #[cfg(feature = "luau")] + pub fn create_buffer(&self, buf: impl AsRef<[u8]>) -> Result { + use crate::types::SubtypeId; + + let lua = self.lock(); + let state = lua.state(); + unsafe { + if lua.unlikely_memory_error() { + crate::util::push_buffer(lua.ref_thread(), buf.as_ref(), false)?; + return Ok(AnyUserData(lua.pop_ref_thread(), SubtypeId::Buffer)); + } + + let _sg = StackGuard::new(state); + check_stack(state, 4)?; + crate::util::push_buffer(state, buf.as_ref(), true)?; + Ok(AnyUserData(lua.pop_ref(), SubtypeId::Buffer)) + } + } + + /// Creates and returns a new empty table. + pub fn create_table(&self) -> Result
{ + self.create_table_with_capacity(0, 0) + } + + /// Creates and returns a new empty table, with the specified capacity. + /// `narr` is a hint for how many elements the table will have as a sequence; + /// `nrec` is a hint for how many other elements the table will have. + /// Lua may use these hints to preallocate memory for the new table. + pub fn create_table_with_capacity(&self, narr: usize, nrec: usize) -> Result
{ + unsafe { self.lock().create_table_with_capacity(narr, nrec) } + } + + /// Creates a table and fills it with values from an iterator. + pub fn create_table_from(&self, iter: I) -> Result
+ where + K: IntoLua, + V: IntoLua, + I: IntoIterator, + { + let lua = self.lock(); + let state = lua.state(); + unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 6)?; + + let iter = iter.into_iter(); + let lower_bound = iter.size_hint().0; + let protect = !lua.unlikely_memory_error(); + push_table(state, 0, lower_bound, protect)?; + for (k, v) in iter { + lua.push(k)?; + lua.push(v)?; + if protect { + protect_lua!(state, 3, 1, fn(state) ffi::lua_rawset(state, -3))?; + } else { + ffi::lua_rawset(state, -3); + } + } + + Ok(Table(lua.pop_ref())) + } + } + + /// Creates a table from an iterator of values, using `1..` as the keys. + pub fn create_sequence_from(&self, iter: I) -> Result
+ where + T: IntoLua, + I: IntoIterator, + { + unsafe { self.lock().create_sequence_from(iter) } + } + + /// Wraps a Rust function or closure, creating a callable Lua function handle to it. + /// + /// The function's return value is always a `Result`: If the function returns `Err`, the error + /// is raised as a Lua error, which can be caught using `(x)pcall` or bubble up to the Rust code + /// that invoked the Lua code. This allows using the `?` operator to propagate errors through + /// intermediate Lua code. + /// + /// If the function returns `Ok`, the contained value will be converted to one or more Lua + /// values. For details on Rust-to-Lua conversions, refer to the [`IntoLua`] and [`IntoLuaMulti`] + /// traits. + /// + /// # Examples + /// + /// Create a function which prints its argument: + /// + /// ``` + /// # use mlua::{Lua, Result}; + /// # fn main() -> Result<()> { + /// # let lua = Lua::new(); + /// let greet = lua.create_function(|_, name: String| { + /// println!("Hello, {}!", name); + /// Ok(()) + /// }); + /// # let _ = greet; // used + /// # Ok(()) + /// # } + /// ``` + /// + /// Use tuples to accept multiple arguments: + /// + /// ``` + /// # use mlua::{Lua, Result}; + /// # fn main() -> Result<()> { + /// # let lua = Lua::new(); + /// let print_person = lua.create_function(|_, (name, age): (String, u8)| { + /// println!("{} is {} years old!", name, age); + /// Ok(()) + /// }); + /// # let _ = print_person; // used + /// # Ok(()) + /// # } + /// ``` + /// + /// [`IntoLua`]: crate::IntoLua + /// [`IntoLuaMulti`]: crate::IntoLuaMulti + pub fn create_function(&self, func: F) -> Result + where + F: Fn(&Lua, A) -> Result + MaybeSend + 'static, + A: FromLuaMulti, + R: IntoLuaMulti, + { + (self.lock()).create_callback(Box::new(move |rawlua, nargs| unsafe { + let args = A::from_stack_args(nargs, 1, None, rawlua)?; + func(rawlua.lua(), args)?.push_into_stack_multi(rawlua) + })) + } + + /// Wraps a Rust mutable closure, creating a callable Lua function handle to it. + /// + /// This is a version of [`create_function`] that accepts a FnMut argument. Refer to + /// [`create_function`] for more information about the implementation. + /// + /// [`create_function`]: #method.create_function + pub fn create_function_mut(&self, func: F) -> Result + where + F: FnMut(&Lua, A) -> Result + MaybeSend + 'static, + A: FromLuaMulti, + R: IntoLuaMulti, + { + let func = RefCell::new(func); + self.create_function(move |lua, args| { + (*func + .try_borrow_mut() + .map_err(|_| Error::RecursiveMutCallback)?)(lua, args) + }) + } + + /// Wraps a C function, creating a callable Lua function handle to it. + /// + /// # Safety + /// This function is unsafe because provides a way to execute unsafe C function. + pub unsafe fn create_c_function(&self, func: ffi::lua_CFunction) -> Result { + let lua = self.lock(); + ffi::lua_pushcfunction(lua.ref_thread(), func); + Ok(Function(lua.pop_ref_thread())) + } + + /// Wraps a Rust async function or closure, creating a callable Lua function handle to it. + /// + /// While executing the function Rust will poll Future and if the result is not ready, call + /// `yield()` passing internal representation of a `Poll::Pending` value. + /// + /// The function must be called inside Lua coroutine ([`Thread`]) to be able to suspend its execution. + /// An executor should be used to poll [`AsyncThread`] and mlua will take a provided Waker + /// in that case. Otherwise noop waker will be used if try to call the function outside of Rust + /// executors. + /// + /// The family of `call_async()` functions takes care about creating [`Thread`]. + /// + /// Requires `feature = "async"` + /// + /// # Examples + /// + /// Non blocking sleep: + /// + /// ``` + /// use std::time::Duration; + /// use mlua::{Lua, Result}; + /// + /// async fn sleep(_lua: &Lua, n: u64) -> Result<&'static str> { + /// tokio::time::sleep(Duration::from_millis(n)).await; + /// Ok("done") + /// } + /// + /// #[tokio::main] + /// async fn main() -> Result<()> { + /// let lua = Lua::new(); + /// lua.globals().set("sleep", lua.create_async_function(sleep)?)?; + /// let res: String = lua.load("return sleep(...)").call_async(100).await?; // Sleep 100ms + /// assert_eq!(res, "done"); + /// Ok(()) + /// } + /// ``` + /// + /// [`Thread`]: crate::Thread + /// [`AsyncThread`]: crate::AsyncThread + #[cfg(feature = "async")] + #[cfg_attr(docsrs, doc(cfg(feature = "async")))] + pub fn create_async_function<'lua, 'a, F, A, FR, R>(&'lua self, func: F) -> Result + where + 'lua: 'a, + F: Fn(&'a Lua, A) -> FR + MaybeSend + 'static, + A: FromLuaMulti, + FR: Future> + 'a, + R: IntoLuaMulti, + { + (self.lock()).create_async_callback(Box::new(move |rawlua, args| unsafe { + let lua = rawlua.lua(); + let args = match A::from_lua_args(args, 1, None, lua) { + Ok(args) => args, + Err(e) => return Box::pin(future::ready(Err(e))), + }; + let fut = func(lua, args); + Box::pin(async move { fut.await?.push_into_stack_multi(rawlua) }) + })) + } + + /// Wraps a Lua function into a new thread (or coroutine). + /// + /// Equivalent to `coroutine.create`. + pub fn create_thread(&self, func: Function) -> Result { + unsafe { self.lock().create_thread(&func) } + } + + /// Creates a Lua userdata object from a custom userdata type. + /// + /// All userdata instances of the same type `T` shares the same metatable. + #[inline] + pub fn create_userdata(&self, data: T) -> Result + where + T: UserData + MaybeSend + 'static, + { + unsafe { self.lock().make_userdata(UserDataVariant::new(data)) } + } + + /// Creates a Lua userdata object from a custom serializable userdata type. + /// + /// Requires `feature = "serialize"` + #[cfg(feature = "serialize")] + #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] + #[inline] + pub fn create_ser_userdata(&self, data: T) -> Result + where + T: UserData + Serialize + MaybeSend + 'static, + { + unsafe { self.lock().make_userdata(UserDataVariant::new_ser(data)) } + } + + /// Creates a Lua userdata object from a custom Rust type. + /// + /// You can register the type using [`Lua::register_userdata_type()`] to add fields or methods + /// _before_ calling this method. + /// Otherwise, the userdata object will have an empty metatable. + /// + /// All userdata instances of the same type `T` shares the same metatable. + #[inline] + pub fn create_any_userdata(&self, data: T) -> Result + where + T: MaybeSend + 'static, + { + unsafe { self.lock().make_any_userdata(UserDataVariant::new(data)) } + } + + /// Creates a Lua userdata object from a custom serializable Rust type. + /// + /// See [`Lua::create_any_userdata()`] for more details. + /// + /// Requires `feature = "serialize"` + #[cfg(feature = "serialize")] + #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] + #[inline] + pub fn create_ser_any_userdata(&self, data: T) -> Result + where + T: Serialize + MaybeSend + 'static, + { + unsafe { (self.lock()).make_any_userdata(UserDataVariant::new_ser(data)) } + } + + /// Registers a custom Rust type in Lua to use in userdata objects. + /// + /// This methods provides a way to add fields or methods to userdata objects of a type `T`. + pub fn register_userdata_type( + &self, + f: impl FnOnce(&mut UserDataRegistry), + ) -> Result<()> { + let mut registry = UserDataRegistry::new(); + f(&mut registry); + + let lua = self.lock(); + unsafe { + // Deregister the type if it already registered + let type_id = TypeId::of::(); + if let Some(&table_id) = (*lua.extra.get()).registered_userdata.get(&type_id) { + ffi::luaL_unref(lua.state(), ffi::LUA_REGISTRYINDEX, table_id); + } + + // Register the type + lua.register_userdata_metatable(registry)?; + } + Ok(()) + } + + /// Create a Lua userdata "proxy" object from a custom userdata type. + /// + /// Proxy object is an empty userdata object that has `T` metatable attached. + /// The main purpose of this object is to provide access to static fields and functions + /// without creating an instance of type `T`. + /// + /// You can get or set uservalues on this object but you cannot borrow any Rust type. + /// + /// # Examples + /// + /// ``` + /// # use mlua::{Lua, Result, UserData, UserDataFields, UserDataMethods}; + /// # fn main() -> Result<()> { + /// # let lua = Lua::new(); + /// struct MyUserData(i32); + /// + /// impl UserData for MyUserData { + /// fn add_fields<'a, F: UserDataFields<'a, Self>>(fields: &mut F) { + /// fields.add_field_method_get("val", |_, this| Ok(this.0)); + /// } + /// + /// fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { + /// methods.add_function("new", |_, value: i32| Ok(MyUserData(value))); + /// } + /// } + /// + /// lua.globals().set("MyUserData", lua.create_proxy::()?)?; + /// + /// lua.load("assert(MyUserData.new(321).val == 321)").exec()?; + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn create_proxy(&self) -> Result + where + T: UserData + 'static, + { + let ud = UserDataProxy::(PhantomData); + unsafe { self.lock().make_userdata(UserDataVariant::new(ud)) } + } + + /// Sets the metatable for a Luau builtin vector type. + #[cfg(any(all(feature = "luau", feature = "unstable"), doc))] + #[cfg_attr(docsrs, doc(cfg(all(feature = "luau", feature = "unstable"))))] + pub fn set_vector_metatable(&self, metatable: Option
) { + let lua = self.lock(); + unsafe { + let state = lua.state(); + let _sg = StackGuard::new(state); + assert_stack(state, 2); + + #[cfg(not(feature = "luau-vector4"))] + ffi::lua_pushvector(state, 0., 0., 0.); + #[cfg(feature = "luau-vector4")] + ffi::lua_pushvector(state, 0., 0., 0., 0.); + match metatable { + Some(metatable) => lua.push_ref(&metatable.0), + None => ffi::lua_pushnil(state), + }; + ffi::lua_setmetatable(state, -2); + } + } + + /// Returns a handle to the global environment. + pub fn globals(&self) -> Table { + let lua = self.lock(); + let state = lua.state(); + unsafe { + let _sg = StackGuard::new(state); + assert_stack(state, 1); + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS); + #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] + ffi::lua_pushvalue(state, ffi::LUA_GLOBALSINDEX); + Table(lua.pop_ref()) + } + } + + /// Returns a handle to the active `Thread`. For calls to `Lua` this will be the main Lua thread, + /// for parameters given to a callback, this will be whatever Lua thread called the callback. + pub fn current_thread(&self) -> Thread { + let lua = self.lock(); + let state = lua.state(); + unsafe { + let _sg = StackGuard::new(state); + assert_stack(state, 1); + ffi::lua_pushthread(state); + Thread(lua.pop_ref(), state) + } + } + + /// Calls the given function with a `Scope` parameter, giving the function the ability to create + /// userdata and callbacks from rust types that are !Send or non-'static. + /// + /// The lifetime of any function or userdata created through `Scope` lasts only until the + /// completion of this method call, on completion all such created values are automatically + /// dropped and Lua references to them are invalidated. If a script accesses a value created + /// through `Scope` outside of this method, a Lua error will result. Since we can ensure the + /// lifetime of values created through `Scope`, and we know that `Lua` cannot be sent to another + /// thread while `Scope` is live, it is safe to allow !Send datatypes and whose lifetimes only + /// outlive the scope lifetime. + /// + /// Inside the scope callback, all handles created through Scope will share the same unique 'lua + /// lifetime of the parent `Lua`. This allows scoped and non-scoped values to be mixed in + /// API calls, which is very useful (e.g. passing a scoped userdata to a non-scoped function). + /// However, this also enables handles to scoped values to be trivially leaked from the given + /// callback. This is not dangerous, though! After the callback returns, all scoped values are + /// invalidated, which means that though references may exist, the Rust types backing them have + /// dropped. `Function` types will error when called, and `AnyUserData` will be typeless. It + /// would be impossible to prevent handles to scoped values from escaping anyway, since you + /// would always be able to smuggle them through Lua state. + // pub fn scope<'lua, 'scope, R>( + // &'lua self, + // f: impl FnOnce(&Scope<'lua, 'scope>) -> Result, + // ) -> Result + // where + // 'lua: 'scope, + // { + // f(&Scope::new(self)) + // } + + /// Attempts to coerce a Lua value into a String in a manner consistent with Lua's internal + /// behavior. + /// + /// To succeed, the value must be a string (in which case this is a no-op), an integer, or a + /// number. + pub fn coerce_string(&self, v: Value) -> Result> { + Ok(match v { + Value::String(s) => Some(s), + v => unsafe { + let lua = self.lock(); + let state = lua.state(); + let _sg = StackGuard::new(state); + check_stack(state, 4)?; + + lua.push_value(&v)?; + let res = if lua.unlikely_memory_error() { + ffi::lua_tolstring(state, -1, ptr::null_mut()) + } else { + protect_lua!(state, 1, 1, |state| { + ffi::lua_tolstring(state, -1, ptr::null_mut()) + })? + }; + if !res.is_null() { + Some(String(lua.pop_ref())) + } else { + None + } + }, + }) + } + + /// Attempts to coerce a Lua value into an integer in a manner consistent with Lua's internal + /// behavior. + /// + /// To succeed, the value must be an integer, a floating point number that has an exact + /// representation as an integer, or a string that can be converted to an integer. Refer to the + /// Lua manual for details. + pub fn coerce_integer(&self, v: Value) -> Result> { + Ok(match v { + Value::Integer(i) => Some(i), + v => unsafe { + let lua = self.lock(); + let state = lua.state(); + let _sg = StackGuard::new(state); + check_stack(state, 2)?; + + lua.push_value(&v)?; + let mut isint = 0; + let i = ffi::lua_tointegerx(state, -1, &mut isint); + if isint == 0 { + None + } else { + Some(i) + } + }, + }) + } + + /// Attempts to coerce a Lua value into a Number in a manner consistent with Lua's internal + /// behavior. + /// + /// To succeed, the value must be a number or a string that can be converted to a number. Refer + /// to the Lua manual for details. + pub fn coerce_number(&self, v: Value) -> Result> { + Ok(match v { + Value::Number(n) => Some(n), + v => unsafe { + let lua = self.lock(); + let state = lua.state(); + let _sg = StackGuard::new(state); + check_stack(state, 2)?; + + lua.push_value(&v)?; + let mut isnum = 0; + let n = ffi::lua_tonumberx(state, -1, &mut isnum); + if isnum == 0 { + None + } else { + Some(n) + } + }, + }) + } + + /// Converts a value that implements `IntoLua` into a `Value` instance. + #[inline] + pub fn pack(&self, t: T) -> Result { + t.into_lua(self) + } + + /// Converts a `Value` instance into a value that implements `FromLua`. + #[inline] + pub fn unpack(&self, value: Value) -> Result { + T::from_lua(value, self) + } + + /// Converts a value that implements `IntoLuaMulti` into a `MultiValue` instance. + #[inline] + pub fn pack_multi(&self, t: T) -> Result { + t.into_lua_multi(self) + } + + /// Converts a `MultiValue` instance into a value that implements `FromLuaMulti`. + #[inline] + pub fn unpack_multi(&self, value: MultiValue) -> Result { + T::from_lua_multi(value, self) + } + + /// Set a value in the Lua registry based on a string name. + /// + /// This value will be available to rust from all `Lua` instances which share the same main + /// state. + pub fn set_named_registry_value(&self, name: &str, t: T) -> Result<()> + where + T: IntoLua, + { + let lua = self.lock(); + let state = lua.state(); + unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 5)?; + + lua.push(t)?; + rawset_field(state, ffi::LUA_REGISTRYINDEX, name) + } + } + + /// Get a value from the Lua registry based on a string name. + /// + /// Any Lua instance which shares the underlying main state may call this method to + /// get a value previously set by [`Lua::set_named_registry_value`]. + pub fn named_registry_value(&self, name: &str) -> Result + where + T: FromLua, + { + let lua = self.lock(); + let state = lua.state(); + unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 3)?; + + let protect = !lua.unlikely_memory_error(); + push_string(state, name.as_bytes(), protect)?; + ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX); + + T::from_stack(-1, &lua) + } + } + + /// Removes a named value in the Lua registry. + /// + /// Equivalent to calling [`Lua::set_named_registry_value`] with a value of Nil. + pub fn unset_named_registry_value(&self, name: &str) -> Result<()> { + self.set_named_registry_value(name, Nil) + } + + /// Place a value in the Lua registry with an auto-generated key. + /// + /// This value will be available to Rust from all `Lua` instances which share the same main + /// state. + /// + /// Be warned, garbage collection of values held inside the registry is not automatic, see + /// [`RegistryKey`] for more details. + /// However, dropped [`RegistryKey`]s automatically reused to store new values. + pub fn create_registry_value(&self, t: T) -> Result { + let lua = self.lock(); + let state = lua.state(); + unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 4)?; + + lua.push(t)?; + + let unref_list = (*lua.extra.get()).registry_unref_list.clone(); + + // Check if the value is nil (no need to store it in the registry) + if ffi::lua_isnil(state, -1) != 0 { + return Ok(RegistryKey::new(ffi::LUA_REFNIL, unref_list)); + } + + // Try to reuse previously allocated slot + let free_registry_id = unref_list.lock().as_mut().and_then(|x| x.pop()); + if let Some(registry_id) = free_registry_id { + // It must be safe to replace the value without triggering memory error + ffi::lua_rawseti(state, ffi::LUA_REGISTRYINDEX, registry_id as Integer); + return Ok(RegistryKey::new(registry_id, unref_list)); + } + + // Allocate a new RegistryKey slot + let registry_id = if lua.unlikely_memory_error() { + ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX) + } else { + protect_lua!(state, 1, 0, |state| { + ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX) + })? + }; + Ok(RegistryKey::new(registry_id, unref_list)) + } + } + + /// Get a value from the Lua registry by its `RegistryKey` + /// + /// Any Lua instance which shares the underlying main state may call this method to get a value + /// previously placed by [`Lua::create_registry_value`]. + pub fn registry_value(&self, key: &RegistryKey) -> Result { + let lua = self.lock(); + if !lua.owns_registry_value(key) { + return Err(Error::MismatchedRegistryKey); + } + + let state = lua.state(); + match key.id() { + ffi::LUA_REFNIL => T::from_lua(Value::Nil, self), + registry_id => unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 1)?; + + ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, registry_id as Integer); + T::from_stack(-1, &lua) + }, + } + } + + /// Removes a value from the Lua registry. + /// + /// You may call this function to manually remove a value placed in the registry with + /// [`Lua::create_registry_value`]. In addition to manual [`RegistryKey`] removal, you can also call + /// [`Lua::expire_registry_values`] to automatically remove values from the registry whose + /// [`RegistryKey`]s have been dropped. + pub fn remove_registry_value(&self, key: RegistryKey) -> Result<()> { + let lua = self.lock(); + if !lua.owns_registry_value(&key) { + return Err(Error::MismatchedRegistryKey); + } + + unsafe { + ffi::luaL_unref(lua.state(), ffi::LUA_REGISTRYINDEX, key.take()); + } + Ok(()) + } + + /// Replaces a value in the Lua registry by its [`RegistryKey`]. + /// + /// See [`Lua::create_registry_value`] for more details. + pub fn replace_registry_value(&self, key: &RegistryKey, t: T) -> Result<()> { + let lua = self.lock(); + if !lua.owns_registry_value(key) { + return Err(Error::MismatchedRegistryKey); + } + + let t = t.into_lua(self)?; + + let state = lua.state(); + unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 2)?; + + match (t, key.id()) { + (Value::Nil, ffi::LUA_REFNIL) => { + // Do nothing, no need to replace nil with nil + } + (Value::Nil, registry_id) => { + // Remove the value + ffi::luaL_unref(state, ffi::LUA_REGISTRYINDEX, registry_id); + key.set_id(ffi::LUA_REFNIL); + } + (value, ffi::LUA_REFNIL) => { + // Allocate a new `RegistryKey` + let new_key = self.create_registry_value(value)?; + key.set_id(new_key.take()); + } + (value, registry_id) => { + // It must be safe to replace the value without triggering memory error + lua.push_value(&value)?; + ffi::lua_rawseti(state, ffi::LUA_REGISTRYINDEX, registry_id as Integer); + } + } + } + Ok(()) + } + + /// Returns true if the given [`RegistryKey`] was created by a [`Lua`] which shares the underlying + /// main state with this [`Lua`] instance. + /// + /// Other than this, methods that accept a [`RegistryKey`] will return + /// [`Error::MismatchedRegistryKey`] if passed a [`RegistryKey`] that was not created with a + /// matching [`Lua`] state. + #[inline] + pub fn owns_registry_value(&self, key: &RegistryKey) -> bool { + self.lock().owns_registry_value(key) + } + + /// Remove any registry values whose [`RegistryKey`]s have all been dropped. + /// + /// Unlike normal handle values, [`RegistryKey`]s do not automatically remove themselves on Drop, + /// but you can call this method to remove any unreachable registry values not manually removed + /// by [`Lua::remove_registry_value`]. + pub fn expire_registry_values(&self) { + let lua = self.lock(); + let state = lua.state(); + unsafe { + let mut unref_list = (*lua.extra.get()).registry_unref_list.lock(); + let unref_list = mem::replace(&mut *unref_list, Some(Vec::new())); + for id in mlua_expect!(unref_list, "unref list not set") { + ffi::luaL_unref(state, ffi::LUA_REGISTRYINDEX, id); + } + } + } + + /// Sets or replaces an application data object of type `T`. + /// + /// Application data could be accessed at any time by using [`Lua::app_data_ref`] or [`Lua::app_data_mut`] + /// methods where `T` is the data type. + /// + /// # Panics + /// + /// Panics if the app data container is currently borrowed. + /// + /// # Examples + /// + /// ``` + /// use mlua::{Lua, Result}; + /// + /// fn hello(lua: &Lua, _: ()) -> Result<()> { + /// let mut s = lua.app_data_mut::<&str>().unwrap(); + /// assert_eq!(*s, "hello"); + /// *s = "world"; + /// Ok(()) + /// } + /// + /// fn main() -> Result<()> { + /// let lua = Lua::new(); + /// lua.set_app_data("hello"); + /// lua.create_function(hello)?.call(())?; + /// let s = lua.app_data_ref::<&str>().unwrap(); + /// assert_eq!(*s, "world"); + /// Ok(()) + /// } + /// ``` + #[track_caller] + pub fn set_app_data(&self, data: T) -> Option { + let lua = self.lock(); + let extra = unsafe { &*lua.extra.get() }; + extra.app_data.insert(data) + } + + /// Tries to set or replace an application data object of type `T`. + /// + /// Returns: + /// - `Ok(Some(old_data))` if the data object of type `T` was successfully replaced. + /// - `Ok(None)` if the data object of type `T` was successfully inserted. + /// - `Err(data)` if the data object of type `T` was not inserted because the container is currently borrowed. + /// + /// See [`Lua::set_app_data()`] for examples. + pub fn try_set_app_data(&self, data: T) -> StdResult, T> { + let lua = self.lock(); + let extra = unsafe { &*lua.extra.get() }; + extra.app_data.try_insert(data) + } + + /// Gets a reference to an application data object stored by [`Lua::set_app_data()`] of type `T`. + /// + /// # Panics + /// + /// Panics if the data object of type `T` is currently mutably borrowed. Multiple immutable reads + /// can be taken out at the same time. + #[track_caller] + pub fn app_data_ref(&self) -> Option> { + let guard = self.lock_arc(); + let extra = unsafe { &*guard.extra.get() }; + extra.app_data.borrow(Some(guard)) + } + + /// Gets a mutable reference to an application data object stored by [`Lua::set_app_data()`] of type `T`. + /// + /// # Panics + /// + /// Panics if the data object of type `T` is currently borrowed. + #[track_caller] + pub fn app_data_mut(&self) -> Option> { + let guard = self.lock_arc(); + let extra = unsafe { &*guard.extra.get() }; + extra.app_data.borrow_mut(Some(guard)) + } + + /// Removes an application data of type `T`. + /// + /// # Panics + /// + /// Panics if the app data container is currently borrowed. + #[track_caller] + pub fn remove_app_data(&self) -> Option { + let lua = self.lock(); + let extra = unsafe { &*lua.extra.get() }; + extra.app_data.remove() + } + + /// Pushes a value that implements `IntoLua` onto the Lua stack. + /// + /// Uses 2 stack spaces, does not call checkstack. + #[doc(hidden)] + #[inline(always)] + pub unsafe fn push(&self, value: impl IntoLua) -> Result<()> { + self.lock().push(value) + } + + /// Returns an internal `Poll::Pending` constant used for executing async callbacks. + #[cfg(feature = "async")] + #[doc(hidden)] + #[inline(always)] + pub fn poll_pending() -> LightUserData { + static ASYNC_POLL_PENDING: u8 = 0; + LightUserData(&ASYNC_POLL_PENDING as *const u8 as *mut c_void) + } + + // Luau version located in `luau/mod.rs` + #[cfg(not(feature = "luau"))] + fn disable_c_modules(&self) -> Result<()> { + let package: Table = self.globals().get("package")?; + + package.set( + "loadlib", + self.create_function(|_, ()| -> Result<()> { + Err(Error::SafetyError( + "package.loadlib is disabled in safe mode".to_string(), + )) + })?, + )?; + + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + let searchers: Table = package.get("searchers")?; + #[cfg(any(feature = "lua51", feature = "luajit"))] + let searchers: Table = package.get("loaders")?; + + let loader = self.create_function(|_, ()| Ok("\n\tcan't load C modules in safe mode"))?; + + // The third and fourth searchers looks for a loader as a C library + searchers.raw_set(3, loader)?; + searchers.raw_remove(4)?; + + Ok(()) + } + + #[inline(always)] + pub(crate) fn lock(&self) -> ReentrantMutexGuard { + self.0.lock() + } + + #[inline(always)] + pub(crate) fn lock_arc(&self) -> LuaGuard { + LuaGuard(self.0.lock_arc()) + } + + #[inline(always)] + pub(crate) fn weak(&self) -> WeakLua { + WeakLua(Arc::downgrade(&self.0)) + } +} + +impl WeakLua { + #[track_caller] + #[inline(always)] + pub(crate) fn lock(&self) -> LuaGuard { + LuaGuard::new(self.0.upgrade().unwrap()) + } + + #[inline(always)] + pub(crate) fn try_lock(&self) -> Option { + Some(LuaGuard::new(self.0.upgrade()?)) + } +} + +impl PartialEq for WeakLua { + fn eq(&self, other: &Self) -> bool { + Weak::ptr_eq(&self.0, &other.0) + } +} + +impl Eq for WeakLua {} + +impl LuaGuard { + pub(crate) fn new(handle: Arc>) -> Self { + Self(handle.lock_arc()) + } +} + +impl Deref for LuaGuard { + type Target = RawLua; + + fn deref(&self) -> &Self::Target { + &*self.0 + } +} + +pub(crate) mod extra; +mod raw; +pub(crate) mod util; + +// #[cfg(test)] +// mod assertions { +// use super::*; + +// // Lua has lots of interior mutability, should not be RefUnwindSafe +// static_assertions::assert_not_impl_any!(Lua: std::panic::RefUnwindSafe); + +// #[cfg(not(feature = "send"))] +// static_assertions::assert_not_impl_any!(Lua: Send); +// #[cfg(feature = "send")] +// static_assertions::assert_impl_all!(Lua: Send); +// } diff --git a/src/state/extra.rs b/src/state/extra.rs new file mode 100644 index 0000000..2a075b4 --- /dev/null +++ b/src/state/extra.rs @@ -0,0 +1,236 @@ +use std::any::TypeId; +use std::cell::UnsafeCell; +// use std::collections::VecDeque; +use std::mem::{self, MaybeUninit}; +use std::os::raw::{c_int, c_void}; +use std::ptr; +use std::sync::{Arc, Weak}; + +use parking_lot::{Mutex, ReentrantMutex}; +use rustc_hash::FxHashMap; + +use crate::error::Result; +use crate::state::RawLua; +use crate::stdlib::StdLib; +use crate::types::AppData; +use crate::util::{get_gc_metatable, push_gc_userdata, WrappedFailure}; + +#[cfg(any(feature = "luau", doc))] +use crate::chunk::Compiler; + +#[cfg(feature = "async")] +use {futures_util::task::noop_waker_ref, std::ptr::NonNull, std::task::Waker}; + +use super::{Lua, WeakLua}; + +// Unique key to store `ExtraData` in the registry +static EXTRA_REGISTRY_KEY: u8 = 0; + +const WRAPPED_FAILURE_POOL_SIZE: usize = 64; +// const MULTIVALUE_POOL_SIZE: usize = 64; +const REF_STACK_RESERVE: c_int = 1; + +/// Data associated with the Lua state. +pub(crate) struct ExtraData { + // Same layout as `Lua` + pub(super) lua: MaybeUninit>>, + // Same layout as `WeakLua` + pub(super) weak: MaybeUninit>>, + + pub(super) registered_userdata: FxHashMap, + pub(super) registered_userdata_mt: FxHashMap<*const c_void, Option>, + pub(super) last_checked_userdata_mt: (*const c_void, Option), + + // When Lua instance dropped, setting `None` would prevent collecting `RegistryKey`s + pub(super) registry_unref_list: Arc>>>, + + // Container to store arbitrary data (extensions) + pub(super) app_data: AppData, + + pub(super) safe: bool, + pub(super) libs: StdLib, + #[cfg(feature = "module")] + pub(super) skip_memory_check: bool, + + // Auxiliary thread to store references + pub(super) ref_thread: *mut ffi::lua_State, + pub(super) ref_stack_size: c_int, + pub(super) ref_stack_top: c_int, + pub(super) ref_free: Vec, + + // Pool of `WrappedFailure` enums in the ref thread (as userdata) + pub(super) wrapped_failure_pool: Vec, + // Pool of `MultiValue` containers + // multivalue_pool: Vec>, + // Pool of `Thread`s (coroutines) for async execution + #[cfg(feature = "async")] + pub(super) thread_pool: Vec, + + // Address of `WrappedFailure` metatable + pub(super) wrapped_failure_mt_ptr: *const c_void, + + // Waker for polling futures + #[cfg(feature = "async")] + pub(super) waker: NonNull, + + #[cfg(not(feature = "luau"))] + pub(super) hook_callback: Option, + #[cfg(not(feature = "luau"))] + pub(super) hook_thread: *mut ffi::lua_State, + #[cfg(feature = "lua54")] + pub(super) warn_callback: Option, + #[cfg(feature = "luau")] + pub(super) interrupt_callback: Option, + + #[cfg(feature = "luau")] + pub(super) sandboxed: bool, + #[cfg(feature = "luau")] + pub(super) compiler: Option, + #[cfg(feature = "luau-jit")] + pub(super) enable_jit: bool, +} + +impl Drop for ExtraData { + fn drop(&mut self) { + #[cfg(feature = "module")] + unsafe { + self.inner.assume_init_drop(); + } + unsafe { self.weak.assume_init_drop() }; + *self.registry_unref_list.lock() = None; + } +} + +impl ExtraData { + // Index of `error_traceback` function in auxiliary thread stack + #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] + pub(super) const ERROR_TRACEBACK_IDX: c_int = 1; + + pub(super) unsafe fn init(state: *mut ffi::lua_State) -> Arc> { + // Create ref stack thread and place it in the registry to prevent it + // from being garbage collected. + let ref_thread = mlua_expect!( + protect_lua!(state, 0, 0, |state| { + let thread = ffi::lua_newthread(state); + ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX); + thread + }), + "Error while creating ref thread", + ); + + let wrapped_failure_mt_ptr = { + get_gc_metatable::(state); + let ptr = ffi::lua_topointer(state, -1); + ffi::lua_pop(state, 1); + ptr + }; + + // Store `error_traceback` function on the ref stack + #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] + { + ffi::lua_pushcfunction(ref_thread, crate::util::error_traceback); + assert_eq!(ffi::lua_gettop(ref_thread), Self::ERROR_TRACEBACK_IDX); + } + + let extra = Arc::new(UnsafeCell::new(ExtraData { + lua: MaybeUninit::uninit(), + weak: MaybeUninit::uninit(), + registered_userdata: FxHashMap::default(), + registered_userdata_mt: FxHashMap::default(), + last_checked_userdata_mt: (ptr::null(), None), + registry_unref_list: Arc::new(Mutex::new(Some(Vec::new()))), + app_data: AppData::default(), + safe: false, + libs: StdLib::NONE, + #[cfg(feature = "module")] + skip_memory_check: false, + ref_thread, + // We need some reserved stack space to move values in and out of the ref stack. + ref_stack_size: ffi::LUA_MINSTACK - REF_STACK_RESERVE, + ref_stack_top: ffi::lua_gettop(ref_thread), + ref_free: Vec::new(), + wrapped_failure_pool: Vec::with_capacity(WRAPPED_FAILURE_POOL_SIZE), + // multivalue_pool: Vec::with_capacity(MULTIVALUE_POOL_SIZE), + #[cfg(feature = "async")] + thread_pool: Vec::new(), + wrapped_failure_mt_ptr, + #[cfg(feature = "async")] + waker: NonNull::from(noop_waker_ref()), + #[cfg(not(feature = "luau"))] + hook_callback: None, + #[cfg(not(feature = "luau"))] + hook_thread: ptr::null_mut(), + #[cfg(feature = "lua54")] + warn_callback: None, + #[cfg(feature = "luau")] + interrupt_callback: None, + #[cfg(feature = "luau")] + sandboxed: false, + #[cfg(feature = "luau")] + compiler: None, + #[cfg(feature = "luau-jit")] + enable_jit: true, + })); + + // Store it in the registry + mlua_expect!(Self::store(&extra, state), "Error while storing extra data"); + + extra + } + + pub(super) unsafe fn set_lua(&mut self, lua: &Arc>) { + self.lua.write(Arc::clone(lua)); + if cfg!(not(feature = "module")) { + Arc::decrement_strong_count(Arc::as_ptr(lua)); + } + self.weak.write(Arc::downgrade(lua)); + } + + pub(super) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self { + #[cfg(feature = "luau")] + if cfg!(not(feature = "module")) { + // In the main app we can use `lua_callbacks` to access ExtraData + return (*ffi::lua_callbacks(state)).userdata as *mut _; + } + + 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 { + // `ExtraData` can be null only when Lua state is foreign. + // This case in used in `Lua::try_from_ptr()`. + ffi::lua_pop(state, 1); + return ptr::null_mut(); + } + let extra_ptr = ffi::lua_touserdata(state, -1) as *mut Arc>; + ffi::lua_pop(state, 1); + (*extra_ptr).get() + } + + unsafe fn store(extra: &Arc>, state: *mut ffi::lua_State) -> Result<()> { + #[cfg(feature = "luau")] + if cfg!(not(feature = "module")) { + (*ffi::lua_callbacks(state)).userdata = extra.get() as *mut _; + return Ok(()); + } + + push_gc_userdata(state, Arc::clone(extra), true)?; + protect_lua!(state, 1, 0, fn(state) { + let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void; + ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, extra_key); + }) + } + + #[inline(always)] + pub(super) unsafe fn lua(&self) -> &Lua { + mem::transmute(self.lua.assume_init_ref()) + } + + #[inline(always)] + pub(super) unsafe fn raw_lua(&self) -> &RawLua { + &*self.lua.assume_init_ref().data_ptr() + } + + #[inline(always)] + pub(super) unsafe fn weak(&self) -> &WeakLua { + mem::transmute(self.weak.assume_init_ref()) + } +} diff --git a/src/state/raw.rs b/src/state/raw.rs new file mode 100644 index 0000000..d47cf31 --- /dev/null +++ b/src/state/raw.rs @@ -0,0 +1,1421 @@ +use std::any::TypeId; +use std::cell::{Cell, UnsafeCell}; +use std::ffi::{CStr, CString}; +use std::os::raw::{c_char, c_int, c_void}; +use std::panic::resume_unwind; +use std::result::Result as StdResult; +use std::sync::Arc; +use std::{mem, ptr}; + +use parking_lot::ReentrantMutex; + +use crate::chunk::ChunkMode; +use crate::error::{Error, Result}; +use crate::function::Function; +use crate::memory::{MemoryState, ALLOCATOR}; +use crate::state::util::{callback_error_ext, ref_stack_pop, StateGuard}; +use crate::stdlib::StdLib; +use crate::string::String; +use crate::table::Table; +use crate::thread::Thread; +use crate::types::{ + AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer, + LightUserData, MaybeSend, RegistryKey, SubtypeId, ValueRef, +}; +use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataRegistry, UserDataVariant}; +use crate::util::{ + assert_stack, check_stack, get_destructed_userdata_metatable, get_gc_userdata, get_main_state, + get_userdata, init_error_registry, init_gc_metatable, init_userdata_metatable, pop_error, + push_gc_userdata, push_string, push_table, rawset_field, safe_pcall, safe_xpcall, + short_type_name, StackGuard, WrappedFailure, +}; +use crate::value::{FromLuaMulti, IntoLua, MultiValue, Nil, Value}; + +use super::extra::ExtraData; +use super::{Lua, LuaOptions, WeakLua}; + +#[cfg(not(feature = "luau"))] +use crate::hook::{Debug, HookTriggers}; + +#[cfg(feature = "async")] +use { + crate::types::{AsyncCallback, AsyncCallbackUpvalue, AsyncPollUpvalue}, + std::ptr::NonNull, + std::task::{Context, Poll, Waker}, +}; + +/// An inner Lua struct which holds a raw Lua state. +pub struct RawLua { + // The state is dynamic and depends on context + pub(super) state: Cell<*mut ffi::lua_State>, + pub(super) main_state: *mut ffi::lua_State, + pub(super) extra: Arc>, +} + +#[cfg(not(feature = "module"))] +impl Drop for RawLua { + fn drop(&mut self) { + unsafe { + let mem_state = MemoryState::get(self.main_state); + + ffi::lua_close(self.main_state); + + // Deallocate `MemoryState` + if !mem_state.is_null() { + drop(Box::from_raw(mem_state)); + } + } + } +} + +impl RawLua { + #[inline(always)] + pub(crate) fn lua(&self) -> &Lua { + unsafe { (*self.extra.get()).lua() } + } + + #[inline(always)] + pub(crate) fn weak(&self) -> &WeakLua { + unsafe { (*self.extra.get()).weak() } + } + + #[inline(always)] + pub(crate) fn state(&self) -> *mut ffi::lua_State { + self.state.get() + } + + #[cfg(feature = "luau")] + #[inline(always)] + pub(crate) fn main_state(&self) -> *mut ffi::lua_State { + self.main_state + } + + #[inline(always)] + pub(crate) fn ref_thread(&self) -> *mut ffi::lua_State { + unsafe { (*self.extra.get()).ref_thread } + } + + pub(super) unsafe fn new(libs: StdLib, options: LuaOptions) -> Arc> { + let mem_state: *mut MemoryState = Box::into_raw(Box::default()); + let mut state = ffi::lua_newstate(ALLOCATOR, mem_state as *mut c_void); + // If state is null then switch to Lua internal allocator + if state.is_null() { + drop(Box::from_raw(mem_state)); + state = ffi::luaL_newstate(); + } + assert!(!state.is_null(), "Failed to create a Lua VM"); + + ffi::luaL_requiref(state, cstr!("_G"), ffi::luaopen_base, 1); + ffi::lua_pop(state, 1); + + // Init Luau code generator (jit) + #[cfg(feature = "luau-jit")] + if ffi::luau_codegen_supported() != 0 { + ffi::luau_codegen_create(state); + } + + let rawlua = Self::init_from_ptr(state); + let extra = rawlua.lock().extra.get(); + + mlua_expect!( + load_from_std_lib(state, libs), + "Error during loading standard libraries" + ); + (*extra).libs |= libs; + + if !options.catch_rust_panics { + mlua_expect!( + (|| -> Result<()> { + let _sg = StackGuard::new(state); + + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS); + #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] + ffi::lua_pushvalue(state, ffi::LUA_GLOBALSINDEX); + + ffi::lua_pushcfunction(state, safe_pcall); + rawset_field(state, -2, "pcall")?; + + ffi::lua_pushcfunction(state, safe_xpcall); + rawset_field(state, -2, "xpcall")?; + + Ok(()) + })(), + "Error during applying option `catch_rust_panics`" + ) + } + + #[cfg(feature = "async")] + if options.thread_pool_size > 0 { + (*extra).thread_pool.reserve_exact(options.thread_pool_size); + } + + rawlua + } + + pub(super) unsafe fn init_from_ptr(state: *mut ffi::lua_State) -> Arc> { + assert!(!state.is_null(), "Lua state is NULL"); + if let Some(lua) = Self::try_from_ptr(state) { + return lua; + } + + let main_state = get_main_state(state).unwrap_or(state); + let main_state_top = ffi::lua_gettop(main_state); + + mlua_expect!( + (|state| { + init_error_registry(state)?; + + // Create the internal metatables and store them in the registry + // to prevent from being garbage collected. + + init_gc_metatable::>>(state, None)?; + init_gc_metatable::(state, None)?; + init_gc_metatable::(state, None)?; + #[cfg(feature = "async")] + { + init_gc_metatable::(state, None)?; + init_gc_metatable::(state, None)?; + init_gc_metatable::(state, None)?; + init_gc_metatable::>(state, None)?; + } + + // Init serde metatables + #[cfg(feature = "serialize")] + crate::serde::init_metatables(state)?; + + Ok::<_, Error>(()) + })(main_state), + "Error during Lua initialization", + ); + + // Init ExtraData + let extra = ExtraData::init(main_state); + + // Register `DestructedUserdata` type + get_destructed_userdata_metatable(main_state); + let destructed_mt_ptr = ffi::lua_topointer(main_state, -1); + let destructed_ud_typeid = TypeId::of::(); + (*extra.get()) + .registered_userdata_mt + .insert(destructed_mt_ptr, Some(destructed_ud_typeid)); + ffi::lua_pop(main_state, 1); + + mlua_debug_assert!( + ffi::lua_gettop(main_state) == main_state_top, + "stack leak during creation" + ); + assert_stack(main_state, ffi::LUA_MINSTACK); + + let rawlua = Arc::new(ReentrantMutex::new(RawLua { + state: Cell::new(state), + main_state, + extra: Arc::clone(&extra), + })); + (*extra.get()).set_lua(&rawlua); + + rawlua + } + + pub(super) unsafe fn try_from_ptr( + state: *mut ffi::lua_State, + ) -> Option>> { + match ExtraData::get(state) { + extra if extra.is_null() => None, + extra => Some(Arc::clone(&(*extra).lua().0)), + } + } + + /// Marks the Lua state as safe. + #[inline(always)] + pub(super) unsafe fn set_safe(&self) { + (*self.extra.get()).safe = true; + } + + /// Loads the specified subset of the standard libraries into an existing Lua state. + /// + /// Use the [`StdLib`] flags to specify the libraries you want to load. + /// + /// [`StdLib`]: crate::StdLib + pub(super) unsafe fn load_std_libs(&self, libs: StdLib) -> Result<()> { + let is_safe = (*self.extra.get()).safe; + + #[cfg(not(feature = "luau"))] + if is_safe && libs.contains(StdLib::DEBUG) { + return Err(Error::SafetyError( + "the unsafe `debug` module can't be loaded in safe mode".to_string(), + )); + } + #[cfg(feature = "luajit")] + if is_safe && libs.contains(StdLib::FFI) { + return Err(Error::SafetyError( + "the unsafe `ffi` module can't be loaded in safe mode".to_string(), + )); + } + + let res = load_from_std_lib(self.main_state, libs); + + // If `package` library loaded into a safe lua state then disable C modules + let curr_libs = (*self.extra.get()).libs; + if is_safe && (curr_libs ^ (curr_libs | libs)).contains(StdLib::PACKAGE) { + mlua_expect!( + self.lua().disable_c_modules(), + "Error during disabling C modules" + ); + } + unsafe { (*self.extra.get()).libs |= libs }; + + res + } + + /// See [`Lua::try_set_app_data`] + #[inline] + pub(crate) fn try_set_app_data( + &self, + data: T, + ) -> StdResult, T> { + let extra = unsafe { &*self.extra.get() }; + extra.app_data.try_insert(data) + } + + /// See [`Lua::app_data_ref`] + #[track_caller] + #[inline] + pub(crate) fn app_data_ref(&self) -> Option> { + let extra = unsafe { &*self.extra.get() }; + extra.app_data.borrow(None) + } + + /// See [`Lua::app_data_mut`] + #[track_caller] + #[inline] + pub(crate) fn app_data_mut(&self) -> Option> { + let extra = unsafe { &*self.extra.get() }; + extra.app_data.borrow_mut(None) + } + + /// See [`Lua::create_registry_value`] + #[inline] + pub(crate) fn owns_registry_value(&self, key: &RegistryKey) -> bool { + let registry_unref_list = unsafe { &(*self.extra.get()).registry_unref_list }; + Arc::ptr_eq(&key.unref_list, registry_unref_list) + } + + pub(crate) fn load_chunk( + &self, + name: Option<&CStr>, + env: Option
, + mode: Option, + source: &[u8], + ) -> Result { + let state = self.state(); + unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 1)?; + + let mode_str = match mode { + Some(ChunkMode::Binary) => cstr!("b"), + Some(ChunkMode::Text) => cstr!("t"), + None => cstr!("bt"), + }; + + match ffi::luaL_loadbufferx( + state, + source.as_ptr() as *const c_char, + source.len(), + name.map(|n| n.as_ptr()).unwrap_or_else(ptr::null), + mode_str, + ) { + ffi::LUA_OK => { + if let Some(env) = env { + self.push_ref(&env.0); + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + ffi::lua_setupvalue(state, -2, 1); + #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] + ffi::lua_setfenv(state, -2); + } + + #[cfg(feature = "luau-jit")] + if (*self.extra.get()).enable_jit && ffi::luau_codegen_supported() != 0 { + ffi::luau_codegen_compile(state, -1); + } + + Ok(Function(self.pop_ref())) + } + err => Err(pop_error(state, err)), + } + } + } + + /// Sets a 'hook' function for a thread (coroutine). + #[cfg(not(feature = "luau"))] + pub(crate) unsafe fn set_thread_hook( + &self, + state: *mut ffi::lua_State, + triggers: HookTriggers, + callback: F, + ) where + F: Fn(&Lua, Debug) -> Result<()> + MaybeSend + 'static, + { + unsafe extern "C-unwind" fn hook_proc(state: *mut ffi::lua_State, ar: *mut ffi::lua_Debug) { + let extra = ExtraData::get(state); + if (*extra).hook_thread != state { + // Hook was destined for a different thread, ignore + ffi::lua_sethook(state, None, 0, 0); + return; + } + callback_error_ext(state, extra, move |_| { + let hook_cb = (*extra).hook_callback.clone(); + let hook_cb = mlua_expect!(hook_cb, "no hook callback set in hook_proc"); + if Arc::strong_count(&hook_cb) > 2 { + return Ok(()); // Don't allow recursion + } + let rawlua = (*extra).raw_lua(); + let _guard = StateGuard::new(rawlua, state); + let debug = Debug::new(rawlua, ar); + hook_cb((*extra).lua(), debug) + }) + } + + (*self.extra.get()).hook_callback = Some(Arc::new(callback)); + (*self.extra.get()).hook_thread = state; // Mark for what thread the hook is set + ffi::lua_sethook(state, Some(hook_proc), triggers.mask(), triggers.count()); + } + + /// See [`Lua::create_string`] + pub(crate) unsafe fn create_string(&self, s: impl AsRef<[u8]>) -> Result { + let state = self.state(); + 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(state); + check_stack(state, 3)?; + push_string(state, s.as_ref(), true)?; + Ok(String(self.pop_ref())) + } + + /// See [`Lua::create_table_with_capacity`] + pub(crate) unsafe fn create_table_with_capacity( + &self, + narr: usize, + nrec: usize, + ) -> Result
{ + if self.unlikely_memory_error() { + push_table(self.ref_thread(), narr, nrec, false)?; + return Ok(Table(self.pop_ref_thread())); + } + + let state = self.state(); + let _sg = StackGuard::new(state); + check_stack(state, 3)?; + push_table(state, narr, nrec, true)?; + Ok(Table(self.pop_ref())) + } + + /// See [`Lua::create_sequence_from`] + pub(crate) unsafe fn create_sequence_from(&self, iter: I) -> Result
+ where + T: IntoLua, + I: IntoIterator, + { + let state = self.state(); + let _sg = StackGuard::new(state); + check_stack(state, 5)?; + + let iter = iter.into_iter(); + let lower_bound = iter.size_hint().0; + let protect = !self.unlikely_memory_error(); + push_table(state, lower_bound, 0, protect)?; + for (i, v) in iter.enumerate() { + self.push(v)?; + if protect { + protect_lua!(state, 2, 1, |state| { + ffi::lua_rawseti(state, -2, (i + 1) as Integer); + })?; + } else { + ffi::lua_rawseti(state, -2, (i + 1) as Integer); + } + } + + Ok(Table(self.pop_ref())) + } + + /// Wraps a Lua function into a new thread (or coroutine). + /// + /// Takes function by reference. + pub(crate) unsafe fn create_thread(&self, func: &Function) -> Result { + let state = self.state(); + let _sg = StackGuard::new(state); + check_stack(state, 3)?; + + let thread_state = if self.unlikely_memory_error() { + ffi::lua_newthread(state) + } else { + protect_lua!(state, 0, 1, |state| ffi::lua_newthread(state))? + }; + let thread = Thread(self.pop_ref(), thread_state); + ffi::lua_xpush(self.ref_thread(), thread_state, func.0.index); + Ok(thread) + } + + /// Wraps a Lua function into a new or recycled thread (coroutine). + #[cfg(feature = "async")] + pub(crate) unsafe fn create_recycled_thread(&self, func: &Function) -> Result { + #[cfg(any(feature = "lua54", feature = "luau"))] + if let Some(index) = (*self.extra.get()).thread_pool.pop() { + let thread_state = ffi::lua_tothread(self.ref_thread(), index); + ffi::lua_xpush(self.ref_thread(), thread_state, func.0.index); + + #[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(ValueRef::new(self, index), thread_state)); + } + + self.create_thread(func) + } + + /// Resets thread (coroutine) and returns it to the pool for later use. + #[cfg(feature = "async")] + #[cfg(any(feature = "lua54", feature = "luau"))] + pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) -> bool { + let extra = &mut *self.extra.get(); + if extra.thread_pool.len() < extra.thread_pool.capacity() { + let thread_state = ffi::lua_tothread(extra.ref_thread, thread.0.index); + #[cfg(all(feature = "lua54", not(feature = "vendored")))] + let status = ffi::lua_resetthread(thread_state); + #[cfg(all(feature = "lua54", feature = "vendored"))] + let status = ffi::lua_closethread(thread_state, self.state()); + #[cfg(feature = "lua54")] + if status != ffi::LUA_OK { + // Error object is on top, drop it + ffi::lua_settop(thread_state, 0); + } + #[cfg(feature = "luau")] + ffi::lua_resetthread(thread_state); + extra.thread_pool.push(thread.0.index); + thread.0.drop = false; + return true; + } + false + } + + // FIXME + // #[inline] + // pub(crate) fn pop_multivalue_from_pool(&self) -> Option> { + // let extra = unsafe { &mut *self.extra.get() }; + // extra.multivalue_pool.pop() + // } + + // FIXME + // #[inline] + // pub(crate) fn push_multivalue_to_pool(&self, mut multivalue: VecDeque) { + // let extra = unsafe { &mut *self.extra.get() }; + // if extra.multivalue_pool.len() < MULTIVALUE_POOL_SIZE { + // multivalue.clear(); + // extra + // .multivalue_pool + // .push(unsafe { mem::transmute(multivalue) }); + // } + // } + + /// Pushes a value that implements `IntoLua` onto the Lua stack. + /// + /// Uses 2 stack spaces, does not call checkstack. + #[doc(hidden)] + #[inline(always)] + pub unsafe fn push(&self, value: impl IntoLua) -> Result<()> { + value.push_into_stack(self) + } + + /// Pushes a `Value` (by reference) onto the Lua stack. + /// + /// Uses 2 stack spaces, does not call `checkstack`. + pub(crate) unsafe fn push_value(&self, value: &Value) -> Result<()> { + let state = self.state(); + match value { + Value::Nil => ffi::lua_pushnil(state), + Value::Boolean(b) => ffi::lua_pushboolean(state, *b as c_int), + Value::LightUserData(ud) => ffi::lua_pushlightuserdata(state, ud.0), + Value::Integer(i) => ffi::lua_pushinteger(state, *i), + Value::Number(n) => ffi::lua_pushnumber(state, *n), + #[cfg(feature = "luau")] + Value::Vector(v) => { + #[cfg(not(feature = "luau-vector4"))] + ffi::lua_pushvector(state, v.x(), v.y(), v.z()); + #[cfg(feature = "luau-vector4")] + ffi::lua_pushvector(state, v.x(), v.y(), v.z(), v.w()); + } + Value::String(s) => self.push_ref(&s.0), + Value::Table(t) => self.push_ref(&t.0), + Value::Function(f) => self.push_ref(&f.0), + Value::Thread(t) => self.push_ref(&t.0), + Value::UserData(ud) => self.push_ref(&ud.0), + Value::Error(err) => { + let protect = !self.unlikely_memory_error(); + push_gc_userdata(state, WrappedFailure::Error(*err.clone()), protect)?; + } + } + Ok(()) + } + + /// Pops a value from the Lua stack. + /// + /// Uses 2 stack spaces, does not call `checkstack`. + pub(crate) unsafe fn pop_value(&self) -> Value { + let value = self.stack_value(-1); + ffi::lua_pop(self.state(), 1); + value + } + + /// Returns value at given stack index without popping it. + /// + /// Uses 2 stack spaces, does not call checkstack. + pub(crate) unsafe fn stack_value(&self, idx: c_int) -> Value { + let state = self.state(); + match ffi::lua_type(state, idx) { + ffi::LUA_TNIL => Nil, + + ffi::LUA_TBOOLEAN => Value::Boolean(ffi::lua_toboolean(state, idx) != 0), + + ffi::LUA_TLIGHTUSERDATA => { + Value::LightUserData(LightUserData(ffi::lua_touserdata(state, idx))) + } + + #[cfg(any(feature = "lua54", feature = "lua53"))] + ffi::LUA_TNUMBER => { + if ffi::lua_isinteger(state, idx) != 0 { + Value::Integer(ffi::lua_tointeger(state, idx)) + } else { + Value::Number(ffi::lua_tonumber(state, idx)) + } + } + + #[cfg(any( + feature = "lua52", + feature = "lua51", + feature = "luajit", + feature = "luau" + ))] + ffi::LUA_TNUMBER => { + use crate::types::Number; + + let n = ffi::lua_tonumber(state, idx); + match num_traits::cast(n) { + Some(i) if (n - (i as Number)).abs() < Number::EPSILON => Value::Integer(i), + _ => Value::Number(n), + } + } + + #[cfg(feature = "luau")] + ffi::LUA_TVECTOR => { + let v = ffi::lua_tovector(state, idx); + mlua_debug_assert!(!v.is_null(), "vector is null"); + #[cfg(not(feature = "luau-vector4"))] + return Value::Vector(crate::types::Vector([*v, *v.add(1), *v.add(2)])); + #[cfg(feature = "luau-vector4")] + return Value::Vector(crate::types::Vector([*v, *v.add(1), *v.add(2), *v.add(3)])); + } + + ffi::LUA_TSTRING => { + ffi::lua_xpush(state, self.ref_thread(), idx); + Value::String(String(self.pop_ref_thread())) + } + + ffi::LUA_TTABLE => { + ffi::lua_xpush(state, self.ref_thread(), idx); + Value::Table(Table(self.pop_ref_thread())) + } + + ffi::LUA_TFUNCTION => { + ffi::lua_xpush(state, self.ref_thread(), idx); + Value::Function(Function(self.pop_ref_thread())) + } + + ffi::LUA_TUSERDATA => { + // If the userdata is `WrappedFailure`, process it as an error or panic. + let failure_mt_ptr = (*self.extra.get()).wrapped_failure_mt_ptr; + match get_gc_userdata::(state, idx, failure_mt_ptr).as_mut() { + Some(WrappedFailure::Error(err)) => Value::Error(Box::new(err.clone())), + Some(WrappedFailure::Panic(panic)) => { + if let Some(panic) = panic.take() { + resume_unwind(panic); + } + // Previously resumed panic? + Value::Nil + } + _ => { + ffi::lua_xpush(state, self.ref_thread(), idx); + Value::UserData(AnyUserData(self.pop_ref_thread(), SubtypeId::None)) + } + } + } + + ffi::LUA_TTHREAD => { + ffi::lua_xpush(state, self.ref_thread(), idx); + let thread_state = ffi::lua_tothread(self.ref_thread(), -1); + Value::Thread(Thread(self.pop_ref_thread(), thread_state)) + } + + #[cfg(feature = "luau")] + ffi::LUA_TBUFFER => { + // Buffer is represented as a userdata type + ffi::lua_xpush(state, self.ref_thread(), idx); + Value::UserData(AnyUserData(self.pop_ref_thread(), SubtypeId::Buffer)) + } + + #[cfg(feature = "luajit")] + ffi::LUA_TCDATA => { + // CData is represented as a userdata type + ffi::lua_xpush(state, self.ref_thread(), idx); + Value::UserData(AnyUserData(self.pop_ref_thread(), SubtypeId::CData)) + } + + _ => mlua_panic!("unexpected value type on stack"), + } + } + + // Pushes a ValueRef value onto the stack, uses 1 stack space, does not call checkstack + #[inline] + pub(crate) fn push_ref(&self, vref: &ValueRef) { + assert!( + self.weak() == &vref.lua, + "Lua instance passed Value created from a different main Lua state" + ); + unsafe { ffi::lua_xpush(self.ref_thread(), self.state(), vref.index) }; + } + + // Pops the topmost element of the stack and stores a reference to it. This pins the object, + // preventing garbage collection until the returned `ValueRef` is dropped. + // + // References are stored on the stack of a specially created auxiliary thread that exists only + // to store reference values. This is much faster than storing these in the registry, and also + // much more flexible and requires less bookkeeping than storing them directly in the currently + // used stack. + #[inline] + pub(crate) unsafe fn pop_ref(&self) -> ValueRef { + ffi::lua_xmove(self.state(), self.ref_thread(), 1); + let index = ref_stack_pop(self.extra.get()); + ValueRef::new(self, index) + } + + // Same as `pop_ref` but assumes the value is already on the reference thread + #[inline] + pub(crate) unsafe fn pop_ref_thread(&self) -> ValueRef { + let index = ref_stack_pop(self.extra.get()); + ValueRef::new(self, index) + } + + #[inline] + pub(crate) unsafe fn clone_ref(&self, vref: &ValueRef) -> ValueRef { + ffi::lua_pushvalue(self.ref_thread(), vref.index); + let index = ref_stack_pop(self.extra.get()); + ValueRef::new(self, index) + } + + pub(crate) unsafe fn drop_ref(&self, vref: &ValueRef) { + let ref_thread = self.ref_thread(); + ffi::lua_pushnil(ref_thread); + ffi::lua_replace(ref_thread, vref.index); + (*self.extra.get()).ref_free.push(vref.index); + } + + #[inline] + pub(crate) unsafe fn push_error_traceback(&self) { + let state = self.state(); + #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] + ffi::lua_xpush(self.ref_thread(), state, ExtraData::ERROR_TRACEBACK_IDX); + // Lua 5.2+ support light C functions that does not require extra allocations + #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] + ffi::lua_pushcfunction(state, crate::util::error_traceback); + } + + #[inline] + pub(crate) unsafe fn unlikely_memory_error(&self) -> bool { + // MemoryInfo is empty in module mode so we cannot predict memory limits + match MemoryState::get(self.main_state) { + mem_state if !mem_state.is_null() => (*mem_state).memory_limit() == 0, + #[cfg(feature = "module")] + _ => (*self.extra.get()).skip_memory_check, // Check the special flag (only for module mode) + #[cfg(not(feature = "module"))] + _ => false, + } + } + + pub(crate) unsafe fn make_userdata(&self, data: UserDataVariant) -> Result + where + T: UserData + 'static, + { + self.make_userdata_with_metatable(data, || { + // Check if userdata/metatable is already registered + let type_id = TypeId::of::(); + if let Some(&table_id) = (*self.extra.get()).registered_userdata.get(&type_id) { + return Ok(table_id as Integer); + } + + // Create a new metatable from `UserData` definition + let mut registry = UserDataRegistry::new(); + T::register(&mut registry); + + self.register_userdata_metatable(registry) + }) + } + + pub(crate) unsafe fn make_any_userdata( + &self, + data: UserDataVariant, + ) -> Result + where + T: 'static, + { + self.make_userdata_with_metatable(data, || { + // Check if userdata/metatable is already registered + let type_id = TypeId::of::(); + if let Some(&table_id) = (*self.extra.get()).registered_userdata.get(&type_id) { + return Ok(table_id as Integer); + } + + // Create an empty metatable + let registry = UserDataRegistry::new(); + self.register_userdata_metatable::(registry) + }) + } + + unsafe fn make_userdata_with_metatable( + &self, + data: UserDataVariant, + get_metatable_id: impl FnOnce() -> Result, + ) -> Result { + let state = self.state(); + let _sg = StackGuard::new(state); + check_stack(state, 3)?; + + // We push metatable first to ensure having correct metatable with `__gc` method + ffi::lua_pushnil(state); + ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, get_metatable_id()?); + let protect = !self.unlikely_memory_error(); + #[cfg(not(feature = "lua54"))] + crate::util::push_userdata(state, data, protect)?; + #[cfg(feature = "lua54")] + crate::util::push_userdata_uv( + state, + data, + crate::userdata::USER_VALUE_MAXSLOT as c_int, + protect, + )?; + ffi::lua_replace(state, -3); + ffi::lua_setmetatable(state, -2); + + // Set empty environment for Lua 5.1 + #[cfg(any(feature = "lua51", feature = "luajit"))] + if protect { + protect_lua!(state, 1, 1, fn(state) { + ffi::lua_newtable(state); + ffi::lua_setuservalue(state, -2); + })?; + } else { + ffi::lua_newtable(state); + ffi::lua_setuservalue(state, -2); + } + + Ok(AnyUserData(self.pop_ref(), SubtypeId::None)) + } + + pub(crate) unsafe fn register_userdata_metatable( + &self, + mut registry: UserDataRegistry, + ) -> Result { + let state = self.state(); + let _sg = StackGuard::new(state); + check_stack(state, 13)?; + + // Prepare metatable, add meta methods first and then meta fields + let metatable_nrec = registry.meta_methods.len() + registry.meta_fields.len(); + #[cfg(feature = "async")] + let metatable_nrec = metatable_nrec + registry.async_meta_methods.len(); + push_table(state, 0, metatable_nrec, true)?; + for (k, m) in registry.meta_methods { + self.push(self.create_callback(m)?)?; + rawset_field(state, -2, MetaMethod::validate(&k)?)?; + } + #[cfg(feature = "async")] + for (k, m) in registry.async_meta_methods { + self.push(self.create_async_callback(m)?)?; + rawset_field(state, -2, MetaMethod::validate(&k)?)?; + } + let mut has_name = false; + for (k, f) in registry.meta_fields { + has_name = has_name || k == MetaMethod::Type; + let rawlua = mem::transmute::<&RawLua, &RawLua>(self); + mlua_assert!(f(rawlua, 0)? == 1, "field function must return one value"); + rawset_field(state, -2, MetaMethod::validate(&k)?)?; + } + // Set `__name/__type` if not provided + if !has_name { + let type_name = short_type_name::(); + push_string(state, type_name.as_bytes(), !self.unlikely_memory_error())?; + rawset_field(state, -2, MetaMethod::Type.name())?; + } + let metatable_index = ffi::lua_absindex(state, -1); + + let mut extra_tables_count = 0; + + let fields_nrec = registry.fields.len(); + if fields_nrec > 0 { + // If `__index` is a table then update it in-place + let index_type = ffi::lua_getfield(state, metatable_index, cstr!("__index")); + match index_type { + ffi::LUA_TNIL | ffi::LUA_TTABLE => { + if index_type == ffi::LUA_TNIL { + // Create a new table + ffi::lua_pop(state, 1); + push_table(state, 0, fields_nrec, true)?; + } + for (k, f) in registry.fields { + let rawlua = mem::transmute::<&RawLua, &RawLua>(self); + mlua_assert!(f(rawlua, 0)? == 1, "field function must return one value"); + rawset_field(state, -2, &k)?; + } + rawset_field(state, metatable_index, "__index")?; + } + _ => { + ffi::lua_pop(state, 1); + // Propagate fields to the field getters + for (k, f) in registry.fields { + registry.field_getters.push((k, f)) + } + } + } + } + + let mut field_getters_index = None; + let field_getters_nrec = registry.field_getters.len(); + if field_getters_nrec > 0 { + push_table(state, 0, field_getters_nrec, true)?; + for (k, m) in registry.field_getters { + self.push(self.create_callback(m)?)?; + rawset_field(state, -2, &k)?; + } + field_getters_index = Some(ffi::lua_absindex(state, -1)); + extra_tables_count += 1; + } + + let mut field_setters_index = None; + let field_setters_nrec = registry.field_setters.len(); + if field_setters_nrec > 0 { + push_table(state, 0, field_setters_nrec, true)?; + for (k, m) in registry.field_setters { + self.push(self.create_callback(m)?)?; + rawset_field(state, -2, &k)?; + } + field_setters_index = Some(ffi::lua_absindex(state, -1)); + extra_tables_count += 1; + } + + let mut methods_index = None; + let methods_nrec = registry.methods.len(); + #[cfg(feature = "async")] + let methods_nrec = methods_nrec + registry.async_methods.len(); + if methods_nrec > 0 { + // If `__index` is a table then update it in-place + let index_type = ffi::lua_getfield(state, metatable_index, cstr!("__index")); + match index_type { + ffi::LUA_TTABLE => {} // Update the existing table + _ => { + // Create a new table + ffi::lua_pop(state, 1); + push_table(state, 0, methods_nrec, true)?; + } + } + for (k, m) in registry.methods { + self.push(self.create_callback(m)?)?; + rawset_field(state, -2, &k)?; + } + #[cfg(feature = "async")] + for (k, m) in registry.async_methods { + self.push(self.create_async_callback(m)?)?; + rawset_field(state, -2, &k)?; + } + match index_type { + ffi::LUA_TTABLE => { + ffi::lua_pop(state, 1); // All done + } + ffi::LUA_TNIL => { + rawset_field(state, metatable_index, "__index")?; // Set the new table as `__index` + } + _ => { + methods_index = Some(ffi::lua_absindex(state, -1)); + extra_tables_count += 1; + } + } + } + + #[cfg(feature = "luau")] + let extra_init = None; + #[cfg(not(feature = "luau"))] + let extra_init: Option Result<()>> = Some(|state| { + ffi::lua_pushcfunction( + state, + crate::util::userdata_destructor::>, + ); + rawset_field(state, -2, "__gc") + }); + + init_userdata_metatable( + state, + metatable_index, + field_getters_index, + field_setters_index, + methods_index, + extra_init, + )?; + + // Pop extra tables to get metatable on top of the stack + ffi::lua_pop(state, extra_tables_count); + + let mt_ptr = ffi::lua_topointer(state, -1); + let id = protect_lua!(state, 1, 0, |state| { + ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX) + })?; + + let type_id = TypeId::of::(); + (*self.extra.get()).registered_userdata.insert(type_id, id); + (*self.extra.get()) + .registered_userdata_mt + .insert(mt_ptr, Some(type_id)); + + Ok(id as Integer) + } + + // #[inline] + // pub(crate) unsafe fn register_raw_userdata_metatable( + // &self, + // ptr: *const c_void, + // type_id: Option, + // ) { + // (*self.extra.get()) + // .registered_userdata_mt + // .insert(ptr, type_id); + // } + + // #[inline] + // pub(crate) unsafe fn deregister_raw_userdata_metatable(&self, ptr: *const c_void) { + // (*self.extra.get()).registered_userdata_mt.remove(&ptr); + // if (*self.extra.get()).last_checked_userdata_mt.0 == ptr { + // (*self.extra.get()).last_checked_userdata_mt = (ptr::null(), None); + // } + // } + + // #[inline(always)] + // pub(crate) unsafe fn get_userdata_ref(&self, idx: c_int) -> Result> { + // let guard = self.lua().lock_arc(); + // (*get_userdata::>(self.state(), idx)).try_make_ref(guard) + // } + + // Returns `TypeId` for the userdata ref, checking that it's registered and not destructed. + // + // Returns `None` if the userdata is registered but non-static. + pub(crate) unsafe fn get_userdata_ref_type_id( + &self, + vref: &ValueRef, + ) -> Result> { + self.get_userdata_type_id_inner(self.ref_thread(), vref.index) + } + + // Same as `get_userdata_ref_type_id` but assumes the userdata is already on the stack. + pub(crate) unsafe fn get_userdata_type_id(&self, idx: c_int) -> Result> { + self.get_userdata_type_id_inner(self.state(), idx) + } + + unsafe fn get_userdata_type_id_inner( + &self, + state: *mut ffi::lua_State, + idx: c_int, + ) -> Result> { + if ffi::lua_getmetatable(state, idx) == 0 { + return Err(Error::UserDataTypeMismatch); + } + let mt_ptr = ffi::lua_topointer(state, -1); + ffi::lua_pop(state, 1); + + // Fast path to skip looking up the metatable in the map + let (last_mt, last_type_id) = (*self.extra.get()).last_checked_userdata_mt; + if last_mt == mt_ptr { + return Ok(last_type_id); + } + + match (*self.extra.get()).registered_userdata_mt.get(&mt_ptr) { + Some(&type_id) if type_id == Some(TypeId::of::()) => { + Err(Error::UserDataDestructed) + } + Some(&type_id) => { + (*self.extra.get()).last_checked_userdata_mt = (mt_ptr, type_id); + Ok(type_id) + } + None => Err(Error::UserDataTypeMismatch), + } + } + + // Pushes a ValueRef (userdata) value onto the stack, returning their `TypeId`. + // Uses 1 stack space, does not call checkstack. + pub(crate) unsafe fn push_userdata_ref(&self, vref: &ValueRef) -> Result> { + let type_id = self.get_userdata_type_id_inner(self.ref_thread(), vref.index)?; + self.push_ref(vref); + Ok(type_id) + } + + // Creates a Function out of a Callback containing a 'static Fn. + pub(crate) fn create_callback(&self, func: Callback) -> Result { + unsafe extern "C-unwind" fn call_callback(state: *mut ffi::lua_State) -> c_int { + // Normal functions can be scoped and therefore destroyed, + // so we need to check that the first upvalue is valid + let (upvalue, extra) = match ffi::lua_type(state, ffi::lua_upvalueindex(1)) { + ffi::LUA_TUSERDATA => { + let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); + (upvalue, (*upvalue).extra.get()) + } + _ => (ptr::null_mut(), ptr::null_mut()), + }; + callback_error_ext(state, extra, |nargs| { + // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) + if upvalue.is_null() { + return Err(Error::CallbackDestructed); + } + + // The lock must be already held as the callback is executed + let rawlua = (*extra).raw_lua(); + let _guard = StateGuard::new(rawlua, state); + let func = &*(*upvalue).data; + + func(rawlua, nargs) + }) + } + + let state = self.state(); + unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 4)?; + + let func = mem::transmute(func); + let extra = Arc::clone(&self.extra); + let protect = !self.unlikely_memory_error(); + push_gc_userdata(state, CallbackUpvalue { data: func, extra }, protect)?; + if protect { + protect_lua!(state, 1, 1, fn(state) { + ffi::lua_pushcclosure(state, call_callback, 1); + })?; + } else { + ffi::lua_pushcclosure(state, call_callback, 1); + } + + Ok(Function(self.pop_ref())) + } + } + + #[cfg(feature = "async")] + pub(crate) fn create_async_callback(&self, func: AsyncCallback) -> Result { + #[cfg(any( + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luau" + ))] + unsafe { + if !(*self.extra.get()).libs.contains(StdLib::COROUTINE) { + load_from_std_lib(self.main_state, StdLib::COROUTINE)?; + (*self.extra.get()).libs |= StdLib::COROUTINE; + } + } + + unsafe extern "C-unwind" fn call_callback(state: *mut ffi::lua_State) -> c_int { + // Async functions cannot be scoped and therefore destroyed, + // so the first upvalue is always valid + let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); + let extra = (*upvalue).extra.get(); + callback_error_ext(state, extra, |nargs| { + // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) + // The lock must be already held as the callback is executed + let rawlua = (*extra).raw_lua(); + let _guard = StateGuard::new(rawlua, state); + + let args = MultiValue::from_stack_multi(nargs, rawlua)?; + let func = &*(*upvalue).data; + let fut = func(rawlua, args); + let extra = Arc::clone(&(*upvalue).extra); + let protect = !rawlua.unlikely_memory_error(); + push_gc_userdata(state, AsyncPollUpvalue { data: fut, extra }, protect)?; + if protect { + protect_lua!(state, 1, 1, fn(state) { + ffi::lua_pushcclosure(state, poll_future, 1); + })?; + } else { + ffi::lua_pushcclosure(state, poll_future, 1); + } + + Ok(1) + }) + } + + unsafe extern "C-unwind" fn poll_future(state: *mut ffi::lua_State) -> c_int { + let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); + let extra = (*upvalue).extra.get(); + callback_error_ext(state, extra, |_| { + // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) + // The lock must be already held as the future is polled + let rawlua = (*extra).raw_lua(); + let _guard = StateGuard::new(&rawlua, state); + + let fut = &mut (*upvalue).data; + let mut ctx = Context::from_waker(rawlua.waker()); + match fut.as_mut().poll(&mut ctx) { + Poll::Pending => { + ffi::lua_pushnil(state); + ffi::lua_pushlightuserdata(state, Lua::poll_pending().0); + Ok(2) + } + Poll::Ready(nresults) => { + match nresults? { + nresults @ 0..=2 => { + // Fast path for up to 2 results without creating a table + ffi::lua_pushinteger(state, nresults as _); + if nresults > 0 { + ffi::lua_insert(state, -nresults - 1); + } + Ok(nresults + 1) + } + nresults => { + let results = MultiValue::from_stack_multi(nresults, &rawlua)?; + ffi::lua_pushinteger(state, nresults as _); + rawlua.push(rawlua.create_sequence_from(results)?)?; + Ok(2) + } + } + } + } + }) + } + + let state = self.state(); + let get_poll = unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 4)?; + + let func = mem::transmute(func); + let extra = Arc::clone(&self.extra); + let protect = !self.unlikely_memory_error(); + let upvalue = AsyncCallbackUpvalue { data: func, extra }; + push_gc_userdata(state, upvalue, protect)?; + if protect { + protect_lua!(state, 1, 1, fn(state) { + ffi::lua_pushcclosure(state, call_callback, 1); + })?; + } else { + ffi::lua_pushcclosure(state, call_callback, 1); + } + + Function(self.pop_ref()) + }; + + unsafe extern "C-unwind" fn unpack(state: *mut ffi::lua_State) -> c_int { + let len = ffi::lua_tointeger(state, 2); + ffi::luaL_checkstack(state, len as c_int, ptr::null()); + for i in 1..=len { + ffi::lua_rawgeti(state, 1, i); + } + len as c_int + } + + let lua = self.lua(); + let coroutine = lua.globals().get::<_, Table>("coroutine")?; + + let env = lua.create_table_with_capacity(0, 3)?; + env.set("get_poll", get_poll)?; + // Cache `yield` function + env.set("yield", coroutine.get::<_, Function>("yield")?)?; + unsafe { + env.set("unpack", lua.create_c_function(unpack)?)?; + } + + lua.load( + r#" + local poll = get_poll(...) + while true do + local nres, res, res2 = poll() + if nres ~= nil then + if nres == 0 then + return + elseif nres == 1 then + return res + elseif nres == 2 then + return res, res2 + else + return unpack(res, nres) + end + end + yield(res) -- `res` is a "pending" value + end + "#, + ) + .try_cache() + .set_name("__mlua_async_poll") + .set_environment(env) + .into_function() + } + + #[cfg(feature = "async")] + #[inline] + pub(crate) unsafe fn waker(&self) -> &Waker { + (*self.extra.get()).waker.as_ref() + } + + #[cfg(feature = "async")] + #[inline] + pub(crate) unsafe fn set_waker(&self, waker: NonNull) -> NonNull { + mem::replace(&mut (*self.extra.get()).waker, waker) + } +} + +// Uses 3 stack spaces +unsafe fn load_from_std_lib(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> { + #[inline(always)] + pub unsafe fn requiref( + state: *mut ffi::lua_State, + modname: &str, + openf: ffi::lua_CFunction, + glb: c_int, + ) -> Result<()> { + let modname = mlua_expect!(CString::new(modname), "modname contains nil byte"); + protect_lua!(state, 0, 1, |state| { + ffi::luaL_requiref(state, modname.as_ptr() as *const c_char, openf, glb) + }) + } + + #[cfg(feature = "luajit")] + struct GcGuard(*mut ffi::lua_State); + + #[cfg(feature = "luajit")] + impl GcGuard { + fn new(state: *mut ffi::lua_State) -> Self { + // Stop collector during library initialization + unsafe { ffi::lua_gc(state, ffi::LUA_GCSTOP, 0) }; + GcGuard(state) + } + } + + #[cfg(feature = "luajit")] + impl Drop for GcGuard { + fn drop(&mut self) { + unsafe { ffi::lua_gc(self.0, ffi::LUA_GCRESTART, -1) }; + } + } + + // Stop collector during library initialization + #[cfg(feature = "luajit")] + let _gc_guard = GcGuard::new(state); + + #[cfg(any( + feature = "lua54", + feature = "lua53", + feature = "lua52", + feature = "luau" + ))] + { + if libs.contains(StdLib::COROUTINE) { + requiref(state, ffi::LUA_COLIBNAME, ffi::luaopen_coroutine, 1)?; + ffi::lua_pop(state, 1); + } + } + + if libs.contains(StdLib::TABLE) { + requiref(state, ffi::LUA_TABLIBNAME, ffi::luaopen_table, 1)?; + ffi::lua_pop(state, 1); + } + + #[cfg(not(feature = "luau"))] + if libs.contains(StdLib::IO) { + requiref(state, ffi::LUA_IOLIBNAME, ffi::luaopen_io, 1)?; + ffi::lua_pop(state, 1); + } + + if libs.contains(StdLib::OS) { + requiref(state, ffi::LUA_OSLIBNAME, ffi::luaopen_os, 1)?; + ffi::lua_pop(state, 1); + } + + if libs.contains(StdLib::STRING) { + requiref(state, ffi::LUA_STRLIBNAME, ffi::luaopen_string, 1)?; + ffi::lua_pop(state, 1); + } + + #[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] + { + if libs.contains(StdLib::UTF8) { + requiref(state, ffi::LUA_UTF8LIBNAME, ffi::luaopen_utf8, 1)?; + ffi::lua_pop(state, 1); + } + } + + #[cfg(any(feature = "lua52", feature = "luau"))] + { + if libs.contains(StdLib::BIT) { + requiref(state, ffi::LUA_BITLIBNAME, ffi::luaopen_bit32, 1)?; + ffi::lua_pop(state, 1); + } + } + + #[cfg(feature = "luajit")] + { + if libs.contains(StdLib::BIT) { + requiref(state, ffi::LUA_BITLIBNAME, ffi::luaopen_bit, 1)?; + ffi::lua_pop(state, 1); + } + } + + #[cfg(feature = "luau")] + if libs.contains(StdLib::BUFFER) { + requiref(state, ffi::LUA_BUFFERLIBNAME, ffi::luaopen_buffer, 1)?; + ffi::lua_pop(state, 1); + } + + if libs.contains(StdLib::MATH) { + requiref(state, ffi::LUA_MATHLIBNAME, ffi::luaopen_math, 1)?; + ffi::lua_pop(state, 1); + } + + if libs.contains(StdLib::DEBUG) { + requiref(state, ffi::LUA_DBLIBNAME, ffi::luaopen_debug, 1)?; + ffi::lua_pop(state, 1); + } + + #[cfg(not(feature = "luau"))] + if libs.contains(StdLib::PACKAGE) { + requiref(state, ffi::LUA_LOADLIBNAME, ffi::luaopen_package, 1)?; + ffi::lua_pop(state, 1); + } + #[cfg(feature = "luau")] + if libs.contains(StdLib::PACKAGE) { + let lua = (*ExtraData::get(state)).lua(); + crate::luau::register_package_module(lua)?; + } + + #[cfg(feature = "luajit")] + { + if libs.contains(StdLib::JIT) { + requiref(state, ffi::LUA_JITLIBNAME, ffi::luaopen_jit, 1)?; + ffi::lua_pop(state, 1); + } + + if libs.contains(StdLib::FFI) { + requiref(state, ffi::LUA_FFILIBNAME, ffi::luaopen_ffi, 1)?; + ffi::lua_pop(state, 1); + } + } + + Ok(()) +} diff --git a/src/state/util.rs b/src/state/util.rs new file mode 100644 index 0000000..8c6a66b --- /dev/null +++ b/src/state/util.rs @@ -0,0 +1,187 @@ +use std::os::raw::c_int; +use std::panic::{catch_unwind, AssertUnwindSafe}; +use std::ptr; +use std::sync::Arc; + +use crate::error::{Error, Result}; +use crate::state::{ExtraData, RawLua}; +use crate::util::{self, get_gc_metatable, WrappedFailure}; + +const WRAPPED_FAILURE_POOL_SIZE: usize = 64; +// const MULTIVALUE_POOL_SIZE: usize = 64; + +pub(super) struct StateGuard<'a>(&'a RawLua, *mut ffi::lua_State); + +impl<'a> StateGuard<'a> { + pub(super) fn new(inner: &'a RawLua, mut state: *mut ffi::lua_State) -> Self { + state = inner.state.replace(state); + Self(inner, state) + } +} + +impl<'a> Drop for StateGuard<'a> { + fn drop(&mut self) { + self.0.state.set(self.1); + } +} + +// An optimized version of `callback_error` that does not allocate `WrappedFailure` userdata +// and instead reuses unsed values from previous calls (or allocates new). +pub(super) unsafe fn callback_error_ext( + state: *mut ffi::lua_State, + mut extra: *mut ExtraData, + f: F, +) -> R +where + F: FnOnce(c_int) -> Result, +{ + if extra.is_null() { + extra = ExtraData::get(state); + } + + let nargs = ffi::lua_gettop(state); + + enum PreallocatedFailure { + New(*mut WrappedFailure), + Existing(i32), + } + + impl PreallocatedFailure { + unsafe fn reserve(state: *mut ffi::lua_State, extra: *mut ExtraData) -> Self { + match (*extra).wrapped_failure_pool.pop() { + Some(index) => PreallocatedFailure::Existing(index), + None => { + // We need to check stack for Luau in case when callback is called from interrupt + // See https://github.com/Roblox/luau/issues/446 and mlua #142 and #153 + #[cfg(feature = "luau")] + ffi::lua_rawcheckstack(state, 2); + // Place it to the beginning of the stack + let ud = WrappedFailure::new_userdata(state); + ffi::lua_insert(state, 1); + PreallocatedFailure::New(ud) + } + } + } + + unsafe fn r#use( + &self, + state: *mut ffi::lua_State, + extra: *mut ExtraData, + ) -> *mut WrappedFailure { + let ref_thread = (*extra).ref_thread; + match *self { + PreallocatedFailure::New(ud) => { + ffi::lua_settop(state, 1); + ud + } + PreallocatedFailure::Existing(index) => { + ffi::lua_settop(state, 0); + #[cfg(feature = "luau")] + ffi::lua_rawcheckstack(state, 2); + ffi::lua_pushvalue(ref_thread, index); + ffi::lua_xmove(ref_thread, state, 1); + ffi::lua_pushnil(ref_thread); + ffi::lua_replace(ref_thread, index); + (*extra).ref_free.push(index); + ffi::lua_touserdata(state, -1) as *mut WrappedFailure + } + } + } + + unsafe fn release(self, state: *mut ffi::lua_State, extra: *mut ExtraData) { + let ref_thread = (*extra).ref_thread; + match self { + PreallocatedFailure::New(_) => { + if (*extra).wrapped_failure_pool.len() < WRAPPED_FAILURE_POOL_SIZE { + ffi::lua_rotate(state, 1, -1); + ffi::lua_xmove(state, ref_thread, 1); + let index = ref_stack_pop(extra); + (*extra).wrapped_failure_pool.push(index); + } else { + ffi::lua_remove(state, 1); + } + } + PreallocatedFailure::Existing(index) => { + if (*extra).wrapped_failure_pool.len() < WRAPPED_FAILURE_POOL_SIZE { + (*extra).wrapped_failure_pool.push(index); + } else { + ffi::lua_pushnil(ref_thread); + ffi::lua_replace(ref_thread, index); + (*extra).ref_free.push(index); + } + } + } + } + } + + // We cannot shadow Rust errors with Lua ones, so we need to reserve pre-allocated memory + // to store a wrapped failure (error or panic) *before* we proceed. + let prealloc_failure = PreallocatedFailure::reserve(state, extra); + + match catch_unwind(AssertUnwindSafe(|| f(nargs))) { + Ok(Ok(r)) => { + // Return unused `WrappedFailure` to the pool + prealloc_failure.release(state, extra); + r + } + Ok(Err(err)) => { + let wrapped_error = prealloc_failure.r#use(state, extra); + + // Build `CallbackError` with traceback + let traceback = if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 { + ffi::luaL_traceback(state, state, ptr::null(), 0); + let traceback = util::to_string(state, -1); + ffi::lua_pop(state, 1); + traceback + } else { + "".to_string() + }; + let cause = Arc::new(err); + ptr::write( + wrapped_error, + WrappedFailure::Error(Error::CallbackError { traceback, cause }), + ); + get_gc_metatable::(state); + ffi::lua_setmetatable(state, -2); + + ffi::lua_error(state) + } + Err(p) => { + let wrapped_panic = prealloc_failure.r#use(state, extra); + ptr::write(wrapped_panic, WrappedFailure::Panic(Some(p))); + get_gc_metatable::(state); + ffi::lua_setmetatable(state, -2); + ffi::lua_error(state) + } + } +} + +pub(super) unsafe fn ref_stack_pop(extra: *mut ExtraData) -> c_int { + let extra = &mut *extra; + if let Some(free) = extra.ref_free.pop() { + ffi::lua_replace(extra.ref_thread, free); + return free; + } + + // Try to grow max stack size + if extra.ref_stack_top >= extra.ref_stack_size { + let mut inc = extra.ref_stack_size; // Try to double stack size + while inc > 0 && ffi::lua_checkstack(extra.ref_thread, inc) == 0 { + inc /= 2; + } + if inc == 0 { + // Pop item on top of the stack to avoid stack leaking and successfully run destructors + // during unwinding. + ffi::lua_pop(extra.ref_thread, 1); + let top = extra.ref_stack_top; + // It is a user error to create enough references to exhaust the Lua max stack size for + // the ref thread. + panic!( + "cannot create a Lua reference, out of auxiliary stack space (used {top} slots)" + ); + } + extra.ref_stack_size += inc; + } + extra.ref_stack_top += 1; + extra.ref_stack_top +} diff --git a/src/thread.rs b/src/thread.rs index b6a0e86..177ce18 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -2,8 +2,8 @@ use std::os::raw::{c_int, c_void}; use crate::error::{Error, Result}; #[allow(unused)] -use crate::lua::Lua; -use crate::lua::LuaInner; +use crate::state::Lua; +use crate::state::RawLua; use crate::types::ValueRef; use crate::util::{check_stack, error_traceback_thread, pop_error, StackGuard}; use crate::value::{FromLuaMulti, IntoLuaMulti}; @@ -64,11 +64,6 @@ pub struct AsyncThread { impl Thread { #[inline(always)] - pub(crate) fn new(lua: &LuaInner, r#ref: ValueRef) -> Self { - let state = unsafe { ffi::lua_tothread(lua.ref_thread(), r#ref.index) }; - Thread(r#ref, state) - } - const fn state(&self) -> *mut ffi::lua_State { self.1 } @@ -502,7 +497,7 @@ unsafe fn is_poll_pending(state: *mut ffi::lua_State) -> bool { #[cfg(feature = "async")] struct WakerGuard<'lua, 'a> { - lua: &'lua LuaInner, + lua: &'lua RawLua, prev: NonNull, _phantom: PhantomData<&'a ()>, } @@ -510,7 +505,7 @@ struct WakerGuard<'lua, 'a> { #[cfg(feature = "async")] impl<'lua, 'a> WakerGuard<'lua, 'a> { #[inline] - pub fn new(lua: &'lua LuaInner, waker: &'a Waker) -> Result> { + pub fn new(lua: &'lua RawLua, waker: &'a Waker) -> Result> { let prev = unsafe { lua.set_waker(NonNull::from(waker)) }; Ok(WakerGuard { lua, diff --git a/src/types.rs b/src/types.rs index 2a28441..d23eb73 100644 --- a/src/types.rs +++ b/src/types.rs @@ -14,7 +14,7 @@ use rustc_hash::FxHashMap; use crate::error::Result; #[cfg(not(feature = "luau"))] use crate::hook::Debug; -use crate::lua::{ExtraData, Lua, LuaGuard, LuaInner, WeakLua}; +use crate::state::{ExtraData, Lua, LuaGuard, RawLua, WeakLua}; #[cfg(feature = "async")] use {crate::value::MultiValue, futures_util::future::LocalBoxFuture}; @@ -41,7 +41,7 @@ pub(crate) enum SubtypeId { #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct LightUserData(pub *mut c_void); -pub(crate) type Callback<'a> = Box Result + 'static>; +pub(crate) type Callback<'a> = Box Result + 'static>; pub(crate) struct Upvalue { pub(crate) data: T, @@ -52,7 +52,7 @@ pub(crate) type CallbackUpvalue = Upvalue>; #[cfg(feature = "async")] pub(crate) type AsyncCallback<'a> = - Box LocalBoxFuture<'a, Result> + 'static>; + Box LocalBoxFuture<'a, Result> + 'static>; #[cfg(feature = "async")] pub(crate) type AsyncCallbackUpvalue = Upvalue>; @@ -281,7 +281,8 @@ pub(crate) struct ValueRef { } impl ValueRef { - pub(crate) fn new(lua: &LuaInner, index: c_int) -> Self { + #[inline] + pub(crate) fn new(lua: &RawLua, index: c_int) -> Self { ValueRef { lua: lua.weak().clone(), index, @@ -304,7 +305,7 @@ impl fmt::Debug for ValueRef { impl Clone for ValueRef { fn clone(&self) -> Self { - self.lua.lock().clone_ref(self) + unsafe { self.lua.lock().clone_ref(self) } } } @@ -312,7 +313,7 @@ impl Drop for ValueRef { fn drop(&mut self) { if self.drop { if let Some(lua) = self.lua.try_lock() { - lua.drop_ref(self); + unsafe { lua.drop_ref(self) }; } } } diff --git a/src/userdata.rs b/src/userdata.rs index 1100dc3..81889d8 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -16,7 +16,7 @@ use { use crate::error::{Error, Result}; use crate::function::Function; -use crate::lua::{Lua, LuaGuard}; +use crate::state::{Lua, LuaGuard}; use crate::string::String; use crate::table::{Table, TablePairs}; use crate::types::{MaybeSend, SubtypeId, ValueRef}; diff --git a/src/userdata/cell.rs b/src/userdata/cell.rs index 294b7b5..33a5361 100644 --- a/src/userdata/cell.rs +++ b/src/userdata/cell.rs @@ -9,7 +9,8 @@ use std::rc::Rc; use serde::ser::{Serialize, Serializer}; use crate::error::{Error, Result}; -use crate::lua::{Lua, LuaGuard, LuaInner}; +use crate::state::{Lua, LuaGuard}; +use crate::state::RawLua; use crate::userdata::AnyUserData; use crate::util::get_userdata; use crate::value::{FromLua, Value}; @@ -199,7 +200,7 @@ impl FromLua for UserDataRef { try_value_to_userdata::(value)?.borrow() } - unsafe fn from_stack(idx: c_int, lua: &LuaInner) -> Result { + unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { let type_id = lua.get_userdata_type_id(idx)?; match type_id { Some(type_id) if type_id == TypeId::of::() => { @@ -268,7 +269,7 @@ impl FromLua for UserDataRefMut { try_value_to_userdata::(value)?.borrow_mut() } - unsafe fn from_stack(idx: c_int, lua: &LuaInner) -> Result { + unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { let type_id = lua.get_userdata_type_id(idx)?; match type_id { Some(type_id) if type_id == TypeId::of::() => { diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index 83ebabf..6cb9a40 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -7,7 +7,7 @@ use std::os::raw::c_int; use std::string::String as StdString; use crate::error::{Error, Result}; -use crate::lua::Lua; +use crate::state::Lua; use crate::types::{Callback, MaybeSend}; use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods}; use crate::util::{get_userdata, short_type_name}; diff --git a/src/util/mod.rs b/src/util/mod.rs index ff8b28e..1fb81ec 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,5 +1,6 @@ use std::any::{Any, TypeId}; use std::borrow::Cow; +use std::cell::UnsafeCell; use std::ffi::CStr; use std::fmt::Write; use std::mem::MaybeUninit; @@ -18,7 +19,19 @@ pub(crate) use short_names::short_type_name; static METATABLE_CACHE: Lazy> = Lazy::new(|| { let mut map = FxHashMap::with_capacity_and_hasher(32, Default::default()); - crate::lua::init_metatable_cache(&mut map); + + map.insert(TypeId::of::>>(), 0); + map.insert(TypeId::of::(), 0); + map.insert(TypeId::of::(), 0); + + #[cfg(feature = "async")] + { + map.insert(TypeId::of::(), 0); + map.insert(TypeId::of::(), 0); + map.insert(TypeId::of::(), 0); + map.insert(TypeId::of::>(), 0); + } + map.insert(TypeId::of::(), 0); map.insert(TypeId::of::(), 0); map diff --git a/src/value.rs b/src/value.rs index e589d8c..d733ce7 100644 --- a/src/value.rs +++ b/src/value.rs @@ -19,7 +19,7 @@ use { use crate::error::{Error, Result}; use crate::function::Function; -use crate::lua::{Lua, LuaInner}; +use crate::state::{Lua, RawLua}; use crate::string::String; use crate::table::Table; use crate::thread::Thread; @@ -698,7 +698,7 @@ pub trait IntoLua: Sized { /// This method does not check Lua stack space. #[doc(hidden)] #[inline] - unsafe fn push_into_stack(self, lua: &LuaInner) -> Result<()> { + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { lua.push_value(&self.into_lua(lua.lua())?) } } @@ -726,19 +726,14 @@ pub trait FromLua: Sized { /// Performs the conversion for a value in the Lua stack at index `idx`. #[doc(hidden)] #[inline] - unsafe fn from_stack(idx: c_int, lua: &LuaInner) -> Result { + unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { Self::from_lua(lua.stack_value(idx), lua.lua()) } /// Same as `from_lua_arg` but for a value in the Lua stack at index `idx`. #[doc(hidden)] #[inline] - unsafe fn from_stack_arg( - idx: c_int, - i: usize, - to: Option<&str>, - lua: &LuaInner, - ) -> Result { + unsafe fn from_stack_arg(idx: c_int, i: usize, to: Option<&str>, lua: &RawLua) -> Result { Self::from_stack(idx, lua).map_err(|err| Error::BadArgument { to: to.map(|s| s.to_string()), pos: i, @@ -876,7 +871,7 @@ pub trait IntoLuaMulti: Sized { /// Returns number of pushed values. #[doc(hidden)] #[inline] - unsafe fn push_into_stack_multi(self, lua: &LuaInner) -> Result { + unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result { let values = self.into_lua_multi(lua.lua())?; let len: c_int = values.len().try_into().unwrap(); unsafe { @@ -916,7 +911,7 @@ pub trait FromLuaMulti: Sized { /// Performs the conversion for a number of values in the Lua stack. #[doc(hidden)] #[inline] - unsafe fn from_stack_multi(nvals: c_int, lua: &LuaInner) -> Result { + unsafe fn from_stack_multi(nvals: c_int, lua: &RawLua) -> Result { let mut values = MultiValue::with_lua_and_capacity(lua.lua(), nvals as usize); for idx in 0..nvals { values.push_back(lua.stack_value(-nvals + idx)); @@ -935,7 +930,7 @@ pub trait FromLuaMulti: Sized { nargs: c_int, i: usize, to: Option<&str>, - lua: &LuaInner, + lua: &RawLua, ) -> Result { let _ = (i, to); Self::from_stack_multi(nargs, lua) diff --git a/tests/tests.rs b/tests/tests.rs index aab4f48..50e02ed 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -17,7 +17,7 @@ use mlua::{ fn test_safety() -> Result<()> { let lua = Lua::new(); assert!(lua.load(r#"require "debug""#).exec().is_err()); - match lua.load_from_std_lib(StdLib::DEBUG) { + match lua.load_std_libs(StdLib::DEBUG) { Err(Error::SafetyError(_)) => {} Err(e) => panic!("expected SafetyError, got {:?}", e), Ok(_) => panic!("expected SafetyError, got no error"), @@ -53,7 +53,7 @@ fn test_safety() -> Result<()> { // Test safety rules after dynamically loading `package` library let lua = Lua::new_with(StdLib::NONE, LuaOptions::default())?; assert!(lua.globals().get::<_, Option>("require")?.is_none()); - lua.load_from_std_lib(StdLib::PACKAGE)?; + lua.load_std_libs(StdLib::PACKAGE)?; match lua.load(r#"package.loadlib()"#).exec() { Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() { Error::SafetyError(_) => {} @@ -657,7 +657,7 @@ fn test_recursive_mut_callback_error() -> Result<()> { let lua = Lua::new(); let mut v = Some(Box::new(123)); - let f = lua.create_function_mut::<_, (), _>(move |lua, mutate: bool| { + let f = lua.create_function_mut(move |lua, mutate: bool| { if mutate { v = None; } else {