use std::any::TypeId; use std::cell::{Cell, UnsafeCell}; use std::ffi::{CStr, CString}; use std::mem; use std::os::raw::{c_char, c_int, c_void}; use std::panic::resume_unwind; use std::ptr::{self, NonNull}; use std::result::Result as StdResult; use std::sync::Arc; 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::traits::IntoLua; use crate::types::{ AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer, LightUserData, MaybeSend, ReentrantMutex, RegistryKey, ValueRef, XRc, }; use crate::userdata::{ AnyUserData, MetaMethod, RawUserDataRegistry, UserData, UserDataRegistry, UserDataStorage, }; use crate::util::{ assert_stack, check_stack, get_destructed_userdata_metatable, get_internal_userdata, get_main_state, get_metatable_ptr, get_userdata, init_error_registry, init_internal_metatable, init_userdata_metatable, pop_error, push_internal_userdata, push_string, push_table, rawset_field, safe_pcall, safe_xpcall, short_type_name, StackGuard, WrappedFailure, }; use crate::value::{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::multi::MultiValue, crate::traits::FromLuaMulti, crate::types::{AsyncCallback, AsyncCallbackUpvalue, AsyncPollUpvalue}, std::task::{Context, Poll, Waker}, }; /// An inner Lua struct which holds a raw Lua state. #[doc(hidden)] pub struct RawLua { // The state is dynamic and depends on context pub(super) state: Cell<*mut ffi::lua_State>, pub(super) main_state: Option>, pub(super) extra: XRc>, } impl Drop for RawLua { fn drop(&mut self) { unsafe { if !(*self.extra.get()).owned { return; } 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)); } } } } #[cfg(feature = "send")] unsafe impl Send for RawLua {} 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() } } /// Returns a pointer to the current Lua state. /// /// The pointer refers to the active Lua coroutine and depends on the context. #[inline(always)] pub fn state(&self) -> *mut ffi::lua_State { self.state.get() } #[inline(always)] pub(crate) fn main_state(&self) -> *mut ffi::lua_State { self.main_state .map(|state| state.as_ptr()) .unwrap_or_else(|| self.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) -> XRc> { 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, true); let extra = rawlua.lock().extra.get(); mlua_expect!( load_std_libs(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, owned: bool) -> XRc> { 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_internal_metatable::>>(state, None)?; init_internal_metatable::(state, None)?; init_internal_metatable::(state, None)?; #[cfg(feature = "async")] { init_internal_metatable::(state, None)?; init_internal_metatable::(state, None)?; init_internal_metatable::(state, None)?; init_internal_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, owned); // 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); #[allow(clippy::arc_with_non_send_sync)] let rawlua = XRc::new(ReentrantMutex::new(RawLua { state: Cell::new(state), // Make sure that we don't store current state as main state (if it's not available) main_state: get_main_state(state).and_then(NonNull::new), extra: XRc::clone(&extra), })); (*extra.get()).set_lua(&rawlua); rawlua } unsafe fn try_from_ptr(state: *mut ffi::lua_State) -> Option>> { match ExtraData::get(state) { extra if extra.is_null() => None, extra => Some(XRc::clone(&(*extra).lua().raw)), } } /// Marks the Lua state as safe. #[inline(always)] pub(super) fn mark_safe(&self) { unsafe { (*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_std_libs(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_unguarded(&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_unguarded(&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<&Table>, mode: Option, source: &[u8], ) -> Result { let state = self.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 2)?; let mode_str = match mode { Some(ChunkMode::Binary) => cstr!("b"), Some(ChunkMode::Text) => cstr!("t"), None => cstr!("bt"), }; match ffi::luaL_loadbufferenv( state, source.as_ptr() as *const c_char, source.len(), name.map(|n| n.as_ptr()).unwrap_or_else(ptr::null), mode_str, match env { Some(env) => { self.push_ref(&env.0); -1 } _ => 0, }, ) { ffi::LUA_OK => { #[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, { use crate::types::VmState; use std::rc::Rc; 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; } let result = callback_error_ext(state, extra, move |extra, _| { let hook_cb = (*extra).hook_callback.clone(); let hook_cb = mlua_expect!(hook_cb, "no hook callback set in hook_proc"); if Rc::strong_count(&hook_cb) > 2 { return Ok(VmState::Continue); // 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) }); match result { VmState::Continue => {} VmState::Yield => { // Only count and line events can yield if (*ar).event == ffi::LUA_HOOKCOUNT || (*ar).event == ffi::LUA_HOOKLINE { #[cfg(any(feature = "lua54", feature = "lua53"))] if ffi::lua_isyieldable(state) != 0 { ffi::lua_yield(state, 0); } #[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))] { ffi::lua_pushliteral(state, "attempt to yield from a hook"); ffi::lua_error(state); } } } } } (*self.extra.get()).hook_callback = Some(Rc::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; // Prevent thread from being garbage collected return true; } false } /// Pushes a value that implements `IntoLua` onto the Lua stack. /// /// Uses up to 2 stack spaces to push a single value, does not call `checkstack`. #[inline(always)] pub(crate) 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), #[cfg(feature = "luau")] Value::Buffer(buf) => self.push_ref(&buf.0), Value::Error(err) => { let protect = !self.unlikely_memory_error(); push_internal_userdata(state, WrappedFailure::Error(*err.clone()), protect)?; } Value::Other(vref) => self.push_ref(vref), } 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, None); 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, type_hint: Option) -> Value { let state = self.state(); match type_hint.unwrap_or_else(|| 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::Vector([*v, *v.add(1), *v.add(2)])); #[cfg(feature = "luau-vector4")] return Value::Vector(crate::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_internal_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())) } } } 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 => { ffi::lua_xpush(state, self.ref_thread(), idx); Value::Buffer(crate::Buffer(self.pop_ref_thread())) } _ => { ffi::lua_xpush(state, self.ref_thread(), idx); Value::Other(self.pop_ref_thread()) } } } // 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 { #[cfg(debug_assertions)] if cfg!(force_memory_limit) { return false; } // MemoryInfo is empty in module mode so we cannot predict memory limits match MemoryState::get(self.state()) { mem_state if !mem_state.is_null() => (*mem_state).memory_limit() == 0, _ => (*self.extra.get()).skip_memory_check, // Check the special flag (only for module mode) } } pub(crate) unsafe fn make_userdata(&self, data: UserDataStorage) -> 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_t.get(&type_id) { return Ok(table_id as Integer); } // Create a new metatable from `UserData` definition let mut registry = UserDataRegistry::new(self.lua(), type_id); T::register(&mut registry); self.create_userdata_metatable(registry.into_raw()) }) } pub(crate) unsafe fn make_any_userdata(&self, data: UserDataStorage) -> 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_t.get(&type_id) { return Ok(table_id as Integer); } // Check if metatable creation is pending or create an empty metatable otherwise let registry = match (*self.extra.get()).pending_userdata_reg.remove(&type_id) { Some(registry) => registry, None => UserDataRegistry::::new(self.lua(), type_id).into_raw(), }; self.create_userdata_metatable(registry) }) } unsafe fn make_userdata_with_metatable( &self, data: UserDataStorage, 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(); crate::util::push_userdata(state, data, 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())) } pub(crate) unsafe fn create_userdata_metatable(&self, registry: RawUserDataRegistry) -> Result { let state = self.state(); let type_id = registry.type_id; self.push_userdata_metatable(registry)?; let mt_ptr = ffi::lua_topointer(state, -1); let id = protect_lua!(state, 1, 0, |state| { ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX) })?; if let Some(type_id) = type_id { (*self.extra.get()).registered_userdata_t.insert(type_id, id); } self.register_userdata_metatable(mt_ptr, type_id); Ok(id as Integer) } pub(crate) unsafe fn push_userdata_metatable(&self, mut registry: RawUserDataRegistry) -> Result<()> { let state = self.state(); let mut stack_guard = 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, v) in registry.meta_fields { has_name = has_name || k == MetaMethod::Type; v?.push_into_stack(self)?; rawset_field(state, -2, MetaMethod::validate(&k)?)?; } // Set `__name/__type` if not provided if !has_name { let type_name = registry.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 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, v) in mem::take(&mut registry.fields) { v?.push_into_stack(self)?; rawset_field(state, -2, &k)?; } rawset_field(state, metatable_index, "__index")?; } _ => { ffi::lua_pop(state, 1); // Fields will be converted to functions and added to field getters } } } let mut field_getters_index = None; let field_getters_nrec = registry.field_getters.len() + registry.fields.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)?; } for (k, v) in registry.fields { unsafe extern "C-unwind" fn return_field(state: *mut ffi::lua_State) -> c_int { ffi::lua_pushvalue(state, ffi::lua_upvalueindex(1)); 1 } v?.push_into_stack(self)?; protect_lua!(state, 1, 1, fn(state) { ffi::lua_pushcclosure(state, return_field, 1); })?; rawset_field(state, -2, &k)?; } field_getters_index = Some(ffi::lua_absindex(state, -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)); } 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 => { // Set the new table as `__index` rawset_field(state, metatable_index, "__index")?; } _ => { methods_index = Some(ffi::lua_absindex(state, -1)); } } } ffi::lua_pushcfunction(state, registry.destructor); rawset_field(state, metatable_index, "__gc")?; init_userdata_metatable( state, metatable_index, field_getters_index, field_setters_index, methods_index, )?; // Update stack guard to keep metatable after return stack_guard.keep(1); Ok(()) } #[inline(always)] pub(crate) unsafe fn register_userdata_metatable(&self, mt_ptr: *const c_void, type_id: Option) { (*self.extra.get()).registered_userdata_mt.insert(mt_ptr, type_id); } #[inline(always)] pub(crate) unsafe fn deregister_userdata_metatable(&self, mt_ptr: *const c_void) { (*self.extra.get()).registered_userdata_mt.remove(&mt_ptr); if (*self.extra.get()).last_checked_userdata_mt.0 == mt_ptr { (*self.extra.get()).last_checked_userdata_mt = (ptr::null(), None); } } // 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> { match self.get_userdata_type_id_inner(self.state(), idx) { Ok(type_id) => Ok(type_id), Err(Error::UserDataTypeMismatch) if ffi::lua_type(self.state(), idx) != ffi::LUA_TUSERDATA => { // Report `FromLuaConversionError` instead let idx_type_name = CStr::from_ptr(ffi::luaL_typename(self.state(), idx)); let idx_type_name = idx_type_name.to_str().unwrap(); let message = format!("expected userdata of type '{}'", short_type_name::()); Err(Error::from_lua_conversion(idx_type_name, "userdata", message)) } Err(err) => Err(err), } } unsafe fn get_userdata_type_id_inner( &self, state: *mut ffi::lua_State, idx: c_int, ) -> Result> { let mt_ptr = get_metatable_ptr(state, idx); if mt_ptr.is_null() { return Err(Error::UserDataTypeMismatch); } // 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 { let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); callback_error_ext(state, (*upvalue).extra.get(), |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); match (*upvalue).data { Some(ref func) => func(rawlua, nargs), None => Err(Error::CallbackDestructed), } }) } let state = self.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 4)?; let func = Some(func); let extra = XRc::clone(&self.extra); let protect = !self.unlikely_memory_error(); push_internal_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 { // Ensure that the coroutine library is loaded #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] unsafe { if !(*self.extra.get()).libs.contains(StdLib::COROUTINE) { load_std_libs(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, |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 func = &*(*upvalue).data; let fut = func(rawlua, nargs); let extra = XRc::clone(&(*upvalue).extra); let protect = !rawlua.unlikely_memory_error(); push_internal_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)); callback_error_ext(state, (*upvalue).extra.get(), |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 if nresults < 3 => { // 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 extra = XRc::clone(&self.extra); let protect = !self.unlikely_memory_error(); let upvalue = AsyncCallbackUpvalue { data: func, extra }; push_internal_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::
("coroutine")?; // Prepare environment for the async poller let env = lua.create_table_with_capacity(0, 3)?; env.set("get_poll", get_poll)?; env.set("yield", coroutine.get::("yield")?)?; env.set("unpack", unsafe { 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_std_libs(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); } #[cfg(feature = "luau")] if libs.contains(StdLib::VECTOR) { requiref(state, ffi::LUA_VECLIBNAME, ffi::luaopen_vector, 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); } #[cfg(feature = "luajit")] if libs.contains(StdLib::FFI) { requiref(state, ffi::LUA_FFILIBNAME, ffi::luaopen_ffi, 1)?; ffi::lua_pop(state, 1); } Ok(()) }