diff --git a/src/lua.rs b/src/lua.rs index 2550059..0bc3794 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -3,7 +3,6 @@ use std::ops::{Deref, DerefMut}; use std::iter::FromIterator; use std::cell::{RefCell, Ref, RefMut}; use std::ptr; -use std::mem; use std::ffi::{CStr, CString}; use std::any::TypeId; use std::marker::PhantomData; @@ -909,9 +908,7 @@ impl<'lua> LuaUserData<'lua> { check_stack(lua.state, 3); lua.push_ref(lua.state, &self.0); - let userdata = ffi::lua_touserdata(lua.state, -1); - lua_assert!(lua.state, !userdata.is_null()); lua_assert!( lua.state, ffi::lua_getmetatable(lua.state, -1) != 0, @@ -928,7 +925,7 @@ impl<'lua> LuaUserData<'lua> { ffi::lua_pop(lua.state, 3); None } else { - let res = func(&*(userdata as *const RefCell)); + let res = func(&*get_userdata::>(lua.state, -3)); ffi::lua_pop(lua.state, 3); Some(res) } @@ -972,16 +969,12 @@ impl Lua { &LUA_USERDATA_REGISTRY_KEY as *const u8 as *mut c_void, ); - let registered_userdata = ffi::lua_newuserdata( - state, - mem::size_of::>>(), - ) as *mut RefCell>; - ptr::write(registered_userdata, RefCell::new(HashMap::new())); + push_userdata::>>(state, RefCell::new(HashMap::new())); ffi::lua_newtable(state); push_string(state, "__gc"); - ffi::lua_pushcfunction(state, destructor::>>); + ffi::lua_pushcfunction(state, userdata_destructor::>>); ffi::lua_rawset(state, -3); ffi::lua_setmetatable(state, -2); @@ -998,7 +991,7 @@ impl Lua { ffi::lua_newtable(state); push_string(state, "__gc"); - ffi::lua_pushcfunction(state, destructor::); + ffi::lua_pushcfunction(state, userdata_destructor::); ffi::lua_rawset(state, -3); push_string(state, "__metatable"); @@ -1183,11 +1176,7 @@ impl Lua { stack_guard(self.state, 0, move || { check_stack(self.state, 2); - let data = RefCell::new(data); - let data_userdata = - ffi::lua_newuserdata(self.state, mem::size_of::>()) as - *mut RefCell; - ptr::write(data_userdata, data); + push_userdata::>(self.state, RefCell::new(data)); ffi::lua_rawgeti( self.state, @@ -1322,8 +1311,7 @@ impl Lua { ephemeral: true, }; - let func = &mut *(ffi::lua_touserdata(state, ffi::lua_upvalueindex(1)) as - *mut LuaCallback); + let func = &mut *get_userdata::(state, ffi::lua_upvalueindex(1)); let nargs = ffi::lua_gettop(state); let mut args = LuaMultiValue::new(); @@ -1346,10 +1334,7 @@ impl Lua { stack_guard(self.state, 0, move || { check_stack(self.state, 2); - let func_userdata = - ffi::lua_newuserdata(self.state, mem::size_of::()) as - *mut LuaCallback; - ptr::write(func_userdata, func); + push_userdata::(self.state, func); ffi::lua_pushlightuserdata( self.state, @@ -1522,8 +1507,7 @@ impl Lua { &LUA_USERDATA_REGISTRY_KEY as *const u8 as *mut c_void, ); ffi::lua_gettable(self.state, ffi::LUA_REGISTRYINDEX); - let registered_userdata = ffi::lua_touserdata(self.state, -1) as - *mut RefCell>; + let registered_userdata = &mut *get_userdata::>>(self.state, -1); let mut map = (*registered_userdata).borrow_mut(); ffi::lua_pop(self.state, 1); @@ -1604,7 +1588,7 @@ impl Lua { } push_string(self.state, "__gc"); - ffi::lua_pushcfunction(self.state, destructor::>); + ffi::lua_pushcfunction(self.state, userdata_destructor::>); ffi::lua_rawset(self.state, -3); push_string(self.state, "__metatable"); diff --git a/src/tests.rs b/src/tests.rs index f28f905..92e21ba 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -802,3 +802,51 @@ fn test_num_conversion() { lua.exec::<()>("a = math.huge", None).unwrap(); assert!(globals.get::<_, i64>("n").is_err()); } + +#[test] +#[should_panic] +fn test_expired_userdata() { + struct Userdata { + id: u8, + } + + impl Drop for Userdata { + fn drop(&mut self) { + println!("dropping {}", self.id); + } + } + + impl LuaUserDataType for Userdata { + fn add_methods(methods: &mut LuaUserDataMethods) { + methods.add_method("access", |lua, this, _| { + println!("accessing userdata {}", this.id); + lua.pack(()) + }); + } + } + + let lua = Lua::new(); + { + let globals = lua.globals(); + globals.set("userdata", Userdata { id: 123 }).unwrap(); + } + + lua.eval::<()>(r#" + local tbl = setmetatable({ + userdata = userdata + }, { __gc = function(self) + -- resurrect userdata + hatch = self.userdata + end }) + + print("userdata = ", userdata) + print("hatch = ", hatch) + print "collecting..." + tbl = nil + userdata = nil -- make table and userdata collectable + collectgarbage("collect") + print("userdata = ", userdata) + print("hatch = ", hatch) + hatch:access() + "#, None).unwrap(); +} diff --git a/src/util.rs b/src/util.rs index c1a8008..a3f188b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -213,8 +213,7 @@ pub unsafe fn handle_error(state: *mut ffi::lua_State, err: c_int) -> LuaResult< Err(err) } else if is_wrapped_panic(state, -1) { - let userdata = ffi::lua_touserdata(state, -1); - let panic = &mut *(userdata as *mut WrappedPanic); + let panic = &mut *get_userdata::(state, -1); if let Some(p) = panic.0.take() { ffi::lua_settop(state, 0); resume_unwind(p); @@ -275,10 +274,22 @@ pub unsafe fn push_string(state: *mut ffi::lua_State, s: &str) { ffi::lua_pushlstring(state, s.as_ptr() as *const c_char, s.len()); } -pub unsafe extern "C" fn destructor(state: *mut ffi::lua_State) -> c_int { +pub unsafe fn push_userdata(state: *mut ffi::lua_State, t: T) -> *mut T { + let ud = ffi::lua_newuserdata(state, mem::size_of::>()) as *mut Option; + ptr::write(ud, None); + *ud = Some(t); + (*ud).as_mut().unwrap() +} + +pub unsafe fn get_userdata(state: *mut ffi::lua_State, index: c_int) -> *mut T { + let ud = ffi::lua_touserdata(state, index) as *mut Option; + lua_assert!(state, !ud.is_null()); + (*ud).as_mut().expect("access of expired userdata") +} + +pub unsafe extern "C" fn userdata_destructor(state: *mut ffi::lua_State) -> c_int { match catch_unwind(|| { - let obj = &mut *(ffi::lua_touserdata(state, 1) as *mut T); - mem::replace(obj, mem::uninitialized()); + *(ffi::lua_touserdata(state, 1) as *mut Option) = None; 0 }) { Ok(r) => r, @@ -435,8 +446,7 @@ pub struct WrappedPanic(pub Option>); pub unsafe fn push_wrapped_error(state: *mut ffi::lua_State, err: LuaError) { unsafe extern "C" fn error_tostring(state: *mut ffi::lua_State) -> c_int { callback_error(state, || if is_wrapped_error(state, -1) { - let userdata = ffi::lua_touserdata(state, -1); - let error = &*(userdata as *const WrappedError); + let error = &*get_userdata::(state, -1); push_string(state, &error.0.to_string()); ffi::lua_remove(state, -2); @@ -452,10 +462,7 @@ pub unsafe fn push_wrapped_error(state: *mut ffi::lua_State, err: LuaError) { ffi::luaL_checkstack(state, 2, ptr::null()); - let err_userdata = ffi::lua_newuserdata(state, mem::size_of::()) as - *mut WrappedError; - - ptr::write(err_userdata, WrappedError(err)); + push_userdata(state, WrappedError(err)); get_error_metatable(state); if ffi::lua_isnil(state, -1) != 0 { @@ -471,7 +478,7 @@ pub unsafe fn push_wrapped_error(state: *mut ffi::lua_State, err: LuaError) { ffi::lua_pushvalue(state, -2); push_string(state, "__gc"); - ffi::lua_pushcfunction(state, destructor::); + ffi::lua_pushcfunction(state, userdata_destructor::); ffi::lua_settable(state, -3); push_string(state, "__tostring"); @@ -492,10 +499,7 @@ pub unsafe fn push_wrapped_error(state: *mut ffi::lua_State, err: LuaError) { pub unsafe fn push_wrapped_panic(state: *mut ffi::lua_State, panic: Box) { ffi::luaL_checkstack(state, 2, ptr::null()); - let panic_userdata = ffi::lua_newuserdata(state, mem::size_of::()) as - *mut WrappedPanic; - - ptr::write(panic_userdata, WrappedPanic(Some(panic))); + push_userdata(state, WrappedPanic(Some(panic))); get_panic_metatable(state); if ffi::lua_isnil(state, -1) != 0 { @@ -511,7 +515,7 @@ pub unsafe fn push_wrapped_panic(state: *mut ffi::lua_State, panic: Box); + ffi::lua_pushcfunction(state, userdata_destructor::); ffi::lua_settable(state, -3); push_string(state, "__metatable"); @@ -530,8 +534,7 @@ pub unsafe fn pop_wrapped_error(state: *mut ffi::lua_State) -> Option if ffi::lua_gettop(state) == 0 || !is_wrapped_error(state, -1) { None } else { - let userdata = ffi::lua_touserdata(state, -1); - let err = &*(userdata as *const WrappedError); + let err = &*get_userdata::(state, -1); let err = err.0.clone(); ffi::lua_pop(state, 1); Some(err)