diff --git a/Cargo.toml b/Cargo.toml index 7121eb0..aaa8bf4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,7 @@ luajit-src = { version = "210.0.0", optional = true } [dev-dependencies] rustyline = "6.0" criterion = "0.3" -trybuild = "1.0" +trybuild = "1.0.27" futures = "0.3.4" hyper = "0.13" tokio = { version = "0.2.18", features = ["full"] } diff --git a/src/conversion.rs b/src/conversion.rs index e64b797..972325c 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -12,7 +12,7 @@ use crate::lua::Lua; use crate::string::String; use crate::table::Table; use crate::thread::Thread; -use crate::types::{LightUserData, Number, MaybeSend}; +use crate::types::{LightUserData, MaybeSend, Number}; use crate::userdata::{AnyUserData, UserData}; use crate::value::{FromLua, Nil, ToLua, Value}; diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index c2cf4ef..b28979d 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -40,7 +40,7 @@ pub use self::lua::{ pub use self::lua::{lua_KContext, lua_KFunction}; #[cfg(any(feature = "lua51", feature = "luajit"))] -pub use self::lua::lua_setfenv; +pub use self::lua::{lua_getfenv, lua_setfenv}; // C API functions pub use self::lua::{ diff --git a/src/lib.rs b/src/lib.rs index 28cc85e..c0eec27 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,6 +59,7 @@ mod ffi; mod function; mod lua; mod multi; +mod scope; mod stdlib; mod string; mod table; @@ -74,6 +75,7 @@ pub use crate::error::{Error, ExternalError, ExternalResult, Result}; pub use crate::function::Function; pub use crate::lua::{Chunk, Lua}; pub use crate::multi::Variadic; +pub use crate::scope::Scope; pub use crate::stdlib::StdLib; pub use crate::string::String; pub use crate::table::{Table, TableExt, TablePairs, TableSequence}; diff --git a/src/lua.rs b/src/lua.rs index 4a1401b..c282b68 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -10,11 +10,12 @@ use std::{mem, ptr, str}; use crate::error::{Error, Result}; use crate::ffi; use crate::function::Function; +use crate::scope::Scope; use crate::stdlib::StdLib; use crate::string::String; use crate::table::Table; use crate::thread::Thread; -use crate::types::{Callback, Integer, LightUserData, LuaRef, Number, RegistryKey, MaybeSend}; +use crate::types::{Callback, Integer, LightUserData, LuaRef, MaybeSend, Number, RegistryKey}; use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods}; use crate::util::{ assert_stack, callback_error, check_stack, get_gc_userdata, get_main_state, @@ -611,6 +612,53 @@ impl Lua { } } + /// 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, F>(&'lua self, f: F) -> Result + where + 'lua: 'scope, + R: 'static, + F: FnOnce(&Scope<'lua, 'scope>) -> Result, + { + f(&Scope::new(self)) + } + + /// An asynchronous version of [`scope`] that allows to create scoped async functions and + /// execute them. + /// + /// [`scope`]: #method.scope + #[cfg(feature = "async")] + pub fn async_scope<'lua, 'scope, R, F, FR>( + &'lua self, + f: F, + ) -> LocalBoxFuture<'scope, Result> + where + 'lua: 'scope, + R: 'static, + F: FnOnce(Scope<'lua, 'scope>) -> FR, + FR: 'scope + Future>, + { + Box::pin(f(Scope::new(self))) + } + /// Attempts to coerce a Lua value into a String in a manner consistent with Lua's internal /// behavior. /// diff --git a/src/scope.rs b/src/scope.rs new file mode 100644 index 0000000..7c12f77 --- /dev/null +++ b/src/scope.rs @@ -0,0 +1,601 @@ +use std::any::Any; +use std::cell::{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, MaybeSend}; +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}; + +#[cfg(feature = "async")] +use { + crate::types::AsyncCallback, + futures_core::future::Future, + futures_util::future::{self, TryFutureExt}, + std::os::raw::c_char, +}; + +/// 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>) -> Vec>)>>, + _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) + }) + } + + /// Wraps a Rust async function or closure, creating a callable Lua function handle to it. + /// + /// This is a version of [`Lua::create_async_function`] that creates a callback which expires on + /// scope drop. See [`Lua::scope`] and [`Lua::async_scope`] for more details. + /// + /// [`Lua::create_async_function`]: struct.Lua.html#method.create_async_function + /// [`Lua::scope`]: struct.Lua.html#method.scope + /// [`Lua::async_scope`]: struct.Lua.html#method.async_scope + #[cfg(feature = "async")] + pub fn create_async_function<'callback, A, R, F, FR>( + &'callback self, + func: F, + ) -> Result> + where + A: FromLuaMulti<'callback>, + R: ToLuaMulti<'callback>, + F: 'scope + Fn(&'callback Lua, A) -> FR, + FR: 'callback + Future>, + { + unsafe { + self.create_async_callback(Box::new(move |lua, args| { + let args = match A::from_lua_multi(args, lua) { + Ok(args) => args, + Err(e) => return Box::pin(future::err(e)), + }; + Box::pin(func(lua, args).and_then(move |ret| future::ready(ret.to_lua_multi(lua)))) + })) + } + } + + /// 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_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. + vec![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_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. + /// + /// [`Scope::create_userdata`]: #method.create_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_rawgeti(lua.state, -1, 1); + 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_pushlightuserdata(state, data.as_ptr() as *mut c_void); + ffi::lua_rawseti(state, -2, 1); + })?; + 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); + + // We know the destructor has not run yet because we hold a reference to the callback. + + ffi::lua_getupvalue(state, -1, 1); + let ud1 = take_userdata::(state); + ffi::lua_pushnil(state); + ffi::lua_setupvalue(state, -2, 1); + + ffi::lua_getupvalue(state, -1, 2); + let ud2 = take_userdata::(state); + ffi::lua_pushnil(state); + ffi::lua_setupvalue(state, -2, 2); + + ffi::lua_pop(state, 1); + vec![Box::new(ud1), Box::new(ud2)] + })); + Ok(f) + } + + #[cfg(feature = "async")] + unsafe fn create_async_callback<'callback>( + &self, + f: AsyncCallback<'callback, 'scope>, + ) -> Result> { + let f = mem::transmute::, AsyncCallback<'lua, 'static>>(f); + let f = self.lua.create_async_callback(f)?; + + let mut destructors = self.destructors.borrow_mut(); + destructors.push((f.0.clone(), |f| { + let state = f.lua.state; + assert_stack(state, 4); + f.lua.push_ref(&f); + + // We know the destructor has not run yet because we hold a reference to the callback. + + // First, get the environment table + #[cfg(any(feature = "lua53", feature = "lua52"))] + ffi::lua_getupvalue(state, -1, 1); + #[cfg(any(feature = "lua51", feature = "luajit"))] + ffi::lua_getfenv(state, -1); + + // Then, get the get_poll() closure using the corresponding key + let key = "get_poll"; + ffi::lua_pushlstring(state, key.as_ptr() as *const c_char, key.len()); + ffi::lua_rawget(state, -2); + + // Finally, destroy all upvalues + ffi::lua_getupvalue(state, -1, 1); + let ud1 = take_userdata::(state); + ffi::lua_pushnil(state); + ffi::lua_setupvalue(state, -2, 1); + + ffi::lua_getupvalue(state, -1, 2); + let ud2 = take_userdata::(state); + ffi::lua_pushnil(state); + ffi::lua_setupvalue(state, -2, 2); + + ffi::lua_pop(state, 1); + + vec![Box::new(ud1), Box::new(ud2)] + })); + + 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(..) + .flat_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: AsRef<[u8]> + ?Sized, + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + M: 'static + MaybeSend + 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: AsRef<[u8]> + ?Sized, + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + M: 'static + MaybeSend + 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) + })), + )); + } + + #[cfg(feature = "async")] + fn add_async_method(&mut self, _name: &S, _method: M) + where + T: Clone, + S: AsRef<[u8]> + ?Sized, + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + M: 'static + MaybeSend + Fn(&'lua Lua, T, A) -> MR, + MR: 'lua + Future>, + { + // The panic should never happen as async non-static code wouldn't compile + // Non-static lifetime must be bounded to 'lua lifetime + mlua_panic!("asynchronous methods are not supported for non-static userdata") + } + + fn add_function(&mut self, name: &S, function: F) + where + S: AsRef<[u8]> + ?Sized, + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + F: 'static + MaybeSend + 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: AsRef<[u8]> + ?Sized, + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + F: 'static + MaybeSend + 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) + })), + )); + } + + #[cfg(feature = "async")] + fn add_async_function(&mut self, _name: &S, _function: F) + where + T: Clone, + S: AsRef<[u8]> + ?Sized, + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + F: 'static + MaybeSend + Fn(&'lua Lua, A) -> FR, + FR: 'lua + Future>, + { + // The panic should never happen as async non-static code wouldn't compile + // Non-static lifetime must be bounded to 'lua lifetime + mlua_panic!("asynchronous functions are not supported for non-static userdata") + } + + fn add_meta_method(&mut self, meta: MetaMethod, method: M) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + M: 'static + MaybeSend + 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 + MaybeSend + 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 + MaybeSend + 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 + MaybeSend + 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) + })), + )); + } +} diff --git a/tests/async.rs b/tests/async.rs index 8ca22c7..8f360ab 100644 --- a/tests/async.rs +++ b/tests/async.rs @@ -1,5 +1,7 @@ #![cfg(feature = "async")] +use std::cell::Cell; +use std::rc::Rc; use std::sync::{ atomic::{AtomicI64, Ordering}, Arc, @@ -286,8 +288,8 @@ async fn test_async_userdata() -> Result<()> { r#" assert(userdata:get_value() == 11) userdata:set_value(12) - assert(userdata:get_value() == 12) assert(userdata.sleep(5) == "elapsed:5ms") + assert(userdata:get_value() == 12) "#, ) .exec_async() @@ -295,3 +297,106 @@ async fn test_async_userdata() -> Result<()> { Ok(()) } + +#[tokio::test] +async fn test_async_scope() -> Result<()> { + let ref lua = Lua::new(); + + let ref rc = Rc::new(Cell::new(0)); + + let fut = lua.async_scope(|scope| async move { + let f = scope.create_async_function(move |_, n: u64| { + let rc2 = rc.clone(); + async move { + rc2.set(42); + Delay::new(Duration::from_millis(n)).await; + assert_eq!(Rc::strong_count(&rc2), 2); + Ok(()) + } + })?; + + lua.globals().set("f", f.clone())?; + + assert_eq!(Rc::strong_count(rc), 1); + let _ = f.call_async::(10).await?; + assert_eq!(Rc::strong_count(rc), 1); + + Ok(()) + }); + + assert_eq!(Rc::strong_count(rc), 1); + let _ = fut.await?; + + match lua + .globals() + .get::<_, Function>("f")? + .call_async::<_, ()>(10) + .await + { + Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() { + Error::CallbackDestructed => {} + e => panic!("expected `CallbackDestructed` error cause, got {:?}", e), + }, + r => panic!("improper return for destructed function: {:?}", r), + }; + + Ok(()) +} + +#[tokio::test] +async fn test_async_scope_userdata() -> Result<()> { + #[derive(Clone)] + struct MyUserData(Arc); + + impl UserData for MyUserData { + fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_async_method("get_value", |_, data, ()| async move { + Delay::new(Duration::from_millis(10)).await; + Ok(data.0.load(Ordering::Relaxed)) + }); + + methods.add_async_method("set_value", |_, data, n| async move { + Delay::new(Duration::from_millis(10)).await; + data.0.store(n, Ordering::Relaxed); + Ok(()) + }); + + methods.add_async_function("sleep", |_, n| async move { + Delay::new(Duration::from_millis(n)).await; + Ok(format!("elapsed:{}ms", n)) + }); + } + } + + let ref lua = Lua::new(); + + let ref arc = Arc::new(AtomicI64::new(11)); + + lua.async_scope(|scope| async move { + let ud = scope.create_userdata(MyUserData(arc.clone()))?; + lua.globals().set("userdata", ud)?; + lua.load( + r#" + assert(userdata:get_value() == 11) + userdata:set_value(12) + assert(userdata.sleep(5) == "elapsed:5ms") + assert(userdata:get_value() == 12) + "#, + ) + .exec_async() + .await + }) + .await?; + + assert_eq!(Arc::strong_count(arc), 1); + + match lua.load("userdata:get_value()").exec_async().await { + Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() { + Error::CallbackDestructed => {} + e => panic!("expected `CallbackDestructed` error cause, got {:?}", e), + }, + r => panic!("improper return for destructed userdata: {:?}", r), + }; + + Ok(()) +} diff --git a/tests/compile_fail/async_nonstatic_userdata.rs b/tests/compile_fail/async_nonstatic_userdata.rs new file mode 100644 index 0000000..8aede32 --- /dev/null +++ b/tests/compile_fail/async_nonstatic_userdata.rs @@ -0,0 +1,17 @@ +use mlua::{Lua, UserData, UserDataMethods}; + +fn main() { + let ref lua = Lua::new(); + + #[derive(Clone)] + struct MyUserData<'a>(&'a i64); + + impl<'a> UserData for MyUserData<'a> { + fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_async_method("print", |_, data, ()| async move { + println!("{}", data.0); + Ok(()) + }); + } + } +} diff --git a/tests/compile_fail/async_nonstatic_userdata.stderr b/tests/compile_fail/async_nonstatic_userdata.stderr new file mode 100644 index 0000000..25e9e5d --- /dev/null +++ b/tests/compile_fail/async_nonstatic_userdata.stderr @@ -0,0 +1,36 @@ +error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements + --> $DIR/scope_async_userdata.rs:11:72 + | +11 | methods.add_async_method("print", |_, data, ()| async move { + | ________________________________________________________________________^ +12 | | println!("{}", data.0); +13 | | Ok(()) +14 | | }); + | |_____________^ + | +note: first, the lifetime cannot outlive the lifetime `'a` as defined on the impl at 9:10... + --> $DIR/scope_async_userdata.rs:9:10 + | +9 | impl<'a> UserData for MyUserData<'a> { + | ^^ +note: ...so that the types are compatible + --> $DIR/scope_async_userdata.rs:11:72 + | +11 | methods.add_async_method("print", |_, data, ()| async move { + | ________________________________________________________________________^ +12 | | println!("{}", data.0); +13 | | Ok(()) +14 | | }); + | |_____________^ + = note: expected `main::MyUserData<'_>` + found `main::MyUserData<'a>` +note: but, the lifetime must be valid for the lifetime `'lua` as defined on the method body at 10:24... + --> $DIR/scope_async_userdata.rs:10:24 + | +10 | fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + | ^^^^ +note: ...so that the type `impl std::future::Future` will meet its required lifetime bounds + --> $DIR/scope_async_userdata.rs:11:21 + | +11 | methods.add_async_method("print", |_, data, ()| async move { + | ^^^^^^^^^^^^^^^^ diff --git a/tests/compile_fail/lua_norefunwindsafe.stderr b/tests/compile_fail/lua_norefunwindsafe.stderr index edd2803..7d03bed 100644 --- a/tests/compile_fail/lua_norefunwindsafe.stderr +++ b/tests/compile_fail/lua_norefunwindsafe.stderr @@ -9,49 +9,3 @@ error[E0277]: the type `std::cell::UnsafeCell<()>` may contain interior mutabili = note: required because it appears within the type `mlua::lua::Lua` = note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua` = note: required because it appears within the type `[closure@$DIR/tests/compile_fail/lua_norefunwindsafe.rs:7:18: 7:48 lua:&mlua::lua::Lua]` - -error[E0277]: the type `std::cell::UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary - --> $DIR/lua_norefunwindsafe.rs:7:5 - | -7 | catch_unwind(|| lua.create_table().unwrap()); - | ^^^^^^^^^^^^ `std::cell::UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary - | - = help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell` - = note: required because it appears within the type `std::cell::Cell` - = note: required because it appears within the type `std::rc::RcBox>` - = note: required because it appears within the type `std::marker::PhantomData>>` - = note: required because it appears within the type `std::rc::Rc>` - = note: required because it appears within the type `mlua::lua::Lua` - = note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua` - = note: required because it appears within the type `[closure@$DIR/tests/compile_fail/lua_norefunwindsafe.rs:7:18: 7:48 lua:&mlua::lua::Lua]` - -error[E0277]: the type `std::cell::UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary - --> $DIR/lua_norefunwindsafe.rs:7:5 - | -7 | catch_unwind(|| lua.create_table().unwrap()); - | ^^^^^^^^^^^^ `std::cell::UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary - | - = help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell` - = note: required because it appears within the type `std::cell::RefCell` - = note: required because it appears within the type `std::rc::RcBox>` - = note: required because it appears within the type `std::marker::PhantomData>>` - = note: required because it appears within the type `std::rc::Rc>` - = note: required because it appears within the type `mlua::lua::Lua` - = note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua` - = note: required because it appears within the type `[closure@$DIR/tests/compile_fail/lua_norefunwindsafe.rs:7:18: 7:48 lua:&mlua::lua::Lua]` - -error[E0277]: the type `std::cell::UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary - --> $DIR/lua_norefunwindsafe.rs:7:5 - | -7 | catch_unwind(|| lua.create_table().unwrap()); - | ^^^^^^^^^^^^ `std::cell::UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary - | - = help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell` - = note: required because it appears within the type `std::cell::Cell` - = note: required because it appears within the type `std::cell::RefCell` - = note: required because it appears within the type `std::rc::RcBox>` - = note: required because it appears within the type `std::marker::PhantomData>>` - = note: required because it appears within the type `std::rc::Rc>` - = note: required because it appears within the type `mlua::lua::Lua` - = note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua` - = note: required because it appears within the type `[closure@$DIR/tests/compile_fail/lua_norefunwindsafe.rs:7:18: 7:48 lua:&mlua::lua::Lua]` diff --git a/tests/compile_fail/ref_nounwindsafe.stderr b/tests/compile_fail/ref_nounwindsafe.stderr index ba6e696..2d78b81 100644 --- a/tests/compile_fail/ref_nounwindsafe.stderr +++ b/tests/compile_fail/ref_nounwindsafe.stderr @@ -11,55 +11,3 @@ error[E0277]: the type `std::cell::UnsafeCell<()>` may contain interior mutabili = note: required because it appears within the type `mlua::types::LuaRef<'_>` = note: required because it appears within the type `mlua::table::Table<'_>` = note: required because it appears within the type `[closure@$DIR/tests/compile_fail/ref_nounwindsafe.rs:8:18: 8:54 table:mlua::table::Table<'_>]` - -error[E0277]: the type `std::cell::UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary - --> $DIR/ref_nounwindsafe.rs:8:5 - | -8 | catch_unwind(move || table.set("a", "b").unwrap()); - | ^^^^^^^^^^^^ `std::cell::UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary - | - = help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell` - = note: required because it appears within the type `std::cell::Cell` - = note: required because it appears within the type `std::rc::RcBox>` - = note: required because it appears within the type `std::marker::PhantomData>>` - = note: required because it appears within the type `std::rc::Rc>` - = note: required because it appears within the type `mlua::lua::Lua` - = note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua` - = note: required because it appears within the type `mlua::types::LuaRef<'_>` - = note: required because it appears within the type `mlua::table::Table<'_>` - = note: required because it appears within the type `[closure@$DIR/tests/compile_fail/ref_nounwindsafe.rs:8:18: 8:54 table:mlua::table::Table<'_>]` - -error[E0277]: the type `std::cell::UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary - --> $DIR/ref_nounwindsafe.rs:8:5 - | -8 | catch_unwind(move || table.set("a", "b").unwrap()); - | ^^^^^^^^^^^^ `std::cell::UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary - | - = help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell` - = note: required because it appears within the type `std::cell::RefCell` - = note: required because it appears within the type `std::rc::RcBox>` - = note: required because it appears within the type `std::marker::PhantomData>>` - = note: required because it appears within the type `std::rc::Rc>` - = note: required because it appears within the type `mlua::lua::Lua` - = note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua` - = note: required because it appears within the type `mlua::types::LuaRef<'_>` - = note: required because it appears within the type `mlua::table::Table<'_>` - = note: required because it appears within the type `[closure@$DIR/tests/compile_fail/ref_nounwindsafe.rs:8:18: 8:54 table:mlua::table::Table<'_>]` - -error[E0277]: the type `std::cell::UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary - --> $DIR/ref_nounwindsafe.rs:8:5 - | -8 | catch_unwind(move || table.set("a", "b").unwrap()); - | ^^^^^^^^^^^^ `std::cell::UnsafeCell` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary - | - = help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell` - = note: required because it appears within the type `std::cell::Cell` - = note: required because it appears within the type `std::cell::RefCell` - = note: required because it appears within the type `std::rc::RcBox>` - = note: required because it appears within the type `std::marker::PhantomData>>` - = note: required because it appears within the type `std::rc::Rc>` - = note: required because it appears within the type `mlua::lua::Lua` - = note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua` - = note: required because it appears within the type `mlua::types::LuaRef<'_>` - = note: required because it appears within the type `mlua::table::Table<'_>` - = note: required because it appears within the type `[closure@$DIR/tests/compile_fail/ref_nounwindsafe.rs:8:18: 8:54 table:mlua::table::Table<'_>]` diff --git a/tests/compile_fail/scope_callback_capture.rs b/tests/compile_fail/scope_callback_capture.rs new file mode 100644 index 0000000..927c36d --- /dev/null +++ b/tests/compile_fail/scope_callback_capture.rs @@ -0,0 +1,18 @@ +use mlua::{Lua, Table}; + +fn main() { + let lua = Lua::new(); + lua.scope(|scope| { + let mut inner: Option = None; + let f = scope + .create_function_mut(move |_, t: Table| { + if let Some(old) = inner.take() { + // Access old callback `Lua`. + } + inner = Some(t); + Ok(()) + })?; + f.call::<_, ()>(lua.create_table()?)?; + Ok(()) + }); +} diff --git a/tests/compile_fail/scope_callback_capture.stderr b/tests/compile_fail/scope_callback_capture.stderr new file mode 100644 index 0000000..8e9dec5 --- /dev/null +++ b/tests/compile_fail/scope_callback_capture.stderr @@ -0,0 +1,45 @@ +error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements + --> $DIR/scope_callback_capture.rs:8:14 + | +8 | .create_function_mut(move |_, t: Table| { + | ^^^^^^^^^^^^^^^^^^^ + | +note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 5:15... + --> $DIR/scope_callback_capture.rs:5:15 + | +5 | lua.scope(|scope| { + | _______________^ +6 | | let mut inner: Option
= None; +7 | | let f = scope +8 | | .create_function_mut(move |_, t: Table| { +... | +16 | | Ok(()) +17 | | }); + | |_____^ +note: ...so that reference does not outlive borrowed content + --> $DIR/scope_callback_capture.rs:7:17 + | +7 | let f = scope + | ^^^^^ +note: but, the lifetime must be valid for the method call at 5:5... + --> $DIR/scope_callback_capture.rs:5:5 + | +5 | / lua.scope(|scope| { +6 | | let mut inner: Option
= None; +7 | | let f = scope +8 | | .create_function_mut(move |_, t: Table| { +... | +16 | | Ok(()) +17 | | }); + | |______^ +note: ...so that a type/lifetime parameter is in scope here + --> $DIR/scope_callback_capture.rs:5:5 + | +5 | / lua.scope(|scope| { +6 | | let mut inner: Option
= None; +7 | | let f = scope +8 | | .create_function_mut(move |_, t: Table| { +... | +16 | | Ok(()) +17 | | }); + | |______^ diff --git a/tests/compile_fail/scope_callback_inner.rs b/tests/compile_fail/scope_callback_inner.rs new file mode 100644 index 0000000..037c6ac --- /dev/null +++ b/tests/compile_fail/scope_callback_inner.rs @@ -0,0 +1,15 @@ +use mlua::{Lua, Table}; + +fn main() { + let lua = Lua::new(); + lua.scope(|scope| { + let mut inner: Option
= None; + let f = scope + .create_function_mut(|_, t: Table| { + inner = Some(t); + Ok(()) + })?; + f.call::<_, ()>(lua.create_table()?)?; + Ok(()) + }); +} diff --git a/tests/compile_fail/scope_callback_inner.stderr b/tests/compile_fail/scope_callback_inner.stderr new file mode 100644 index 0000000..1c5f3b5 --- /dev/null +++ b/tests/compile_fail/scope_callback_inner.stderr @@ -0,0 +1,45 @@ +error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements + --> $DIR/scope_callback_inner.rs:8:14 + | +8 | .create_function_mut(|_, t: Table| { + | ^^^^^^^^^^^^^^^^^^^ + | +note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 5:15... + --> $DIR/scope_callback_inner.rs:5:15 + | +5 | lua.scope(|scope| { + | _______________^ +6 | | let mut inner: Option
= None; +7 | | let f = scope +8 | | .create_function_mut(|_, t: Table| { +... | +13 | | Ok(()) +14 | | }); + | |_____^ +note: ...so that reference does not outlive borrowed content + --> $DIR/scope_callback_inner.rs:7:17 + | +7 | let f = scope + | ^^^^^ +note: but, the lifetime must be valid for the method call at 5:5... + --> $DIR/scope_callback_inner.rs:5:5 + | +5 | / lua.scope(|scope| { +6 | | let mut inner: Option
= None; +7 | | let f = scope +8 | | .create_function_mut(|_, t: Table| { +... | +13 | | Ok(()) +14 | | }); + | |______^ +note: ...so that a type/lifetime parameter is in scope here + --> $DIR/scope_callback_inner.rs:5:5 + | +5 | / lua.scope(|scope| { +6 | | let mut inner: Option
= None; +7 | | let f = scope +8 | | .create_function_mut(|_, t: Table| { +... | +13 | | Ok(()) +14 | | }); + | |______^ diff --git a/tests/compile_fail/scope_callback_outer.rs b/tests/compile_fail/scope_callback_outer.rs new file mode 100644 index 0000000..7c9974e --- /dev/null +++ b/tests/compile_fail/scope_callback_outer.rs @@ -0,0 +1,15 @@ +use mlua::{Lua, Table}; + +fn main() { + let lua = Lua::new(); + let mut outer: Option
= None; + lua.scope(|scope| { + let f = scope + .create_function_mut(|_, t: Table| { + outer = Some(t); + Ok(()) + })?; + f.call::<_, ()>(lua.create_table()?)?; + Ok(()) + }); +} diff --git a/tests/compile_fail/scope_callback_outer.stderr b/tests/compile_fail/scope_callback_outer.stderr new file mode 100644 index 0000000..e1f98ce --- /dev/null +++ b/tests/compile_fail/scope_callback_outer.stderr @@ -0,0 +1,11 @@ +error: borrowed data cannot be stored outside of its closure + --> $DIR/scope_callback_outer.rs:7:17 + | +5 | let mut outer: Option
= None; + | --------- ...so that variable is valid at time of its declaration +6 | lua.scope(|scope| { + | ------- borrowed data cannot outlive this closure +7 | let f = scope + | ^^^^^ cannot be stored outside of its closure +8 | .create_function_mut(|_, t: Table| { + | ------------------- cannot infer an appropriate lifetime... diff --git a/tests/compile_fail/scope_invariance.rs b/tests/compile_fail/scope_invariance.rs new file mode 100644 index 0000000..e4f4ea7 --- /dev/null +++ b/tests/compile_fail/scope_invariance.rs @@ -0,0 +1,23 @@ +use mlua::Lua; + +struct Test { + field: i32, +} + +fn main() { + let lua = Lua::new(); + lua.scope(|scope| { + let f = { + let mut test = Test { field: 0 }; + + scope + .create_function_mut(|_, ()| { + test.field = 42; + //~^ error: `test` does not live long enough + Ok(()) + })? + }; + + f.call::<_, ()>(()) + }); +} diff --git a/tests/compile_fail/scope_invariance.stderr b/tests/compile_fail/scope_invariance.stderr new file mode 100644 index 0000000..ee2a525 --- /dev/null +++ b/tests/compile_fail/scope_invariance.stderr @@ -0,0 +1,25 @@ +error[E0373]: closure may outlive the current function, but it borrows `test`, which is owned by the current function + --> $DIR/scope_invariance.rs:14:38 + | +9 | lua.scope(|scope| { + | ----- has type `&mlua::scope::Scope<'_, '1>` +... +14 | .create_function_mut(|_, ()| { + | ^^^^^^^ may outlive borrowed value `test` +15 | test.field = 42; + | ---- `test` is borrowed here + | +note: function requires argument type to outlive `'1` + --> $DIR/scope_invariance.rs:13:13 + | +13 | / scope +14 | | .create_function_mut(|_, ()| { +15 | | test.field = 42; +16 | | //~^ error: `test` does not live long enough +17 | | Ok(()) +18 | | })? + | |__________________^ +help: to force the closure to take ownership of `test` (and any other referenced variables), use the `move` keyword + | +14 | .create_function_mut(move |_, ()| { + | ^^^^^^^^^^^^ diff --git a/tests/compile_fail/scope_mutable_aliasing.rs b/tests/compile_fail/scope_mutable_aliasing.rs new file mode 100644 index 0000000..6bdf498 --- /dev/null +++ b/tests/compile_fail/scope_mutable_aliasing.rs @@ -0,0 +1,15 @@ +use mlua::{Lua, UserData}; + +fn main() { + struct MyUserData<'a>(&'a mut i32); + impl<'a> UserData for MyUserData<'a> {}; + + let mut i = 1; + + let lua = Lua::new(); + lua.scope(|scope| { + let _a = scope.create_nonstatic_userdata(MyUserData(&mut i)).unwrap(); + let _b = scope.create_nonstatic_userdata(MyUserData(&mut i)).unwrap(); + Ok(()) + }); +} diff --git a/tests/compile_fail/scope_mutable_aliasing.stderr b/tests/compile_fail/scope_mutable_aliasing.stderr new file mode 100644 index 0000000..c661826 --- /dev/null +++ b/tests/compile_fail/scope_mutable_aliasing.stderr @@ -0,0 +1,9 @@ +error[E0499]: cannot borrow `i` as mutable more than once at a time + --> $DIR/scope_mutable_aliasing.rs:12:61 + | +11 | let _a = scope.create_nonstatic_userdata(MyUserData(&mut i)).unwrap(); + | ------ first mutable borrow occurs here +12 | let _b = scope.create_nonstatic_userdata(MyUserData(&mut i)).unwrap(); + | ------------------------- ^^^^^^ second mutable borrow occurs here + | | + | first borrow later used by call diff --git a/tests/compile_fail/scope_userdata_borrow.rs b/tests/compile_fail/scope_userdata_borrow.rs new file mode 100644 index 0000000..6546633 --- /dev/null +++ b/tests/compile_fail/scope_userdata_borrow.rs @@ -0,0 +1,19 @@ +use mlua::{Lua, UserData}; + +fn main() { + // Should not allow userdata borrow to outlive lifetime of AnyUserData handle + struct MyUserData<'a>(&'a i32); + impl<'a> UserData for MyUserData<'a> {}; + + let igood = 1; + + let lua = Lua::new(); + lua.scope(|scope| { + let _ugood = scope.create_nonstatic_userdata(MyUserData(&igood)).unwrap(); + let _ubad = { + let ibad = 42; + scope.create_nonstatic_userdata(MyUserData(&ibad)).unwrap(); + }; + Ok(()) + }); +} diff --git a/tests/compile_fail/scope_userdata_borrow.stderr b/tests/compile_fail/scope_userdata_borrow.stderr new file mode 100644 index 0000000..1e422f4 --- /dev/null +++ b/tests/compile_fail/scope_userdata_borrow.stderr @@ -0,0 +1,13 @@ +error[E0597]: `ibad` does not live long enough + --> $DIR/scope_userdata_borrow.rs:15:56 + | +11 | lua.scope(|scope| { + | ----- has type `&mlua::scope::Scope<'_, '1>` +... +15 | scope.create_nonstatic_userdata(MyUserData(&ibad)).unwrap(); + | -------------------------------------------^^^^^-- + | | | + | | borrowed value does not live long enough + | argument requires that `ibad` is borrowed for `'1` +16 | }; + | - `ibad` dropped here while still borrowed diff --git a/tests/scope.rs b/tests/scope.rs new file mode 100644 index 0000000..d622dfa --- /dev/null +++ b/tests/scope.rs @@ -0,0 +1,228 @@ +use std::cell::Cell; +use std::rc::Rc; + +use mlua::{Error, Function, Lua, MetaMethod, Result, String, UserData, UserDataMethods}; + +#[test] +fn scope_func() -> Result<()> { + let lua = Lua::new(); + + let rc = Rc::new(Cell::new(0)); + lua.scope(|scope| { + let r = rc.clone(); + let f = scope.create_function(move |_, ()| { + r.set(42); + Ok(()) + })?; + lua.globals().set("bad", f.clone())?; + f.call::<_, ()>(())?; + assert_eq!(Rc::strong_count(&rc), 2); + Ok(()) + })?; + assert_eq!(rc.get(), 42); + assert_eq!(Rc::strong_count(&rc), 1); + + match lua.globals().get::<_, Function>("bad")?.call::<_, ()>(()) { + Err(Error::CallbackError { .. }) => {} + r => panic!("improper return for destructed function: {:?}", r), + }; + + Ok(()) +} + +#[test] +fn scope_drop() -> Result<()> { + let lua = Lua::new(); + + struct MyUserdata(Rc<()>); + impl UserData for MyUserdata { + fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_method("method", |_, _, ()| Ok(())); + } + } + + let rc = Rc::new(()); + + lua.scope(|scope| { + lua.globals() + .set("test", scope.create_userdata(MyUserdata(rc.clone()))?)?; + assert_eq!(Rc::strong_count(&rc), 2); + Ok(()) + })?; + assert_eq!(Rc::strong_count(&rc), 1); + + match lua.load("test:method()").exec() { + Err(Error::CallbackError { .. }) => {} + r => panic!("improper return for destructed userdata: {:?}", r), + }; + + Ok(()) +} + +#[test] +fn scope_capture() -> Result<()> { + let lua = Lua::new(); + + let mut i = 0; + lua.scope(|scope| { + scope + .create_function_mut(|_, ()| { + i = 42; + Ok(()) + })? + .call::<_, ()>(()) + })?; + assert_eq!(i, 42); + + Ok(()) +} + +#[test] +fn outer_lua_access() -> Result<()> { + let lua = Lua::new(); + + let table = lua.create_table()?; + lua.scope(|scope| { + scope + .create_function_mut(|_, ()| table.set("a", "b"))? + .call::<_, ()>(()) + })?; + assert_eq!(table.get::<_, String>("a")?, "b"); + + Ok(()) +} + +#[test] +fn scope_userdata_methods() -> Result<()> { + struct MyUserData<'a>(&'a Cell); + + impl<'a> UserData for MyUserData<'a> { + fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_method("inc", |_, data, ()| { + data.0.set(data.0.get() + 1); + Ok(()) + }); + + methods.add_method("dec", |_, data, ()| { + data.0.set(data.0.get() - 1); + Ok(()) + }); + } + } + + let lua = Lua::new(); + + let i = Cell::new(42); + let f: Function = lua + .load( + r#" + function(u) + u:inc() + u:inc() + u:inc() + u:dec() + end + "#, + ) + .eval()?; + + lua.scope(|scope| f.call::<_, ()>(scope.create_nonstatic_userdata(MyUserData(&i))?))?; + + assert_eq!(i.get(), 44); + + Ok(()) +} + +#[test] +fn scope_userdata_functions() -> Result<()> { + struct MyUserData<'a>(&'a i64); + + impl<'a> UserData for MyUserData<'a> { + fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_meta_function(MetaMethod::Add, |lua, ()| { + let globals = lua.globals(); + globals.set("i", globals.get::<_, i64>("i")? + 1)?; + Ok(()) + }); + methods.add_meta_function(MetaMethod::Sub, |lua, ()| { + let globals = lua.globals(); + globals.set("i", globals.get::<_, i64>("i")? + 1)?; + Ok(()) + }); + } + } + + let lua = Lua::new(); + + let dummy = 0; + let f = lua + .load( + r#" + i = 0 + return function(u) + _ = u + u + _ = u - 1 + _ = 1 + u + end + "#, + ) + .eval::()?; + + lua.scope(|scope| f.call::<_, ()>(scope.create_nonstatic_userdata(MyUserData(&dummy))?))?; + + assert_eq!(lua.globals().get::<_, i64>("i")?, 3); + + Ok(()) +} + +#[test] +fn scope_userdata_mismatch() -> Result<()> { + struct MyUserData<'a>(&'a Cell); + + impl<'a> UserData for MyUserData<'a> { + fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_method("inc", |_, data, ()| { + data.0.set(data.0.get() + 1); + Ok(()) + }); + } + } + + let lua = Lua::new(); + + lua.load( + r#" + function okay(a, b) + a.inc(a) + b.inc(b) + end + function bad(a, b) + a.inc(b) + end + "#, + ) + .exec()?; + + let a = Cell::new(1); + let b = Cell::new(1); + + let okay: Function = lua.globals().get("okay")?; + let bad: Function = lua.globals().get("bad")?; + + lua.scope(|scope| { + let au = scope.create_nonstatic_userdata(MyUserData(&a))?; + let bu = scope.create_nonstatic_userdata(MyUserData(&b))?; + assert!(okay.call::<_, ()>((au.clone(), bu.clone())).is_ok()); + match bad.call::<_, ()>((au, bu)) { + Err(Error::CallbackError { ref cause, .. }) => match *cause.as_ref() { + Error::UserDataTypeMismatch => {} + ref other => panic!("wrong error type {:?}", other), + }, + Err(other) => panic!("wrong error type {:?}", other), + Ok(_) => panic!("incorrectly returned Ok"), + } + Ok(()) + })?; + + Ok(()) +}