Merge pull request #68 from chucklefish/scope

Lots of changes, not sure if actually safe yet.
This commit is contained in:
kyren 2018-02-07 17:07:13 -05:00 committed by GitHub
commit 728e8ea714
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 344 additions and 112 deletions

View File

@ -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)?))
} }

View File

@ -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);

View File

@ -1,14 +1,12 @@
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;
use std::ffi::CString; use std::ffi::CString;
use std::any::TypeId; use std::any::{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::process;
use libc; use libc;
@ -30,12 +28,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) -> Box<Any>>>>,
}
// 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<Option<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 {
@ -49,6 +56,7 @@ impl Drop for Lua {
} }
let extra_data = *(ffi::lua_getextraspace(self.state) as *mut *mut ExtraData); let extra_data = *(ffi::lua_getextraspace(self.state) as *mut *mut ExtraData);
*(*extra_data).registry_unref_list.lock().unwrap() = None;
Box::from_raw(extra_data); Box::from_raw(extra_data);
ffi::lua_close(self.state); ffi::lua_close(self.state);
@ -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(), &mut *(*self.extra()).registry_unref_list.lock().unwrap(),
Vec::new(), Some(Vec::new()),
); );
for id in drop_list { for id in unref_list.unwrap() {
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(Some(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,106 @@ 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);
let ud = take_userdata::<RefCell<Callback>>(state);
ffi::lua_pushnil(state);
ffi::lua_setupvalue(state, -2, 1);
ffi::lua_pop(state, 1);
Box::new(ud)
}));
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,
);
Box::new(take_userdata::<RefCell<T>>(state))
}));
Ok(u)
}
}
}
impl<'lua> Drop for Scope<'lua> {
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.
let state = self.lua.state;
let to_drop = self.destructors
.get_mut()
.drain(..)
.map(|mut destructor| destructor(state))
.collect::<Vec<_>>();
drop(to_drop);
}
}
static FUNCTION_METATABLE_REGISTRY_KEY: u8 = 0; static FUNCTION_METATABLE_REGISTRY_KEY: u8 = 0;

View File

@ -6,48 +6,54 @@ macro_rules! cstr {
// A panic that clears the given lua stack before panicking // A panic that clears the given lua stack before panicking
macro_rules! lua_panic { macro_rules! lua_panic {
($state:expr) => {
{
$crate::ffi::lua_settor($state, 0);
panic!("rlua internal error");
}
};
($state:expr, $msg:expr) => { ($state:expr, $msg:expr) => {
{ {
$crate::ffi::lua_settop($state, 0); $crate::ffi::lua_settop($state, 0);
panic!(concat!("rlua internal error: ", $msg)); panic!($msg);
} }
}; };
($state:expr, $fmt:expr, $($arg:tt)+) => { ($state:expr, $msg:expr, $($arg:tt)+) => {
{ {
$crate::ffi::lua_settop($state, 0); $crate::ffi::lua_settop($state, 0);
panic!(concat!("rlua internal error: ", $fmt), $($arg)+); panic!($msg, $($arg)+);
} }
}; };
} }
// An assert that clears the given lua stack before panicking // An assert that clears the given lua stack before panicking
macro_rules! lua_assert { macro_rules! lua_assert {
($state:expr, $cond:expr) => {
if !$cond {
$crate::ffi::lua_settop($state, 0);
panic!("rlua internal error");
}
};
($state:expr, $cond:expr, $msg:expr) => { ($state:expr, $cond:expr, $msg:expr) => {
if !$cond { if !$cond {
$crate::ffi::lua_settop($state, 0); $crate::ffi::lua_settop($state, 0);
panic!(concat!("rlua internal error: ", $msg)); panic!($msg);
} }
}; };
($state:expr, $cond:expr, $fmt:expr, $($arg:tt)+) => { ($state:expr, $cond:expr, $msg:expr, $($arg:tt)+) => {
if !$cond { if !$cond {
$crate::ffi::lua_settop($state, 0); $crate::ffi::lua_settop($state, 0);
panic!(concat!("rlua internal error: ", $fmt), $($arg)+); panic!($msg, $($arg)+);
} }
}; };
} }
macro_rules! lua_internal_panic {
($state:expr, $msg:expr) => {
lua_panic!($state, concat!("rlua internal error: ", $msg));
};
($state:expr, $msg:expr, $($arg:tt)+) => {
lua_panic!($state, concat!("rlua internal error: ", $msg), $($arg)+);
};
}
macro_rules! lua_internal_assert {
($state:expr, $cond:expr, $msg:expr) => {
lua_assert!($state, $cond, concat!("rlua internal error: ", $msg));
};
($state:expr, $cond:expr, $msg:expr, $($arg:tt)+) => {
lua_assert!($state, $cond, concat!("rlua internal error: ", $msg), $($arg)+);
};
}

View File

@ -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 {

View File

@ -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());
});
} }
*/ */

View File

@ -25,13 +25,16 @@ 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<Option<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); if let Some(list) = self.unref_list.lock().unwrap().as_mut() {
list.push(self.registry_id);
}
} }
} }
} }
@ -42,6 +45,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 +56,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);
}
} }
} }
} }

View File

