Skip to content

Commit ace5cb4

Browse files
committed
Letting scope handles escape the scope was unsafe
This simplifies the Scope lifetimes, and should make it a compile error for scope created handles to exit the scope. This should be strictly better, as you would never WANT to do this, but I hope that I have not caused a subtle lifetime problem that would prevent passing those created handles back into Lua. I've tested every situation I can think of, and it doesn't appear to be an issue, but I admit that I don't fully understand everything involved and I could be missing something. The reason that I needed to do this is that if you can let a scope handle escape the scope, you have a LuaRef with an unused registry id, and that can lead to UB. Since not letting the scope references escape is a strict improvement ANYWAY (if I haven't caused a lifetime issue), this is the easiest fix. This is technically a breaking change but I think in most cases if you notice it you would be invoking UB, or you had a function that accepted a Scope or something. I don't know if it's worth a version bump?
1 parent 0450c9b commit ace5cb4

File tree

2 files changed

+61
-36
lines changed

2 files changed

+61
-36
lines changed

src/lua.rs

Lines changed: 44 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ pub struct Lua {
3333
/// See [`Lua::scope`] for more details.
3434
///
3535
/// [`Lua::scope`]: struct.Lua.html#method.scope
36-
pub struct Scope<'lua, 'scope> {
37-
lua: &'lua Lua,
36+
pub struct Scope<'scope> {
37+
lua: &'scope Lua,
3838
destructors: RefCell<Vec<Box<Fn(*mut ffi::lua_State) -> Box<Any>>>>,
3939
// 'scope lifetime must be invariant
4040
_scope: PhantomData<&'scope mut &'scope ()>,
@@ -347,15 +347,15 @@ impl Lua {
347347
/// thread while `Scope` is live, it is safe to allow !Send datatypes and functions whose
348348
/// lifetimes only outlive the scope lifetime.
349349
///
350-
/// To make the lifetimes work out, handles that `Lua::scope` produces have the `'lua` lifetime
351-
/// of the parent `Lua` instance. This allows the handles to scoped values to escape the
352-
/// callback, but this was possible anyway by going through Lua itself. This is safe to do, but
353-
/// not useful, because after the scope is dropped, all references to scoped values, whether in
354-
/// Lua or in rust, are invalidated. `Function` types will error when called, and `AnyUserData`
355-
/// types will be typeless.
356-
pub fn scope<'lua, 'scope, F, R>(&'lua self, f: F) -> R
350+
/// Handles that `Lua::scope` produces have a `'lua` lifetime of the scope parameter, to prevent
351+
/// the handles from escaping the callback. However, this is not the only way for values to
352+
/// escape the callback, as they can be smuggled through Lua itself. This is safe to do, but
353+
/// not very useful, because after the scope is dropped, all references to scoped values,
354+
/// whether in Lua or in rust, are invalidated. `Function` types will error when called, and
355+
/// `AnyUserData` types will be typeless.
356+
pub fn scope<'scope, 'lua: 'scope, F, R>(&'lua self, f: F) -> R
357357
where
358-
F: FnOnce(&Scope<'lua, 'scope>) -> R,
358+
F: FnOnce(&Scope<'scope>) -> R,
359359
{
360360
let scope = Scope {
361361
lua: self,
@@ -1082,15 +1082,15 @@ impl Lua {
10821082
}
10831083
}
10841084

1085-
impl<'lua, 'scope> Scope<'lua, 'scope> {
1085+
impl<'scope> Scope<'scope> {
10861086
/// Wraps a Rust function or closure, creating a callable Lua function handle to it.
10871087
///
10881088
/// This is a version of [`Lua::create_function`] that creates a callback which expires on scope
10891089
/// drop. See [`Lua::scope`] for more details.
10901090
///
10911091
/// [`Lua::create_function`]: struct.Lua.html#method.create_function
10921092
/// [`Lua::scope`]: struct.Lua.html#method.scope
1093-
pub fn create_function<'callback, A, R, F>(&self, func: F) -> Result<Function<'lua>>
1093+
pub fn create_function<'callback, 'lua, A, R, F>(&'lua self, func: F) -> Result<Function<'lua>>
10941094
where
10951095
A: FromLuaMulti<'callback>,
10961096
R: ToLuaMulti<'callback>,
@@ -1107,22 +1107,25 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
11071107
let mut destructors = self.destructors.borrow_mut();
11081108
let registry_id = f.0.registry_id;
11091109
destructors.push(Box::new(move |state| {
1110-
check_stack(state, 2);
1111-
ffi::lua_rawgeti(
1112-
state,
1113-
ffi::LUA_REGISTRYINDEX,
1114-
registry_id as ffi::lua_Integer,
1115-
);
1116-
ffi::luaL_unref(state, ffi::LUA_REGISTRYINDEX, registry_id);
1110+
stack_guard(state, 0, || {
1111+
check_stack(state, 2);
11171112

1118-
ffi::lua_getupvalue(state, -1, 1);
1119-
let ud = take_userdata::<Callback>(state);
1113+
ffi::lua_rawgeti(
1114+
state,
1115+
ffi::LUA_REGISTRYINDEX,
1116+
registry_id as ffi::lua_Integer,
1117+
);
1118+
ffi::luaL_unref(state, ffi::LUA_REGISTRYINDEX, registry_id);
11201119

1121-
ffi::lua_pushnil(state);
1122-
ffi::lua_setupvalue(state, -2, 1);
1120+
ffi::lua_getupvalue(state, -1, 1);
1121+
let ud = take_userdata::<Callback>(state);
11231122

1124-
ffi::lua_pop(state, 1);
1125-
Box::new(ud)
1123+
ffi::lua_pushnil(state);
1124+
ffi::lua_setupvalue(state, -2, 1);
1125+
1126+
ffi::lua_pop(state, 1);
1127+
Box::new(ud)
1128+
})
11261129
}));
11271130
Ok(f)
11281131
}
@@ -1135,7 +1138,10 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
11351138
///
11361139
/// [`Lua::create_function_mut`]: struct.Lua.html#method.create_function_mut
11371140
/// [`Lua::scope`]: struct.Lua.html#method.scope
1138-
pub fn create_function_mut<'callback, A, R, F>(&self, func: F) -> Result<Function<'lua>>
1141+
pub fn create_function_mut<'callback, 'lua, A, R, F>(
1142+
&'lua self,
1143+
func: F,
1144+
) -> Result<Function<'lua>>
11391145
where
11401146
A: FromLuaMulti<'callback>,
11411147
R: ToLuaMulti<'callback>,
@@ -1155,7 +1161,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
11551161
///
11561162
/// [`Lua::create_userdata`]: struct.Lua.html#method.create_userdata
11571163
/// [`Lua::scope`]: struct.Lua.html#method.scope
1158-
pub fn create_userdata<T>(&self, data: T) -> Result<AnyUserData<'lua>>
1164+
pub fn create_userdata<'lua, T>(&'lua self, data: T) -> Result<AnyUserData<'lua>>
11591165
where
11601166
T: UserData,
11611167
{
@@ -1165,21 +1171,23 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
11651171
let mut destructors = self.destructors.borrow_mut();
11661172
let registry_id = u.0.registry_id;
11671173
destructors.push(Box::new(move |state| {
1168-
check_stack(state, 1);
1169-
ffi::lua_rawgeti(
1170-
state,
1171-
ffi::LUA_REGISTRYINDEX,
1172-
registry_id as ffi::lua_Integer,
1173-
);
1174-
ffi::luaL_unref(state, ffi::LUA_REGISTRYINDEX, registry_id);
1175-
Box::new(take_userdata::<RefCell<T>>(state))
1174+
stack_guard(state, 0, || {
1175+
check_stack(state, 1);
1176+
ffi::lua_rawgeti(
1177+
state,
1178+
ffi::LUA_REGISTRYINDEX,
1179+
registry_id as ffi::lua_Integer,
1180+
);
1181+
ffi::luaL_unref(state, ffi::LUA_REGISTRYINDEX, registry_id);
1182+
Box::new(take_userdata::<RefCell<T>>(state))
1183+
})
11761184
}));
11771185
Ok(u)
11781186
}
11791187
}
11801188
}
11811189

1182-
impl<'lua, 'scope> Drop for Scope<'lua, 'scope> {
1190+
impl<'scope> Drop for Scope<'scope> {
11831191
fn drop(&mut self) {
11841192
// We separate the action of invalidating the userdata in Lua and actually dropping the
11851193
// userdata type into two phases. This is so that, in the event a userdata drop panics, we

tests/compile-fail/scope_escape.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
extern crate rlua;
2+
3+
use rlua::*;
4+
5+
fn main() {
6+
struct Test {
7+
field: i32,
8+
}
9+
10+
let lua = Lua::new();
11+
let mut outer: Option<Function> = None;
12+
lua.scope(|scope| {
13+
let f = scope.create_function_mut(|_, ()| Ok(())).unwrap();
14+
outer = Some(scope.create_function_mut(|_, ()| Ok(())).unwrap());
15+
//~^ error: borrowed data cannot be stored outside of its closure
16+
});
17+
}

0 commit comments

Comments
 (0)