Userdata can have __gc metamethods called multiple times

Lua 5.3 has the ability for scripts to define __gc metamethods on
tables, which gives them the ability to "resurrect" userdata after __gc
has been called.  This means, __gc can be called multiple times on
userdata.  This commit protects against this by simply panicking on
access after resurrection.  This is possibly not the best approach?
This commit is contained in:
kyren 2017-07-23 01:00:33 -04:00
parent 396a4b0916
commit 36134e6373
3 changed files with 79 additions and 44 deletions

View file

@ -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<T>));
let res = func(&*get_userdata::<RefCell<T>>(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::<RefCell<HashMap<TypeId, c_int>>>(),
) as *mut RefCell<HashMap<TypeId, c_int>>;
ptr::write(registered_userdata, RefCell::new(HashMap::new()));
push_userdata::<RefCell<HashMap<TypeId, c_int>>>(state, RefCell::new(HashMap::new()));
ffi::lua_newtable(state);
push_string(state, "__gc");
ffi::lua_pushcfunction(state, destructor::<RefCell<HashMap<TypeId, c_int>>>);
ffi::lua_pushcfunction(state, userdata_destructor::<RefCell<HashMap<TypeId, c_int>>>);
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::<LuaCallback>);
ffi::lua_pushcfunction(state, userdata_destructor::<LuaCallback>);
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::<RefCell<T>>()) as
*mut RefCell<T>;
ptr::write(data_userdata, data);
push_userdata::<RefCell<T>>(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::<LuaCallback>(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::<LuaCallback>()) as
*mut LuaCallback;
ptr::write(func_userdata, func);
push_userdata::<LuaCallback>(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<HashMap<TypeId, c_int>>;
let registered_userdata = &mut *get_userdata::<RefCell<HashMap<TypeId, c_int>>>(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::<RefCell<T>>);
ffi::lua_pushcfunction(self.state, userdata_destructor::<RefCell<T>>);
ffi::lua_rawset(self.state, -3);
push_string(self.state, "__metatable");

View file

@ -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<Self>) {
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();
}

View file

@ -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::<WrappedPanic>(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<T>(state: *mut ffi::lua_State) -> c_int {
pub unsafe fn push_userdata<T>(state: *mut ffi::lua_State, t: T) -> *mut T {
let ud = ffi::lua_newuserdata(state, mem::size_of::<Option<T>>()) as *mut Option<T>;
ptr::write(ud, None);
*ud = Some(t);
(*ud).as_mut().unwrap()
}
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 Option<T>;
lua_assert!(state, !ud.is_null());
(*ud).as_mut().expect("access of expired userdata")
}
pub unsafe extern "C" fn userdata_destructor<T>(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<T>) = None;
0
}) {
Ok(r) => r,
@ -435,8 +446,7 @@ pub struct WrappedPanic(pub Option<Box<Any + Send>>);
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::<WrappedError>(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::<WrappedError>()) 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::<WrappedError>);
ffi::lua_pushcfunction(state, userdata_destructor::<WrappedError>);
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<Any + Send>) {
ffi::luaL_checkstack(state, 2, ptr::null());
let panic_userdata = ffi::lua_newuserdata(state, mem::size_of::<WrappedPanic>()) 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<Any + Se
ffi::lua_pushvalue(state, -2);
push_string(state, "__gc");
ffi::lua_pushcfunction(state, destructor::<WrappedPanic>);
ffi::lua_pushcfunction(state, userdata_destructor::<WrappedPanic>);
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<LuaError>
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::<WrappedError>(state, -1);
let err = err.0.clone();
ffi::lua_pop(state, 1);
Some(err)