@ -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() {
@ -370,7 +370,7 @@ impl<'lua> AnyUserData<'lua> {
lua.push_ref(lua.state, &self.0); lua.push_ref(lua.state, &self.0);
lua_assert!( lua_internal_assert!(
lua.state, lua.state,
ffi::lua_getmetatable(lua.state, -1) != 0, ffi::lua_getmetatable(lua.state, -1) != 0,
"AnyUserData missing metatable" "AnyUserData missing metatable"
@ -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]

View File

@ -11,7 +11,7 @@ use error::{Error, Result};
// Checks that Lua has enough free stack space for future stack operations. // Checks that Lua has enough free stack space for future stack operations.
// On failure, this will clear the stack and panic. // On failure, this will clear the stack and panic.
pub unsafe fn check_stack(state: *mut ffi::lua_State, amount: c_int) { pub unsafe fn check_stack(state: *mut ffi::lua_State, amount: c_int) {
lua_assert!( lua_internal_assert!(
state, state,
ffi::lua_checkstack(state, amount) != 0, ffi::lua_checkstack(state, amount) != 0,
"out of stack space" "out of stack space"
@ -25,7 +25,7 @@ where
F: FnOnce() -> R, F: FnOnce() -> R,
{ {
let expected = ffi::lua_gettop(state) + change; let expected = ffi::lua_gettop(state) + change;
lua_assert!( lua_internal_assert!(
state, state,
expected >= 0, expected >= 0,
"too many stack values would be popped" "too many stack values would be popped"
@ -34,7 +34,7 @@ where
let res = op(); let res = op();
let top = ffi::lua_gettop(state); let top = ffi::lua_gettop(state);
lua_assert!( lua_internal_assert!(
state, state,
ffi::lua_gettop(state) == expected, ffi::lua_gettop(state) == expected,
"expected stack to be {}, got {}", "expected stack to be {}, got {}",
@ -59,7 +59,7 @@ where
F: FnOnce() -> Result<R>, F: FnOnce() -> Result<R>,
{ {
let expected = ffi::lua_gettop(state) + change; let expected = ffi::lua_gettop(state) + change;
lua_assert!( lua_internal_assert!(
state, state,
expected >= 0, expected >= 0,
"too many stack values would be popped" "too many stack values would be popped"
@ -69,7 +69,7 @@ where
let top = ffi::lua_gettop(state); let top = ffi::lua_gettop(state);
if res.is_ok() { if res.is_ok() {
lua_assert!( lua_internal_assert!(
state, state,
ffi::lua_gettop(state) == expected, ffi::lua_gettop(state) == expected,
"expected stack to be {}, got {}", "expected stack to be {}, got {}",
@ -77,7 +77,7 @@ where
top top
); );
} else { } else {
lua_assert!( lua_internal_assert!(
state, state,
top >= expected, top >= expected,
"{} too many stack values popped", "{} too many stack values popped",
@ -171,7 +171,7 @@ where
// the current lua stack and continues the panic. If the error on the top of the stack is actually // the current lua stack and continues the panic. If the error on the top of the stack is actually
// a WrappedError, just returns it. Otherwise, interprets the error as the appropriate lua error. // a WrappedError, just returns it. Otherwise, interprets the error as the appropriate lua error.
pub unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> Error { pub unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> Error {
lua_assert!( lua_internal_assert!(
state, state,
err_code != ffi::LUA_OK && err_code != ffi::LUA_YIELD, err_code != ffi::LUA_OK && err_code != ffi::LUA_YIELD,
"pop_error called with non-error return code" "pop_error called with non-error return code"
@ -185,7 +185,7 @@ pub unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> Error {
ffi::lua_settop(state, 0); ffi::lua_settop(state, 0);
resume_unwind(p); resume_unwind(p);
} else { } else {
lua_panic!(state, "panic was resumed twice") lua_internal_panic!(state, "panic was resumed twice")
} }
} else { } else {
let err_string = gc_guard(state, || { let err_string = gc_guard(state, || {
@ -221,7 +221,7 @@ pub unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> Error {
process::abort() process::abort()
} }
ffi::LUA_ERRGCMM => Error::GarbageCollectorError(err_string), ffi::LUA_ERRGCMM => Error::GarbageCollectorError(err_string),
_ => lua_panic!(state, "unrecognized lua error code"), _ => lua_internal_panic!(state, "unrecognized lua error code"),
} }
} }
} }
@ -244,20 +244,27 @@ pub unsafe fn push_userdata<T>(state: *mut ffi::lua_State, t: T) -> Result<()> {
// Returns None in the case that the userdata has already been garbage collected. // Returns None in the case that the userdata has already been garbage collected.
pub unsafe fn get_userdata<T>(state: *mut ffi::lua_State, index: c_int) -> *mut T { pub unsafe fn get_userdata<T>(state: *mut ffi::lua_State, index: c_int) -> *mut T {
let ud = ffi::lua_touserdata(state, index) as *mut T; let ud = ffi::lua_touserdata(state, index) as *mut T;
lua_assert!(state, !ud.is_null(), "userdata pointer is null"); lua_internal_assert!(state, !ud.is_null(), "userdata pointer is null");
ud ud
} }
// Pops the userdata off of the top of the stack and returns it to rust, invalidating the lua
// userdata.
pub unsafe fn take_userdata<T>(state: *mut ffi::lua_State) -> T {
// 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())
}
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 take_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)
}) })
} }
@ -517,7 +524,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 +565,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 +595,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 +616,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 +650,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);
} }