use std::any::Any; use std::cell::Cell; use std::cell::RefCell; use std::marker::PhantomData; use std::mem; use std::os::raw::c_void; use std::rc::Rc; use crate::error::{Error, Result}; use crate::ffi; use crate::function::Function; use crate::lua::Lua; use crate::types::{Callback, LuaRef}; use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods}; use crate::util::{ assert_stack, init_userdata_metatable, protect_lua_closure, push_string, push_userdata, take_userdata, StackGuard, }; use crate::value::{FromLuaMulti, MultiValue, ToLuaMulti, Value}; /// Constructed by the [`Lua::scope`] method, allows temporarily creating Lua userdata and /// callbacks that are not required to be Send or 'static. /// /// See [`Lua::scope`] for more details. /// /// [`Lua::scope`]: struct.Lua.html#method.scope pub struct Scope<'lua, 'scope> { lua: &'lua Lua, destructors: RefCell, fn(LuaRef<'lua>) -> Box)>>, _scope_invariant: PhantomData>, } impl<'lua, 'scope> Scope<'lua, 'scope> { pub(crate) fn new(lua: &'lua Lua) -> Scope<'lua, 'scope> { Scope { lua, destructors: RefCell::new(Vec::new()), _scope_invariant: PhantomData, } } /// Wraps a Rust function or closure, creating a callable Lua function handle to it. /// /// This is a version of [`Lua::create_function`] that creates a callback which expires on /// scope drop. See [`Lua::scope`] for more details. /// /// [`Lua::create_function`]: struct.Lua.html#method.create_function /// [`Lua::scope`]: struct.Lua.html#method.scope pub fn create_function<'callback, A, R, F>(&'callback self, func: F) -> Result> where A: FromLuaMulti<'callback>, R: ToLuaMulti<'callback>, F: 'scope + Fn(&'callback Lua, A) -> Result, { // Safe, because 'scope must outlive 'callback (due to Self containing 'scope), however the // callback itself must be 'scope lifetime, so the function should not be able to capture // anything of 'callback lifetime. 'scope can't be shortened due to being invariant, and // the 'callback lifetime here can't be enlarged due to coming from a universal // quantification in Lua::scope. // // I hope I got this explanation right, but in any case this is tested with compiletest_rs // to make sure callbacks can't capture handles with lifetime outside the scope, inside the // scope, and owned inside the callback itself. unsafe { self.create_callback(Box::new(move |lua, args| { func(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua) })) } } /// Wraps a Rust mutable closure, creating a callable Lua function handle to it. /// /// This is a version of [`Lua::create_function_mut`] that creates a callback which expires /// on scope drop. See [`Lua::scope`] and [`Scope::create_function`] for more details. /// /// [`Lua::create_function_mut`]: struct.Lua.html#method.create_function_mut /// [`Lua::scope`]: struct.Lua.html#method.scope /// [`Scope::create_function`]: #method.create_function pub fn create_function_mut<'callback, A, R, F>( &'callback self, func: F, ) -> Result> where A: FromLuaMulti<'callback>, R: ToLuaMulti<'callback>, F: 'scope + FnMut(&'callback Lua, A) -> Result, { let func = RefCell::new(func); self.create_function(move |lua, args| { (&mut *func .try_borrow_mut() .map_err(|_| Error::RecursiveMutCallback)?)(lua, args) }) } /// Create a Lua userdata object from a custom userdata type. /// /// This is a version of [`Lua::create_userdata`] that creates a userdata which expires on /// scope drop, and does not require that the userdata type be Send (but still requires that the /// UserData be 'static). See [`Lua::scope`] for more details. /// /// [`Lua::create_userdata`]: struct.Lua.html#method.create_userdata /// [`Lua::scope`]: struct.Lua.html#method.scope pub fn create_static_userdata(&self, data: T) -> Result> where T: 'static + UserData, { // Safe even though T may not be Send, because the parent Lua cannot be sent to another // thread while the Scope is alive (or the returned AnyUserData handle even). unsafe { let u = self.lua.make_userdata(data)?; self.destructors.borrow_mut().push((u.0.clone(), |u| { let state = u.lua.state; assert_stack(state, 2); u.lua.push_ref(&u); // We know the destructor has not run yet because we hold a reference to the // userdata. Box::new(take_userdata::>(state)) })); Ok(u) } } /// Create a Lua userdata object from a custom userdata type. /// /// This is a version of [`Lua::create_userdata`] that creates a userdata which expires on /// scope drop, and does not require that the userdata type be Send or 'static. See /// [`Lua::scope`] for more details. /// /// Lifting the requirement that the UserData type be 'static comes with some important /// limitations, so if you only need to eliminate the Send requirement, it is probably better to /// use [`Scope::create_static_userdata`] instead. /// /// The main limitation that comes from using non-'static userdata is that the produced userdata /// will no longer have a `TypeId` associated with it, becuase `TypeId` can only work for /// 'static types. This means that it is impossible, once the userdata is created, to get a /// reference to it back *out* of an `AnyUserData` handle. This also implies that the /// "function" type methods that can be added via [`UserDataMethods`] (the ones that accept /// `AnyUserData` as a first parameter) are vastly less useful. Also, there is no way to re-use /// a single metatable for multiple non-'static types, so there is a higher cost associated with /// creating the userdata metatable each time a new userdata is created. /// /// [`create_static_userdata`]: #method.create_static_userdata /// [`Lua::create_userdata`]: struct.Lua.html#method.create_userdata /// [`Lua::scope`]: struct.Lua.html#method.scope /// [`UserDataMethods`]: trait.UserDataMethods.html pub fn create_nonstatic_userdata(&self, data: T) -> Result> where T: 'scope + UserData, { let data = Rc::new(RefCell::new(data)); // 'callback outliving 'scope is a lie to make the types work out, required due to the // inability to work with the more correct callback type that is universally quantified over // 'lua. This is safe though, because `UserData::add_methods` does not get to pick the 'lua // lifetime, so none of the static methods UserData types can add can possibly capture // parameters. fn wrap_method<'scope, 'lua, 'callback: 'scope, T: 'scope>( scope: &Scope<'lua, 'scope>, data: Rc>, method: NonStaticMethod<'callback, T>, ) -> Result> { // On methods that actually receive the userdata, we fake a type check on the passed in // userdata, where we pretend there is a unique type per call to // `Scope::create_nonstatic_userdata`. You can grab a method from a userdata and call // it on a mismatched userdata type, which when using normal 'static userdata will fail // with a type mismatch, but here without this check would proceed as though you had // called the method on the original value (since we otherwise completely ignore the // first argument). let check_data = data.clone(); let check_ud_type = move |lua: &'callback Lua, value| { if let Some(value) = value { if let Value::UserData(u) = value { unsafe { assert_stack(lua.state, 1); lua.push_ref(&u.0); ffi::lua_getuservalue(lua.state, -1); #[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))] { ffi::lua_pushinteger(lua.state, 1); ffi::lua_gettable(lua.state, -2); ffi::lua_remove(lua.state, -2); } return ffi::lua_touserdata(lua.state, -1) == check_data.as_ptr() as *mut c_void; } } } false }; match method { NonStaticMethod::Method(method) => { let method_data = data.clone(); let f = Box::new(move |lua, mut args: MultiValue<'callback>| { if !check_ud_type(lua, args.pop_front()) { return Err(Error::UserDataTypeMismatch); } let data = method_data .try_borrow() .map_err(|_| Error::UserDataBorrowError)?; method(lua, &*data, args) }); unsafe { scope.create_callback(f) } } NonStaticMethod::MethodMut(method) => { let method = RefCell::new(method); let method_data = data.clone(); let f = Box::new(move |lua, mut args: MultiValue<'callback>| { if !check_ud_type(lua, args.pop_front()) { return Err(Error::UserDataTypeMismatch); } let mut method = method .try_borrow_mut() .map_err(|_| Error::RecursiveMutCallback)?; let mut data = method_data .try_borrow_mut() .map_err(|_| Error::UserDataBorrowMutError)?; (&mut *method)(lua, &mut *data, args) }); unsafe { scope.create_callback(f) } } NonStaticMethod::Function(function) => unsafe { scope.create_callback(function) }, NonStaticMethod::FunctionMut(function) => { let function = RefCell::new(function); let f = Box::new(move |lua, args| { (&mut *function .try_borrow_mut() .map_err(|_| Error::RecursiveMutCallback)?)( lua, args ) }); unsafe { scope.create_callback(f) } } } } let mut ud_methods = NonStaticUserDataMethods::default(); T::add_methods(&mut ud_methods); unsafe { let lua = self.lua; let _sg = StackGuard::new(lua.state); assert_stack(lua.state, 6); push_userdata(lua.state, ())?; #[cfg(feature = "lua53")] ffi::lua_pushlightuserdata(lua.state, data.as_ptr() as *mut c_void); #[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))] protect_lua_closure(lua.state, 0, 1, |state| { // Lua 5.2/5.1 allows to store only table. Then we will wrap the value. ffi::lua_createtable(state, 1, 0); ffi::lua_pushinteger(state, 1); ffi::lua_pushlightuserdata(state, data.as_ptr() as *mut c_void); ffi::lua_settable(state, -3); })?; ffi::lua_setuservalue(lua.state, -2); protect_lua_closure(lua.state, 0, 1, move |state| { ffi::lua_newtable(state); })?; for (k, m) in ud_methods.meta_methods { push_string(lua.state, k.name())?; lua.push_value(Value::Function(wrap_method(self, data.clone(), m)?))?; protect_lua_closure(lua.state, 3, 1, |state| { ffi::lua_rawset(state, -3); })?; } if ud_methods.methods.is_empty() { init_userdata_metatable::<()>(lua.state, -1, None)?; } else { protect_lua_closure(lua.state, 0, 1, |state| { ffi::lua_newtable(state); })?; for (k, m) in ud_methods.methods { push_string(lua.state, &k)?; lua.push_value(Value::Function(wrap_method(self, data.clone(), m)?))?; protect_lua_closure(lua.state, 3, 1, |state| { ffi::lua_rawset(state, -3); })?; } init_userdata_metatable::<()>(lua.state, -2, Some(-1))?; ffi::lua_pop(lua.state, 1); } ffi::lua_setmetatable(lua.state, -2); Ok(AnyUserData(lua.pop_ref())) } } // Unsafe, because the callback can improperly capture any value with 'callback scope, such as // improperly capturing an argument. Since the 'callback lifetime is chosen by the user and the // lifetime of the callback itself is 'scope (non-'static), the borrow checker will happily pick // a 'callback that outlives 'scope to allow this. In order for this to be safe, the callback // must NOT capture any parameters. unsafe fn create_callback<'callback>( &self, f: Callback<'callback, 'scope>, ) -> Result> { let f = mem::transmute::, Callback<'lua, 'static>>(f); let f = self.lua.create_callback(f)?; let mut destructors = self.destructors.borrow_mut(); destructors.push((f.0.clone(), |f| { let state = f.lua.state; assert_stack(state, 3); f.lua.push_ref(&f); ffi::lua_getupvalue(state, -1, 1); // We know the destructor has not run yet because we hold a reference to the callback. let ud = take_userdata::(state); ffi::lua_pushnil(state); ffi::lua_setupvalue(state, -2, 1); ffi::lua_pop(state, 1); Box::new(ud) })); Ok(f) } } impl<'lua, 'scope> Drop for Scope<'lua, 'scope> { fn drop(&mut self) { // We separate the action of invalidating the userdata in Lua and actually dropping the // userdata type into two phases. This is so that, in the event a userdata drop panics, we // can be sure that all of the userdata in Lua is actually invalidated. // All destructors are non-panicking, so this is fine let to_drop = self .destructors .get_mut() .drain(..) .map(|(r, dest)| dest(r)) .collect::>(); drop(to_drop); } } enum NonStaticMethod<'lua, T> { Method(Box) -> Result>>), MethodMut(Box) -> Result>>), Function(Box) -> Result>>), FunctionMut(Box) -> Result>>), } struct NonStaticUserDataMethods<'lua, T: UserData> { methods: Vec<(Vec, NonStaticMethod<'lua, T>)>, meta_methods: Vec<(MetaMethod, NonStaticMethod<'lua, T>)>, } impl<'lua, T: UserData> Default for NonStaticUserDataMethods<'lua, T> { fn default() -> NonStaticUserDataMethods<'lua, T> { NonStaticUserDataMethods { methods: Vec::new(), meta_methods: Vec::new(), } } } impl<'lua, T: UserData> UserDataMethods<'lua, T> for NonStaticUserDataMethods<'lua, T> { fn add_method(&mut self, name: &S, method: M) where S: ?Sized + AsRef<[u8]>, A: FromLuaMulti<'lua>, R: ToLuaMulti<'lua>, M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result, { self.methods.push(( name.as_ref().to_vec(), NonStaticMethod::Method(Box::new(move |lua, ud, args| { method(lua, ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua) })), )); } fn add_method_mut(&mut self, name: &S, mut method: M) where S: ?Sized + AsRef<[u8]>, A: FromLuaMulti<'lua>, R: ToLuaMulti<'lua>, M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result, { self.methods.push(( name.as_ref().to_vec(), NonStaticMethod::MethodMut(Box::new(move |lua, ud, args| { method(lua, ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua) })), )); } fn add_function(&mut self, name: &S, function: F) where S: ?Sized + AsRef<[u8]>, A: FromLuaMulti<'lua>, R: ToLuaMulti<'lua>, F: 'static + Send + Fn(&'lua Lua, A) -> Result, { self.methods.push(( name.as_ref().to_vec(), NonStaticMethod::Function(Box::new(move |lua, args| { function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua) })), )); } fn add_function_mut(&mut self, name: &S, mut function: F) where S: ?Sized + AsRef<[u8]>, A: FromLuaMulti<'lua>, R: ToLuaMulti<'lua>, F: 'static + Send + FnMut(&'lua Lua, A) -> Result, { self.methods.push(( name.as_ref().to_vec(), NonStaticMethod::FunctionMut(Box::new(move |lua, args| { function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua) })), )); } fn add_meta_method(&mut self, meta: MetaMethod, method: M) where A: FromLuaMulti<'lua>, R: ToLuaMulti<'lua>, M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result, { self.meta_methods.push(( meta, NonStaticMethod::Method(Box::new(move |lua, ud, args| { method(lua, ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua) })), )); } fn add_meta_method_mut(&mut self, meta: MetaMethod, mut method: M) where A: FromLuaMulti<'lua>, R: ToLuaMulti<'lua>, M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result, { self.meta_methods.push(( meta, NonStaticMethod::MethodMut(Box::new(move |lua, ud, args| { method(lua, ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua) })), )); } fn add_meta_function(&mut self, meta: MetaMethod, function: F) where A: FromLuaMulti<'lua>, R: ToLuaMulti<'lua>, F: 'static + Send + Fn(&'lua Lua, A) -> Result, { self.meta_methods.push(( meta, NonStaticMethod::Function(Box::new(move |lua, args| { function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua) })), )); } fn add_meta_function_mut(&mut self, meta: MetaMethod, mut function: F) where A: FromLuaMulti<'lua>, R: ToLuaMulti<'lua>, F: 'static + Send + FnMut(&'lua Lua, A) -> Result, { self.meta_methods.push(( meta, NonStaticMethod::FunctionMut(Box::new(move |lua, args| { function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua) })), )); } }