Lots of changes, not sure if actually safe yet.
* Make Lua Send * Add Send bounds to (nearly) all instances where userdata and functions are passed to Lua * Add a "scope" method which takes a callback that accepts a `Scope`, and give `Scope` the ability to create functions and userdata that are !Send, *and also functions that are not even 'static!*.
This commit is contained in:
parent
7780a91e19
commit
cb25a99f70
|
@ -112,7 +112,7 @@ impl<'lua> FromLua<'lua> for AnyUserData<'lua> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'lua, T: UserData> ToLua<'lua> for T {
|
impl<'lua, T: Send + UserData> ToLua<'lua> for T {
|
||||||
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
|
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
|
||||||
Ok(Value::UserData(lua.create_userdata(self)?))
|
Ok(Value::UserData(lua.create_userdata(self)?))
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,6 +124,9 @@ extern "C" {
|
||||||
pub fn lua_setuservalue(state: *mut lua_State, index: c_int);
|
pub fn lua_setuservalue(state: *mut lua_State, index: c_int);
|
||||||
pub fn lua_getuservalue(state: *mut lua_State, index: c_int) -> c_int;
|
pub fn lua_getuservalue(state: *mut lua_State, index: c_int) -> c_int;
|
||||||
|
|
||||||
|
pub fn lua_getupvalue(state: *mut lua_State, funcindex: c_int, n: c_int) -> *const c_char;
|
||||||
|
pub fn lua_setupvalue(state: *mut lua_State, funcindex: c_int, n: c_int) -> *const c_char;
|
||||||
|
|
||||||
pub fn lua_settable(state: *mut lua_State, index: c_int);
|
pub fn lua_settable(state: *mut lua_State, index: c_int);
|
||||||
pub fn lua_rawset(state: *mut lua_State, index: c_int);
|
pub fn lua_rawset(state: *mut lua_State, index: c_int);
|
||||||
pub fn lua_setmetatable(state: *mut lua_State, index: c_int);
|
pub fn lua_setmetatable(state: *mut lua_State, index: c_int);
|
||||||
|
|
183
src/lua.rs
183
src/lua.rs
|
@ -1,4 +1,4 @@
|
||||||
use std::{ptr, str};
|
use std::{mem, process, ptr, str};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
@ -7,8 +7,7 @@ use std::any::TypeId;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::os::raw::{c_char, c_int, c_void};
|
use std::os::raw::{c_char, c_int, c_void};
|
||||||
use std::mem;
|
use std::panic::{catch_unwind, AssertUnwindSafe};
|
||||||
use std::process;
|
|
||||||
|
|
||||||
use libc;
|
use libc;
|
||||||
|
|
||||||
|
@ -30,12 +29,21 @@ pub struct Lua {
|
||||||
ephemeral: bool,
|
ephemeral: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Constructed by the `Lua::scope` method, allows temporarily passing to Lua userdata that is
|
||||||
|
/// !Send, and callbacks that are !Send and not 'static.
|
||||||
|
pub struct Scope<'lua> {
|
||||||
|
lua: &'lua Lua,
|
||||||
|
destructors: RefCell<Vec<Box<FnMut(*mut ffi::lua_State)>>>,
|
||||||
|
}
|
||||||
|
|
||||||
// Data associated with the main lua_State via lua_getextraspace.
|
// Data associated with the main lua_State via lua_getextraspace.
|
||||||
struct ExtraData {
|
struct ExtraData {
|
||||||
registered_userdata: HashMap<TypeId, c_int>,
|
registered_userdata: HashMap<TypeId, c_int>,
|
||||||
registry_drop_list: Arc<Mutex<Vec<c_int>>>,
|
registry_unref_list: Arc<Mutex<Vec<c_int>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for Lua {}
|
||||||
|
|
||||||
impl Drop for Lua {
|
impl Drop for Lua {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -259,7 +267,7 @@ impl Lua {
|
||||||
where
|
where
|
||||||
A: FromLuaMulti<'lua>,
|
A: FromLuaMulti<'lua>,
|
||||||
R: ToLuaMulti<'lua>,
|
R: ToLuaMulti<'lua>,
|
||||||
F: 'static + FnMut(&'lua Lua, A) -> Result<R>,
|
F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>,
|
||||||
{
|
{
|
||||||
self.create_callback_function(Box::new(move |lua, args| {
|
self.create_callback_function(Box::new(move |lua, args| {
|
||||||
func(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
|
func(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
|
||||||
|
@ -286,25 +294,9 @@ impl Lua {
|
||||||
/// Create a Lua userdata object from a custom userdata type.
|
/// Create a Lua userdata object from a custom userdata type.
|
||||||
pub fn create_userdata<T>(&self, data: T) -> Result<AnyUserData>
|
pub fn create_userdata<T>(&self, data: T) -> Result<AnyUserData>
|
||||||
where
|
where
|
||||||
T: UserData,
|
T: Send + UserData,
|
||||||
{
|
{
|
||||||
unsafe {
|
self.do_create_userdata(data)
|
||||||
stack_err_guard(self.state, 0, move || {
|
|
||||||
check_stack(self.state, 3);
|
|
||||||
|
|
||||||
push_userdata::<RefCell<T>>(self.state, RefCell::new(data))?;
|
|
||||||
|
|
||||||
ffi::lua_rawgeti(
|
|
||||||
self.state,
|
|
||||||
ffi::LUA_REGISTRYINDEX,
|
|
||||||
self.userdata_metatable::<T>()? as ffi::lua_Integer,
|
|
||||||
);
|
|
||||||
|
|
||||||
ffi::lua_setmetatable(self.state, -2);
|
|
||||||
|
|
||||||
Ok(AnyUserData(self.pop_ref(self.state)))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a handle to the global environment.
|
/// Returns a handle to the global environment.
|
||||||
|
@ -318,6 +310,28 @@ impl Lua {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Calls the given function with a `Scope` parameter, giving the function the ability to create
|
||||||
|
/// userdata from rust types that are !Send, and rust callbacks that are !Send and not '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 functions whose
|
||||||
|
/// lifetimes only outlive the scope lifetime.
|
||||||
|
pub fn scope<'lua, F, R>(&'lua self, f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut Scope<'lua>) -> R,
|
||||||
|
{
|
||||||
|
let mut scope = Scope {
|
||||||
|
lua: self,
|
||||||
|
destructors: RefCell::new(Vec::new()),
|
||||||
|
};
|
||||||
|
let r = f(&mut scope);
|
||||||
|
drop(scope);
|
||||||
|
r
|
||||||
|
}
|
||||||
|
|
||||||
/// Coerces a Lua value to a string.
|
/// Coerces a Lua value to a string.
|
||||||
///
|
///
|
||||||
/// The value must be a string (in which case this is a no-op) or a number.
|
/// The value must be a string (in which case this is a no-op) or a number.
|
||||||
|
@ -492,7 +506,8 @@ impl Lua {
|
||||||
|
|
||||||
Ok(RegistryKey {
|
Ok(RegistryKey {
|
||||||
registry_id,
|
registry_id,
|
||||||
drop_list: (*self.extra()).registry_drop_list.clone(),
|
unref_list: (*self.extra()).registry_unref_list.clone(),
|
||||||
|
drop_unref: true,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -504,7 +519,7 @@ impl Lua {
|
||||||
/// value previously placed by `create_registry_value`.
|
/// value previously placed by `create_registry_value`.
|
||||||
pub fn registry_value<'lua, T: FromLua<'lua>>(&'lua self, key: &RegistryKey) -> Result<T> {
|
pub fn registry_value<'lua, T: FromLua<'lua>>(&'lua self, key: &RegistryKey) -> Result<T> {
|
||||||
unsafe {
|
unsafe {
|
||||||
if !Arc::ptr_eq(&key.drop_list, &(*self.extra()).registry_drop_list) {
|
if !Arc::ptr_eq(&key.unref_list, &(*self.extra()).registry_unref_list) {
|
||||||
return Err(Error::MismatchedRegistryKey);
|
return Err(Error::MismatchedRegistryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -528,13 +543,12 @@ impl Lua {
|
||||||
/// `RegistryKey`s have been dropped.
|
/// `RegistryKey`s have been dropped.
|
||||||
pub fn remove_registry_value(&self, mut key: RegistryKey) -> Result<()> {
|
pub fn remove_registry_value(&self, mut key: RegistryKey) -> Result<()> {
|
||||||
unsafe {
|
unsafe {
|
||||||
if !Arc::ptr_eq(&key.drop_list, &(*self.extra()).registry_drop_list) {
|
if !Arc::ptr_eq(&key.unref_list, &(*self.extra()).registry_unref_list) {
|
||||||
return Err(Error::MismatchedRegistryKey);
|
return Err(Error::MismatchedRegistryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
ffi::luaL_unref(self.state, ffi::LUA_REGISTRYINDEX, key.registry_id);
|
ffi::luaL_unref(self.state, ffi::LUA_REGISTRYINDEX, key.registry_id);
|
||||||
// Don't adding to the registry drop list when dropping the key
|
key.drop_unref = false;
|
||||||
key.registry_id = ffi::LUA_REFNIL;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -546,7 +560,7 @@ impl Lua {
|
||||||
/// `Error::MismatchedRegistryKey` if passed a `RegistryKey` that was not created with a
|
/// `Error::MismatchedRegistryKey` if passed a `RegistryKey` that was not created with a
|
||||||
/// matching `Lua` state.
|
/// matching `Lua` state.
|
||||||
pub fn owns_registry_value(&self, key: &RegistryKey) -> bool {
|
pub fn owns_registry_value(&self, key: &RegistryKey) -> bool {
|
||||||
unsafe { Arc::ptr_eq(&key.drop_list, &(*self.extra()).registry_drop_list) }
|
unsafe { Arc::ptr_eq(&key.unref_list, &(*self.extra()).registry_unref_list) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove any registry values whose `RegistryKey`s have all been dropped. Unlike normal handle
|
/// Remove any registry values whose `RegistryKey`s have all been dropped. Unlike normal handle
|
||||||
|
@ -554,11 +568,11 @@ impl Lua {
|
||||||
/// can call this method to remove any unreachable registry values.
|
/// can call this method to remove any unreachable registry values.
|
||||||
pub fn expire_registry_values(&self) {
|
pub fn expire_registry_values(&self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let drop_list = mem::replace(
|
let unref_list = mem::replace(
|
||||||
(*self.extra()).registry_drop_list.lock().unwrap().as_mut(),
|
(*self.extra()).registry_unref_list.lock().unwrap().as_mut(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
);
|
);
|
||||||
for id in drop_list {
|
for id in unref_list {
|
||||||
ffi::luaL_unref(self.state, ffi::LUA_REGISTRYINDEX, id);
|
ffi::luaL_unref(self.state, ffi::LUA_REGISTRYINDEX, id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -694,6 +708,7 @@ impl Lua {
|
||||||
LuaRef {
|
LuaRef {
|
||||||
lua: self,
|
lua: self,
|
||||||
registry_id: registry_id,
|
registry_id: registry_id,
|
||||||
|
drop_unref: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -920,7 +935,7 @@ impl Lua {
|
||||||
|
|
||||||
let extra_data = Box::into_raw(Box::new(ExtraData {
|
let extra_data = Box::into_raw(Box::new(ExtraData {
|
||||||
registered_userdata: HashMap::new(),
|
registered_userdata: HashMap::new(),
|
||||||
registry_drop_list: Arc::new(Mutex::new(Vec::new())),
|
registry_unref_list: Arc::new(Mutex::new(Vec::new())),
|
||||||
}));
|
}));
|
||||||
*(ffi::lua_getextraspace(state) as *mut *mut ExtraData) = extra_data;
|
*(ffi::lua_getextraspace(state) as *mut *mut ExtraData) = extra_data;
|
||||||
});
|
});
|
||||||
|
@ -934,6 +949,11 @@ impl Lua {
|
||||||
|
|
||||||
fn create_callback_function<'lua>(&'lua self, func: Callback<'lua>) -> Result<Function<'lua>> {
|
fn create_callback_function<'lua>(&'lua self, func: Callback<'lua>) -> Result<Function<'lua>> {
|
||||||
unsafe extern "C" fn callback_call_impl(state: *mut ffi::lua_State) -> c_int {
|
unsafe extern "C" fn callback_call_impl(state: *mut ffi::lua_State) -> c_int {
|
||||||
|
if ffi::lua_type(state, ffi::lua_upvalueindex(1)) == ffi::LUA_TNIL {
|
||||||
|
ffi::lua_pushstring(state, cstr!("rust callback has been destructed"));
|
||||||
|
ffi::lua_error(state)
|
||||||
|
}
|
||||||
|
|
||||||
callback_error(state, || {
|
callback_error(state, || {
|
||||||
let lua = Lua {
|
let lua = Lua {
|
||||||
state: state,
|
state: state,
|
||||||
|
@ -976,7 +996,7 @@ impl Lua {
|
||||||
self.state,
|
self.state,
|
||||||
&FUNCTION_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
|
&FUNCTION_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
|
||||||
);
|
);
|
||||||
ffi::lua_gettable(self.state, ffi::LUA_REGISTRYINDEX);
|
ffi::lua_rawget(self.state, ffi::LUA_REGISTRYINDEX);
|
||||||
ffi::lua_setmetatable(self.state, -2);
|
ffi::lua_setmetatable(self.state, -2);
|
||||||
|
|
||||||
protect_lua_call(self.state, 1, 1, |state| {
|
protect_lua_call(self.state, 1, 1, |state| {
|
||||||
|
@ -988,9 +1008,104 @@ impl Lua {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn do_create_userdata<T>(&self, data: T) -> Result<AnyUserData>
|
||||||
|
where
|
||||||
|
T: UserData,
|
||||||
|
{
|
||||||
|
unsafe {
|
||||||
|
stack_err_guard(self.state, 0, move || {
|
||||||
|
check_stack(self.state, 3);
|
||||||
|
|
||||||
|
push_userdata::<RefCell<T>>(self.state, RefCell::new(data))?;
|
||||||
|
|
||||||
|
ffi::lua_rawgeti(
|
||||||
|
self.state,
|
||||||
|
ffi::LUA_REGISTRYINDEX,
|
||||||
|
self.userdata_metatable::<T>()? as ffi::lua_Integer,
|
||||||
|
);
|
||||||
|
|
||||||
|
ffi::lua_setmetatable(self.state, -2);
|
||||||
|
|
||||||
|
Ok(AnyUserData(self.pop_ref(self.state)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
unsafe fn extra(&self) -> *mut ExtraData {
|
unsafe fn extra(&self) -> *mut ExtraData {
|
||||||
*(ffi::lua_getextraspace(self.main_state) as *mut *mut ExtraData)
|
*(ffi::lua_getextraspace(self.main_state) as *mut *mut ExtraData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'lua> Scope<'lua> {
|
||||||
|
pub fn create_function<'scope, A, R, F>(&'scope self, mut func: F) -> Result<Function<'scope>>
|
||||||
|
where
|
||||||
|
A: FromLuaMulti<'scope>,
|
||||||
|
R: ToLuaMulti<'scope>,
|
||||||
|
F: 'scope + FnMut(&'scope Lua, A) -> Result<R>,
|
||||||
|
{
|
||||||
|
unsafe {
|
||||||
|
let mut f = self.lua
|
||||||
|
.create_callback_function(Box::new(move |lua, args| {
|
||||||
|
func(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
|
||||||
|
}))?;
|
||||||
|
f.0.drop_unref = false;
|
||||||
|
let mut destructors = self.destructors.borrow_mut();
|
||||||
|
let registry_id = f.0.registry_id;
|
||||||
|
destructors.push(Box::new(move |state| {
|
||||||
|
check_stack(state, 2);
|
||||||
|
ffi::lua_rawgeti(
|
||||||
|
state,
|
||||||
|
ffi::LUA_REGISTRYINDEX,
|
||||||
|
registry_id as ffi::lua_Integer,
|
||||||
|
);
|
||||||
|
ffi::lua_getupvalue(state, -1, 1);
|
||||||
|
destruct_userdata::<RefCell<Callback>>(state);
|
||||||
|
|
||||||
|
ffi::lua_pushnil(state);
|
||||||
|
ffi::lua_setupvalue(state, -2, 1);
|
||||||
|
|
||||||
|
ffi::lua_pop(state, 1);
|
||||||
|
}));
|
||||||
|
Ok(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_userdata<T>(&self, data: T) -> Result<AnyUserData>
|
||||||
|
where
|
||||||
|
T: UserData,
|
||||||
|
{
|
||||||
|
unsafe {
|
||||||
|
let mut u = self.lua.do_create_userdata(data)?;
|
||||||
|
u.0.drop_unref = false;
|
||||||
|
let mut destructors = self.destructors.borrow_mut();
|
||||||
|
let registry_id = u.0.registry_id;
|
||||||
|
destructors.push(Box::new(move |state| {
|
||||||
|
check_stack(state, 1);
|
||||||
|
ffi::lua_rawgeti(
|
||||||
|
state,
|
||||||
|
ffi::LUA_REGISTRYINDEX,
|
||||||
|
registry_id as ffi::lua_Integer,
|
||||||
|
);
|
||||||
|
destruct_userdata::<RefCell<T>>(state);
|
||||||
|
}));
|
||||||
|
Ok(u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'lua> Drop for Scope<'lua> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let state = self.lua.state;
|
||||||
|
for mut destructor in self.destructors.get_mut().drain(..) {
|
||||||
|
match catch_unwind(AssertUnwindSafe(move || destructor(state))) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(_) => {
|
||||||
|
eprintln!("Scope userdata Drop impl has panicked, aborting!");
|
||||||
|
process::abort()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static FUNCTION_METATABLE_REGISTRY_KEY: u8 = 0;
|
static FUNCTION_METATABLE_REGISTRY_KEY: u8 = 0;
|
||||||
|
|
|
@ -266,6 +266,7 @@ impl<'lua> Table<'lua> {
|
||||||
let next_key = Some(LuaRef {
|
let next_key = Some(LuaRef {
|
||||||
lua: self.0.lua,
|
lua: self.0.lua,
|
||||||
registry_id: ffi::LUA_REFNIL,
|
registry_id: ffi::LUA_REFNIL,
|
||||||
|
drop_unref: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
TablePairs {
|
TablePairs {
|
||||||
|
|
86
src/tests.rs
86
src/tests.rs
|
@ -1,6 +1,8 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::error;
|
use std::error;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
use std::cell::Cell;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::panic::catch_unwind;
|
use std::panic::catch_unwind;
|
||||||
|
|
||||||
use {Error, ExternalError, Function, Lua, Nil, Result, Table, UserData, Value, Variadic};
|
use {Error, ExternalError, Function, Lua, Nil, Result, Table, UserData, Value, Variadic};
|
||||||
|
@ -539,16 +541,16 @@ fn test_registry_value() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_drop_registry_value() {
|
fn test_drop_registry_value() {
|
||||||
struct MyUserdata(Rc<()>);
|
struct MyUserdata(Arc<()>);
|
||||||
|
|
||||||
impl UserData for MyUserdata {}
|
impl UserData for MyUserdata {}
|
||||||
|
|
||||||
let lua = Lua::new();
|
let lua = Lua::new();
|
||||||
|
|
||||||
let rc = Rc::new(());
|
let rc = Arc::new(());
|
||||||
|
|
||||||
let r = lua.create_registry_value(MyUserdata(rc.clone())).unwrap();
|
let r = lua.create_registry_value(MyUserdata(rc.clone())).unwrap();
|
||||||
assert_eq!(Rc::strong_count(&rc), 2);
|
assert_eq!(Arc::strong_count(&rc), 2);
|
||||||
|
|
||||||
drop(r);
|
drop(r);
|
||||||
lua.expire_registry_values();
|
lua.expire_registry_values();
|
||||||
|
@ -556,7 +558,7 @@ fn test_drop_registry_value() {
|
||||||
lua.exec::<()>(r#"collectgarbage("collect")"#, None)
|
lua.exec::<()>(r#"collectgarbage("collect")"#, None)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(Rc::strong_count(&rc), 1);
|
assert_eq!(Arc::strong_count(&rc), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -597,6 +599,72 @@ fn test_mismatched_registry_key() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scope_func() {
|
||||||
|
let rc = Rc::new(Cell::new(0));
|
||||||
|
|
||||||
|
let lua = Lua::new();
|
||||||
|
lua.scope(|scope| {
|
||||||
|
let r = rc.clone();
|
||||||
|
let f = scope
|
||||||
|
.create_function(move |_, ()| {
|
||||||
|
r.set(42);
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
lua.globals().set("bad", f.clone()).unwrap();
|
||||||
|
f.call::<_, ()>(()).unwrap();
|
||||||
|
});
|
||||||
|
assert_eq!(rc.get(), 42);
|
||||||
|
assert_eq!(Rc::strong_count(&rc), 1);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
lua.globals()
|
||||||
|
.get::<_, Function>("bad")
|
||||||
|
.unwrap()
|
||||||
|
.call::<_, ()>(())
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scope_drop() {
|
||||||
|
struct MyUserdata(Rc<()>);
|
||||||
|
impl UserData for MyUserdata {}
|
||||||
|
|
||||||
|
let rc = Rc::new(());
|
||||||
|
|
||||||
|
let lua = Lua::new();
|
||||||
|
lua.scope(|scope| {
|
||||||
|
lua.globals()
|
||||||
|
.set(
|
||||||
|
"test",
|
||||||
|
scope.create_userdata(MyUserdata(rc.clone())).unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(Rc::strong_count(&rc), 2);
|
||||||
|
});
|
||||||
|
assert_eq!(Rc::strong_count(&rc), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scope_capture() {
|
||||||
|
let mut i = 0;
|
||||||
|
|
||||||
|
let lua = Lua::new();
|
||||||
|
lua.scope(|scope| {
|
||||||
|
scope
|
||||||
|
.create_function(|_, ()| {
|
||||||
|
i = 42;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
.call::<_, ()>(())
|
||||||
|
.unwrap();
|
||||||
|
});
|
||||||
|
assert_eq!(i, 42);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Need to use compiletest-rs or similar to make sure these don't compile.
|
// TODO: Need to use compiletest-rs or similar to make sure these don't compile.
|
||||||
/*
|
/*
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -619,5 +687,15 @@ fn should_not_compile() {
|
||||||
globals.set("boom", lua.create_function(|_, _| {
|
globals.set("boom", lua.create_function(|_, _| {
|
||||||
lua.eval::<i32>("1 + 1", None)
|
lua.eval::<i32>("1 + 1", None)
|
||||||
})).unwrap();
|
})).unwrap();
|
||||||
|
|
||||||
|
// Should not allow Scope references to leak
|
||||||
|
struct MyUserdata(Rc<()>);
|
||||||
|
impl UserData for MyUserdata {}
|
||||||
|
|
||||||
|
let lua = Lua::new();
|
||||||
|
let mut r = None;
|
||||||
|
lua.scope(|scope| {
|
||||||
|
r = Some(scope.create_userdata(MyUserdata(Rc::new(()))).unwrap());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
28
src/types.rs
28
src/types.rs
|
@ -25,13 +25,14 @@ pub struct LightUserData(pub *mut c_void);
|
||||||
/// can be used in many situations where it would be impossible to store a regular handle value.
|
/// can be used in many situations where it would be impossible to store a regular handle value.
|
||||||
pub struct RegistryKey {
|
pub struct RegistryKey {
|
||||||
pub(crate) registry_id: c_int,
|
pub(crate) registry_id: c_int,
|
||||||
pub(crate) drop_list: Arc<Mutex<Vec<c_int>>>,
|
pub(crate) unref_list: Arc<Mutex<Vec<c_int>>>,
|
||||||
|
pub(crate) drop_unref: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for RegistryKey {
|
impl Drop for RegistryKey {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if self.registry_id != ffi::LUA_REFNIL {
|
if self.drop_unref {
|
||||||
self.drop_list.lock().unwrap().push(self.registry_id);
|
self.unref_list.lock().unwrap().push(self.registry_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,6 +43,7 @@ pub(crate) type Callback<'lua> =
|
||||||
pub(crate) struct LuaRef<'lua> {
|
pub(crate) struct LuaRef<'lua> {
|
||||||
pub lua: &'lua Lua,
|
pub lua: &'lua Lua,
|
||||||
pub registry_id: c_int,
|
pub registry_id: c_int,
|
||||||
|
pub drop_unref: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'lua> fmt::Debug for LuaRef<'lua> {
|
impl<'lua> fmt::Debug for LuaRef<'lua> {
|
||||||
|
@ -52,17 +54,27 @@ impl<'lua> fmt::Debug for LuaRef<'lua> {
|
||||||
|
|
||||||
impl<'lua> Clone for LuaRef<'lua> {
|
impl<'lua> Clone for LuaRef<'lua> {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
unsafe {
|
if self.drop_unref {
|
||||||
self.lua.push_ref(self.lua.state, self);
|
unsafe {
|
||||||
self.lua.pop_ref(self.lua.state)
|
self.lua.push_ref(self.lua.state, self);
|
||||||
|
self.lua.pop_ref(self.lua.state)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LuaRef {
|
||||||
|
lua: self.lua,
|
||||||
|
registry_id: self.registry_id,
|
||||||
|
drop_unref: self.drop_unref,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'lua> Drop for LuaRef<'lua> {
|
impl<'lua> Drop for LuaRef<'lua> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
unsafe {
|
if self.drop_unref {
|
||||||
ffi::luaL_unref(self.lua.state, ffi::LUA_REGISTRYINDEX, self.registry_id);
|
unsafe {
|
||||||
|
ffi::luaL_unref(self.lua.state, ffi::LUA_REGISTRYINDEX, self.registry_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,7 +89,7 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> {
|
||||||
where
|
where
|
||||||
A: FromLuaMulti<'lua>,
|
A: FromLuaMulti<'lua>,
|
||||||
R: ToLuaMulti<'lua>,
|
R: ToLuaMulti<'lua>,
|
||||||
M: 'static + for<'a> FnMut(&'lua Lua, &'a T, A) -> Result<R>,
|
M: 'static + Send + for<'a> FnMut(&'lua Lua, &'a T, A) -> Result<R>,
|
||||||
{
|
{
|
||||||
self.methods
|
self.methods
|
||||||
.insert(name.to_owned(), Self::box_method(method));
|
.insert(name.to_owned(), Self::box_method(method));
|
||||||
|
@ -104,7 +104,7 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> {
|
||||||
where
|
where
|
||||||
A: FromLuaMulti<'lua>,
|
A: FromLuaMulti<'lua>,
|
||||||
R: ToLuaMulti<'lua>,
|
R: ToLuaMulti<'lua>,
|
||||||
M: 'static + for<'a> FnMut(&'lua Lua, &'a mut T, A) -> Result<R>,
|
M: 'static + Send + for<'a> FnMut(&'lua Lua, &'a mut T, A) -> Result<R>,
|
||||||
{
|
{
|
||||||
self.methods
|
self.methods
|
||||||
.insert(name.to_owned(), Self::box_method_mut(method));
|
.insert(name.to_owned(), Self::box_method_mut(method));
|
||||||
|
@ -121,7 +121,7 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> {
|
||||||
where
|
where
|
||||||
A: FromLuaMulti<'lua>,
|
A: FromLuaMulti<'lua>,
|
||||||
R: ToLuaMulti<'lua>,
|
R: ToLuaMulti<'lua>,
|
||||||
F: 'static + FnMut(&'lua Lua, A) -> Result<R>,
|
F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>,
|
||||||
{
|
{
|
||||||
self.methods
|
self.methods
|
||||||
.insert(name.to_owned(), Self::box_function(function));
|
.insert(name.to_owned(), Self::box_function(function));
|
||||||
|
@ -139,7 +139,7 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> {
|
||||||
where
|
where
|
||||||
A: FromLuaMulti<'lua>,
|
A: FromLuaMulti<'lua>,
|
||||||
R: ToLuaMulti<'lua>,
|
R: ToLuaMulti<'lua>,
|
||||||
M: 'static + for<'a> FnMut(&'lua Lua, &'a T, A) -> Result<R>,
|
M: 'static + Send + for<'a> FnMut(&'lua Lua, &'a T, A) -> Result<R>,
|
||||||
{
|
{
|
||||||
self.meta_methods.insert(meta, Self::box_method(method));
|
self.meta_methods.insert(meta, Self::box_method(method));
|
||||||
}
|
}
|
||||||
|
@ -156,7 +156,7 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> {
|
||||||
where
|
where
|
||||||
A: FromLuaMulti<'lua>,
|
A: FromLuaMulti<'lua>,
|
||||||
R: ToLuaMulti<'lua>,
|
R: ToLuaMulti<'lua>,
|
||||||
M: 'static + for<'a> FnMut(&'lua Lua, &'a mut T, A) -> Result<R>,
|
M: 'static + Send + for<'a> FnMut(&'lua Lua, &'a mut T, A) -> Result<R>,
|
||||||
{
|
{
|
||||||
self.meta_methods.insert(meta, Self::box_method_mut(method));
|
self.meta_methods.insert(meta, Self::box_method_mut(method));
|
||||||
}
|
}
|
||||||
|
@ -170,7 +170,7 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> {
|
||||||
where
|
where
|
||||||
A: FromLuaMulti<'lua>,
|
A: FromLuaMulti<'lua>,
|
||||||
R: ToLuaMulti<'lua>,
|
R: ToLuaMulti<'lua>,
|
||||||
F: 'static + FnMut(&'lua Lua, A) -> Result<R>,
|
F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>,
|
||||||
{
|
{
|
||||||
self.meta_methods.insert(meta, Self::box_function(function));
|
self.meta_methods.insert(meta, Self::box_function(function));
|
||||||
}
|
}
|
||||||
|
@ -179,7 +179,7 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> {
|
||||||
where
|
where
|
||||||
A: FromLuaMulti<'lua>,
|
A: FromLuaMulti<'lua>,
|
||||||
R: ToLuaMulti<'lua>,
|
R: ToLuaMulti<'lua>,
|
||||||
F: 'static + FnMut(&'lua Lua, A) -> Result<R>,
|
F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>,
|
||||||
{
|
{
|
||||||
Box::new(move |lua, args| function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua))
|
Box::new(move |lua, args| function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua))
|
||||||
}
|
}
|
||||||
|
@ -188,7 +188,7 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> {
|
||||||
where
|
where
|
||||||
A: FromLuaMulti<'lua>,
|
A: FromLuaMulti<'lua>,
|
||||||
R: ToLuaMulti<'lua>,
|
R: ToLuaMulti<'lua>,
|
||||||
M: 'static + for<'a> FnMut(&'lua Lua, &'a T, A) -> Result<R>,
|
M: 'static + Send + for<'a> FnMut(&'lua Lua, &'a T, A) -> Result<R>,
|
||||||
{
|
{
|
||||||
Box::new(move |lua, mut args| {
|
Box::new(move |lua, mut args| {
|
||||||
if let Some(front) = args.pop_front() {
|
if let Some(front) = args.pop_front() {
|
||||||
|
@ -209,7 +209,7 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> {
|
||||||
where
|
where
|
||||||
A: FromLuaMulti<'lua>,
|
A: FromLuaMulti<'lua>,
|
||||||
R: ToLuaMulti<'lua>,
|
R: ToLuaMulti<'lua>,
|
||||||
M: 'static + for<'a> FnMut(&'lua Lua, &'a mut T, A) -> Result<R>,
|
M: 'static + Send + for<'a> FnMut(&'lua Lua, &'a mut T, A) -> Result<R>,
|
||||||
{
|
{
|
||||||
Box::new(move |lua, mut args| {
|
Box::new(move |lua, mut args| {
|
||||||
if let Some(front) = args.pop_front() {
|
if let Some(front) = args.pop_front() {
|
||||||
|
@ -433,7 +433,7 @@ impl<'lua> AnyUserData<'lua> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::rc::Rc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::{MetaMethod, UserData, UserDataMethods};
|
use super::{MetaMethod, UserData, UserDataMethods};
|
||||||
use error::ExternalError;
|
use error::ExternalError;
|
||||||
|
@ -590,11 +590,11 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn detroys_userdata() {
|
fn detroys_userdata() {
|
||||||
struct MyUserdata(Rc<()>);
|
struct MyUserdata(Arc<()>);
|
||||||
|
|
||||||
impl UserData for MyUserdata {}
|
impl UserData for MyUserdata {}
|
||||||
|
|
||||||
let rc = Rc::new(());
|
let rc = Arc::new(());
|
||||||
|
|
||||||
let lua = Lua::new();
|
let lua = Lua::new();
|
||||||
{
|
{
|
||||||
|
@ -602,9 +602,9 @@ mod tests {
|
||||||
globals.set("userdata", MyUserdata(rc.clone())).unwrap();
|
globals.set("userdata", MyUserdata(rc.clone())).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(Rc::strong_count(&rc), 2);
|
assert_eq!(Arc::strong_count(&rc), 2);
|
||||||
drop(lua); // should destroy all objects
|
drop(lua); // should destroy all objects
|
||||||
assert_eq!(Rc::strong_count(&rc), 1);
|
assert_eq!(Arc::strong_count(&rc), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
48
src/util.rs
48
src/util.rs
|
@ -250,18 +250,24 @@ pub unsafe fn get_userdata<T>(state: *mut ffi::lua_State, index: c_int) -> *mut
|
||||||
|
|
||||||
pub unsafe extern "C" fn userdata_destructor<T>(state: *mut ffi::lua_State) -> c_int {
|
pub unsafe extern "C" fn userdata_destructor<T>(state: *mut ffi::lua_State) -> c_int {
|
||||||
callback_error(state, || {
|
callback_error(state, || {
|
||||||
// We set the metatable of userdata on __gc to a special table with no __gc method and with
|
destruct_userdata::<T>(state);
|
||||||
// metamethods that trigger an error on access. We do this so that it will not be double
|
|
||||||
// dropped, and also so that it cannot be used or identified as any particular userdata type
|
|
||||||
// after the first call to __gc.
|
|
||||||
get_gc_userdata_metatable(state);
|
|
||||||
ffi::lua_setmetatable(state, -2);
|
|
||||||
let ud = &mut *(ffi::lua_touserdata(state, 1) as *mut T);
|
|
||||||
mem::replace(ud, mem::uninitialized());
|
|
||||||
Ok(0)
|
Ok(0)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pops the userdata off of the top of the stack and drops it
|
||||||
|
pub unsafe fn destruct_userdata<T>(state: *mut ffi::lua_State) {
|
||||||
|
// We set the metatable of userdata on __gc to a special table with no __gc method and with
|
||||||
|
// metamethods that trigger an error on access. We do this so that it will not be double
|
||||||
|
// dropped, and also so that it cannot be used or identified as any particular userdata type
|
||||||
|
// after the first call to __gc.
|
||||||
|
get_destructed_userdata_metatable(state);
|
||||||
|
ffi::lua_setmetatable(state, -2);
|
||||||
|
let ud = &mut *(ffi::lua_touserdata(state, -1) as *mut T);
|
||||||
|
ffi::lua_pop(state, 1);
|
||||||
|
mem::replace(ud, mem::uninitialized());
|
||||||
|
}
|
||||||
|
|
||||||
// In the context of a lua callback, this will call the given function and if the given function
|
// In the context of a lua callback, this will call the given function and if the given function
|
||||||
// returns an error, *or if the given function panics*, this will result in a call to lua_error (a
|
// returns an error, *or if the given function panics*, this will result in a call to lua_error (a
|
||||||
// longjmp). The error or panic is wrapped in such a way that when calling pop_error back on
|
// longjmp). The error or panic is wrapped in such a way that when calling pop_error back on
|
||||||
|
@ -517,7 +523,7 @@ unsafe fn get_error_metatable(state: *mut ffi::lua_State) -> c_int {
|
||||||
state,
|
state,
|
||||||
&ERROR_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
|
&ERROR_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
|
||||||
);
|
);
|
||||||
let t = ffi::lua_gettable(state, ffi::LUA_REGISTRYINDEX);
|
let t = ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX);
|
||||||
|
|
||||||
if t != ffi::LUA_TTABLE {
|
if t != ffi::LUA_TTABLE {
|
||||||
ffi::lua_pop(state, 1);
|
ffi::lua_pop(state, 1);
|
||||||
|
@ -558,7 +564,7 @@ unsafe fn get_panic_metatable(state: *mut ffi::lua_State) -> c_int {
|
||||||
state,
|
state,
|
||||||
&PANIC_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
|
&PANIC_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
|
||||||
);
|
);
|
||||||
let t = ffi::lua_gettable(state, ffi::LUA_REGISTRYINDEX);
|
let t = ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX);
|
||||||
|
|
||||||
if t != ffi::LUA_TTABLE {
|
if t != ffi::LUA_TTABLE {
|
||||||
ffi::lua_pop(state, 1);
|
ffi::lua_pop(state, 1);
|
||||||
|
@ -588,16 +594,19 @@ unsafe fn get_panic_metatable(state: *mut ffi::lua_State) -> c_int {
|
||||||
ffi::LUA_TTABLE
|
ffi::LUA_TTABLE
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn get_gc_userdata_metatable(state: *mut ffi::lua_State) -> c_int {
|
unsafe fn get_destructed_userdata_metatable(state: *mut ffi::lua_State) -> c_int {
|
||||||
static GC_USERDATA_METATABLE: u8 = 0;
|
static DESTRUCTED_USERDATA_METATABLE: u8 = 0;
|
||||||
|
|
||||||
unsafe extern "C" fn gc_error(state: *mut ffi::lua_State) -> c_int {
|
unsafe extern "C" fn destructed_error(state: *mut ffi::lua_State) -> c_int {
|
||||||
ffi::lua_pushstring(state, cstr!("userdata has been garbage collected"));
|
ffi::lua_pushstring(state, cstr!("userdata has been destructed"));
|
||||||
ffi::lua_error(state)
|
ffi::lua_error(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
ffi::lua_pushlightuserdata(state, &GC_USERDATA_METATABLE as *const u8 as *mut c_void);
|
ffi::lua_pushlightuserdata(
|
||||||
let t = ffi::lua_gettable(state, ffi::LUA_REGISTRYINDEX);
|
state,
|
||||||
|
&DESTRUCTED_USERDATA_METATABLE as *const u8 as *mut c_void,
|
||||||
|
);
|
||||||
|
let t = ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX);
|
||||||
|
|
||||||
if t != ffi::LUA_TTABLE {
|
if t != ffi::LUA_TTABLE {
|
||||||
ffi::lua_pop(state, 1);
|
ffi::lua_pop(state, 1);
|
||||||
|
@ -606,7 +615,10 @@ unsafe fn get_gc_userdata_metatable(state: *mut ffi::lua_State) -> c_int {
|
||||||
|
|
||||||
gc_guard(state, || {
|
gc_guard(state, || {
|
||||||
ffi::lua_newtable(state);
|
ffi::lua_newtable(state);
|
||||||
ffi::lua_pushlightuserdata(state, &GC_USERDATA_METATABLE as *const u8 as *mut c_void);
|
ffi::lua_pushlightuserdata(
|
||||||
|
state,
|
||||||
|
&DESTRUCTED_USERDATA_METATABLE as *const u8 as *mut c_void,
|
||||||
|
);
|
||||||
ffi::lua_pushvalue(state, -2);
|
ffi::lua_pushvalue(state, -2);
|
||||||
|
|
||||||
for &method in &[
|
for &method in &[
|
||||||
|
@ -637,7 +649,7 @@ unsafe fn get_gc_userdata_metatable(state: *mut ffi::lua_State) -> c_int {
|
||||||
cstr!("__ipairs"),
|
cstr!("__ipairs"),
|
||||||
] {
|
] {
|
||||||
ffi::lua_pushstring(state, method);
|
ffi::lua_pushstring(state, method);
|
||||||
ffi::lua_pushcfunction(state, gc_error);
|
ffi::lua_pushcfunction(state, destructed_error);
|
||||||
ffi::lua_rawset(state, -3);
|
ffi::lua_rawset(state, -3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue