Skip to content

Commit 77eb73a

Browse files
committed
Simplify handling of userdata __gc and resurrected userdata.
Now, simply remove the userdata table immediately before dropping the userdata. This does two things, it prevents __gc from double dropping the userdata, and after the first call to __gc, it prevents the userdata from being identified as any particular userdata type, so it cannot be misused after being finalized. This change thus removes the userdata invalidation error, and simplifies a lot of userdata handling code. It also fixes a panic bug. Because there is no predictable order for finalizers, it is possible to run a userdata finalizer that does not resurrect itself before a lua table finalizer that accesses that userdata, and this means that there were several asserts that were possible to trigger in normal Lua code in util.rs related to `WrappedError`. Now, finalized userdata is simply a userdata with no methods, so any use of finalized userdata becomes a normal script runtime error (though, with a potentially confusing error message). As a future improvement, we could set a metatable on finalized userdata that provides a better error message.
1 parent cbc882b commit 77eb73a

File tree

4 files changed

+50
-52
lines changed

4 files changed

+50
-52
lines changed

src/error.rs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,6 @@ pub enum Error {
3131
/// This is an error because `rlua` callbacks are FnMut and thus can only be mutably borrowed
3232
/// once.
3333
RecursiveCallbackError,
34-
/// Lua code has accessed a [`UserData`] value that was already garbage collected.
35-
///
36-
/// This can happen when a [`UserData`] has a custom `__gc` metamethod, this method resurrects
37-
/// the [`UserData`], and then the [`UserData`] is subsequently accessed.
38-
///
39-
/// [`UserData`]: trait.UserData.html
40-
ExpiredUserData,
4134
/// A Rust value could not be converted to a Lua value.
4235
ToLuaConversionError {
4336
/// Name of the Rust type that could not be converted.
@@ -123,10 +116,6 @@ impl fmt::Display for Error {
123116
write!(fmt, "garbage collector error: {}", msg)
124117
}
125118
Error::RecursiveCallbackError => write!(fmt, "callback called recursively"),
126-
Error::ExpiredUserData => write!(
127-
fmt,
128-
"access of userdata which has already been garbage collected"
129-
),
130119
Error::ToLuaConversionError {
131120
from,
132121
to,

src/lua.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -957,7 +957,7 @@ impl Lua {
957957
ephemeral: true,
958958
};
959959

960-
let func = get_userdata::<RefCell<Callback>>(state, ffi::lua_upvalueindex(1))?;
960+
let func = get_userdata::<RefCell<Callback>>(state, ffi::lua_upvalueindex(1));
961961
let mut func = (*func)
962962
.try_borrow_mut()
963963
.map_err(|_| Error::RecursiveCallbackError)?;

src/userdata.rs

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ impl<'lua> AnyUserData<'lua> {
386386
ffi::lua_pop(lua.state, 3);
387387
Err(Error::UserDataTypeMismatch)
388388
} else {
389-
let res = func(&*get_userdata::<RefCell<T>>(lua.state, -3)?);
389+
let res = func(&*get_userdata::<RefCell<T>>(lua.state, -3));
390390
ffi::lua_pop(lua.state, 3);
391391
res
392392
}
@@ -434,7 +434,7 @@ impl<'lua> AnyUserData<'lua> {
434434
#[cfg(test)]
435435
mod tests {
436436
use super::{MetaMethod, UserData, UserDataMethods};
437-
use error::{Error, ExternalError};
437+
use error::ExternalError;
438438
use string::String;
439439
use function::Function;
440440
use lua::Lua;
@@ -568,7 +568,7 @@ mod tests {
568568
globals.set("userdata", MyUserdata { id: 123 }).unwrap();
569569
}
570570

571-
match lua.eval::<()>(
571+
assert!(lua.eval::<()>(
572572
r#"
573573
local tbl = setmetatable({
574574
userdata = userdata
@@ -582,14 +582,8 @@ mod tests {
582582
collectgarbage("collect")
583583
hatch:access()
584584
"#,
585-
None,
586-
) {
587-
Err(Error::CallbackError { cause, .. }) => match *cause {
588-
Error::ExpiredUserData { .. } => {}
589-
ref other => panic!("incorrect result: {}", other),
590-
},
591-
other => panic!("incorrect result: {:?}", other),
592-
}
585+
None
586+
).is_err());
593587
}
594588

595589
#[test]

src/util.rs

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -104,21 +104,25 @@ pub unsafe fn protect_lua_call<F, R>(
104104
f: F,
105105
) -> Result<R>
106106
where
107-
F: FnMut(*mut ffi::lua_State) -> R,
107+
F: FnOnce(*mut ffi::lua_State) -> R,
108108
{
109109
struct Params<F, R> {
110110
function: F,
111-
ret: Option<R>,
111+
result: R,
112112
nresults: c_int,
113113
}
114114

115115
unsafe extern "C" fn do_call<F, R>(state: *mut ffi::lua_State) -> c_int
116116
where
117-
F: FnMut(*mut ffi::lua_State) -> R,
117+
F: FnOnce(*mut ffi::lua_State) -> R,
118118
{
119119
let params = ffi::lua_touserdata(state, -1) as *mut Params<F, R>;
120120
ffi::lua_pop(state, 1);
121-
(*params).ret = Some(((*params).function)(state));
121+
122+
let function = mem::replace(&mut (*params).function, mem::uninitialized());
123+
mem::replace(&mut (*params).result, function(state));
124+
// params now has function uninitialied and result initialized
125+
122126
if (*params).nresults == ffi::LUA_MULTRET {
123127
ffi::lua_gettop(state)
124128
} else {
@@ -132,20 +136,32 @@ where
132136
ffi::lua_pushcfunction(state, do_call::<F, R>);
133137
ffi::lua_rotate(state, stack_start + 1, 2);
134138

139+
// We are about to do some really scary stuff with the Params structure, both because
140+
// protect_lua_call is very hot, and becuase we would like to allow the function type to be
141+
// FnOnce rather than FnMut. We are using Params here both to pass data to the callback and
142+
// return data from the callback.
143+
//
144+
// params starts out with function initialized and result uninitialized, nresults is Copy so we
145+
// don't care about it.
135146
let mut params = Params {
136147
function: f,
137-
ret: None,
148+
result: mem::uninitialized(),
138149
nresults,
139150
};
140151

141152
ffi::lua_pushlightuserdata(state, &mut params as *mut Params<F, R> as *mut c_void);
142153

143154
let ret = ffi::lua_pcall(state, nargs + 1, nresults, stack_start + 1);
155+
let result = mem::replace(&mut params.result, mem::uninitialized());
156+
157+
// params now has both function and result uninitialized, so we need to forget it so Drop isn't
158+
// run.
159+
mem::forget(params);
144160

145161
ffi::lua_remove(state, stack_start + 1);
146162

147163
if ret == ffi::LUA_OK {
148-
Ok(params.ret.unwrap())
164+
Ok(result)
149165
} else {
150166
Err(pop_error(state, ret))
151167
}
@@ -164,8 +180,7 @@ pub unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> Error {
164180
if let Some(err) = pop_wrapped_error(state) {
165181
err
166182
} else if is_wrapped_panic(state, -1) {
167-
let panic = get_userdata::<WrappedPanic>(state, -1)
168-
.expect("WrappedPanic was somehow resurrected after garbage collection");
183+
let panic = get_userdata::<WrappedPanic>(state, -1);
169184
if let Some(p) = (*panic).0.take() {
170185
ffi::lua_settop(state, 0);
171186
resume_unwind(p);
@@ -220,26 +235,30 @@ pub unsafe fn push_string(state: *mut ffi::lua_State, s: &str) -> Result<()> {
220235

221236
// Internally uses 4 stack spaces, does not call checkstack
222237
pub unsafe fn push_userdata<T>(state: *mut ffi::lua_State, t: T) -> Result<()> {
223-
let mut t = Some(t);
224-
protect_lua_call(state, 0, 1, |state| {
225-
let ud = ffi::lua_newuserdata(state, mem::size_of::<Option<T>>()) as *mut Option<T>;
226-
ptr::write(ud, t.take());
238+
protect_lua_call(state, 0, 1, move |state| {
239+
let ud = ffi::lua_newuserdata(state, mem::size_of::<T>()) as *mut T;
240+
ptr::write(ud, t);
227241
})
228242
}
229243

230244
// Returns None in the case that the userdata has already been garbage collected.
231-
pub unsafe fn get_userdata<T>(state: *mut ffi::lua_State, index: c_int) -> Result<*mut T> {
232-
let ud = ffi::lua_touserdata(state, index) as *mut Option<T>;
245+
pub unsafe fn get_userdata<T>(state: *mut ffi::lua_State, index: c_int) -> *mut T {
246+
let ud = ffi::lua_touserdata(state, index) as *mut T;
233247
lua_assert!(state, !ud.is_null(), "userdata pointer is null");
234-
(*ud)
235-
.as_mut()
236-
.map(|v| v as *mut T)
237-
.ok_or(Error::ExpiredUserData)
248+
ud
238249
}
239250

240251
pub unsafe extern "C" fn userdata_destructor<T>(state: *mut ffi::lua_State) -> c_int {
241252
callback_error(state, || {
242-
*(ffi::lua_touserdata(state, 1) as *mut Option<T>) = None;
253+
// We clear the metatable of userdata on __gc so that it will not be double dropped, and
254+
// also so that it cannot be used or identified as any particular userdata type after the
255+
// first call to __gc.
256+
gc_guard(state, || {
257+
ffi::lua_newtable(state);
258+
ffi::lua_setmetatable(state, -2);
259+
});
260+
let ud = &mut *(ffi::lua_touserdata(state, 1) as *mut T);
261+
mem::replace(ud, mem::uninitialized());
243262
Ok(0)
244263
})
245264
}
@@ -367,9 +386,8 @@ pub unsafe fn push_wrapped_error(state: *mut ffi::lua_State, err: Error) {
367386
ffi::luaL_checkstack(state, 2, ptr::null());
368387

369388
gc_guard(state, || {
370-
let ud = ffi::lua_newuserdata(state, mem::size_of::<Option<WrappedError>>())
371-
as *mut Option<WrappedError>;
372-
ptr::write(ud, Some(WrappedError(err)))
389+
let ud = ffi::lua_newuserdata(state, mem::size_of::<WrappedError>()) as *mut WrappedError;
390+
ptr::write(ud, WrappedError(err))
373391
});
374392

375393
get_error_metatable(state);
@@ -382,8 +400,7 @@ pub unsafe fn pop_wrapped_error(state: *mut ffi::lua_State) -> Option<Error> {
382400
if ffi::lua_gettop(state) == 0 || !is_wrapped_error(state, -1) {
383401
None
384402
} else {
385-
let err = &*get_userdata::<WrappedError>(state, -1)
386-
.expect("WrappedError was somehow resurrected after garbage collection");
403+
let err = &*get_userdata::<WrappedError>(state, -1);
387404
let err = err.0.clone();
388405
ffi::lua_pop(state, 1);
389406
Some(err)
@@ -415,9 +432,8 @@ unsafe fn push_wrapped_panic(state: *mut ffi::lua_State, panic: Box<Any + Send>)
415432
ffi::luaL_checkstack(state, 2, ptr::null());
416433

417434
gc_guard(state, || {
418-
let ud = ffi::lua_newuserdata(state, mem::size_of::<Option<WrappedPanic>>())
419-
as *mut Option<WrappedPanic>;
420-
ptr::write(ud, Some(WrappedPanic(Some(panic))))
435+
let ud = ffi::lua_newuserdata(state, mem::size_of::<WrappedPanic>()) as *mut WrappedPanic;
436+
ptr::write(ud, WrappedPanic(Some(panic)))
421437
});
422438

423439
get_panic_metatable(state);
@@ -480,8 +496,7 @@ unsafe fn get_error_metatable(state: *mut ffi::lua_State) -> c_int {
480496
unsafe extern "C" fn error_tostring(state: *mut ffi::lua_State) -> c_int {
481497
callback_error(state, || {
482498
if is_wrapped_error(state, -1) {
483-
let error = get_userdata::<WrappedError>(state, -1)
484-
.expect("WrappedError was somehow resurrected after garbage collection");
499+
let error = get_userdata::<WrappedError>(state, -1);
485500
let error_str = (*error).0.to_string();
486501
gc_guard(state, || {
487502
ffi::lua_pushlstring(

0 commit comments

Comments
 (0)