Skip to content

Commit 0c7db49

Browse files
committed
Serialize only known (registered) userdata.
This reverts commit 7332c6a. Non-static userdata is a special case and can cause segfault if try to serialize it. Now it should be safe, plus I added non-static userdata destructor to generate better error messages in case of accessing destructed userdata.
1 parent b958949 commit 0c7db49

File tree

4 files changed

+112
-18
lines changed

4 files changed

+112
-18
lines changed

src/lua.rs

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::any::TypeId;
22
use std::cell::{RefCell, UnsafeCell};
3-
use std::collections::HashMap;
3+
use std::collections::{HashMap, HashSet};
44
use std::ffi::CString;
55
use std::marker::PhantomData;
66
use std::os::raw::{c_char, c_int, c_void};
@@ -58,6 +58,7 @@ pub struct Lua {
5858
// Data associated with the lua_State.
5959
struct ExtraData {
6060
registered_userdata: HashMap<TypeId, c_int>,
61+
registered_userdata_mt: HashSet<isize>,
6162
registry_unref_list: Arc<Mutex<Option<Vec<c_int>>>>,
6263

6364
libs: StdLib,
@@ -322,6 +323,7 @@ impl Lua {
322323

323324
let extra = Arc::new(Mutex::new(ExtraData {
324325
registered_userdata: HashMap::new(),
326+
registered_userdata_mt: HashSet::new(),
325327
registry_unref_list: Arc::new(Mutex::new(Some(Vec::new()))),
326328
ref_thread,
327329
libs: StdLib::NONE,
@@ -1569,34 +1571,52 @@ impl Lua {
15691571
ffi::lua_pop(self.state, 1);
15701572
}
15711573

1574+
let ptr = ffi::lua_topointer(self.state, -1);
15721575
let id = protect_lua_closure(self.state, 1, 0, |state| {
15731576
ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX)
15741577
})?;
15751578

15761579
let mut extra = mlua_expect!(self.extra.lock(), "extra is poisoned");
15771580
extra.registered_userdata.insert(type_id, id);
1581+
extra.registered_userdata_mt.insert(ptr as isize);
15781582

15791583
Ok(id)
15801584
}
15811585

1582-
// Pushes a LuaRef value onto the stack, checking that it's not destructed
1586+
pub(crate) fn register_userdata_metatable(&self, id: isize) {
1587+
let mut extra = mlua_expect!(self.extra.lock(), "extra is poisoned");
1588+
extra.registered_userdata_mt.insert(id);
1589+
}
1590+
1591+
pub(crate) fn deregister_userdata_metatable(&self, id: isize) {
1592+
let mut extra = mlua_expect!(self.extra.lock(), "extra is poisoned");
1593+
extra.registered_userdata_mt.remove(&id);
1594+
}
1595+
1596+
// Pushes a LuaRef value onto the stack, checking that it's a registered
1597+
// and not destructed UserData.
15831598
// Uses 2 stack spaces, does not call checkstack
15841599
#[cfg(feature = "serialize")]
15851600
pub(crate) unsafe fn push_userdata_ref(&self, lref: &LuaRef) -> Result<()> {
15861601
self.push_ref(lref);
15871602
if ffi::lua_getmetatable(self.state, -1) == 0 {
1588-
Err(Error::UserDataTypeMismatch)
1589-
} else {
1590-
// Check that userdata is not destructed
1591-
get_destructed_userdata_metatable(self.state);
1592-
let eq = ffi::lua_rawequal(self.state, -1, -2) == 1;
1603+
return Err(Error::UserDataTypeMismatch);
1604+
}
1605+
// Check that userdata is registered
1606+
let ptr = ffi::lua_topointer(self.state, -1);
1607+
let extra = mlua_expect!(self.extra.lock(), "extra is poisoned");
1608+
if extra.registered_userdata_mt.contains(&(ptr as isize)) {
1609+
ffi::lua_pop(self.state, 1);
1610+
return Ok(());
1611+
}
1612+
// Maybe userdata was destructed?
1613+
get_destructed_userdata_metatable(self.state);
1614+
if ffi::lua_rawequal(self.state, -1, -2) != 0 {
15931615
ffi::lua_pop(self.state, 2);
1594-
if eq {
1595-
Err(Error::UserDataDestructed)
1596-
} else {
1597-
Ok(())
1598-
}
1616+
return Err(Error::UserDataDestructed);
15991617
}
1618+
ffi::lua_pop(self.state, 2);
1619+
Err(Error::UserDataTypeMismatch)
16001620
}
16011621

16021622
// Creates a Function out of a Callback containing a 'static Fn. This is safe ONLY because the

src/scope.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,8 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
312312
let _sg = StackGuard::new(lua.state);
313313
assert_stack(lua.state, 6);
314314

315-
push_userdata(lua.state, ())?;
315+
// We need to wrap dummy userdata because their memory can be accessed by serializer
316+
push_userdata(lua.state, UserDataCell::new(UserDataWrapped::new(())))?;
316317
#[cfg(any(feature = "lua54", feature = "lua53"))]
317318
ffi::lua_pushlightuserdata(lua.state, data.as_ptr() as *mut c_void);
318319
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
@@ -355,9 +356,22 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
355356
ffi::lua_pop(lua.state, 1);
356357
}
357358

359+
let mt_id = ffi::lua_topointer(lua.state, -1);
358360
ffi::lua_setmetatable(lua.state, -2);
359361

360-
Ok(AnyUserData(lua.pop_ref()))
362+
let ud = AnyUserData(lua.pop_ref());
363+
lua.register_userdata_metatable(mt_id as isize);
364+
self.destructors.borrow_mut().push((ud.0.clone(), |ud| {
365+
let state = ud.lua.state;
366+
assert_stack(state, 2);
367+
ud.lua.push_ref(&ud);
368+
ffi::lua_getmetatable(state, -1);
369+
let mt_id = ffi::lua_topointer(state, -1);
370+
ffi::lua_pop(state, 1);
371+
ud.lua.deregister_userdata_metatable(mt_id as isize);
372+
vec![Box::new(take_userdata::<UserDataCell<()>>(state))]
373+
}));
374+
Ok(ud)
361375
}
362376
}
363377

tests/scope.rs

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ extern "system" {}
1313
use std::cell::Cell;
1414
use std::rc::Rc;
1515

16-
use mlua::{Error, Function, Lua, MetaMethod, Result, String, UserData, UserDataMethods};
16+
use mlua::{
17+
AnyUserData, Error, Function, Lua, MetaMethod, Result, String, UserData, UserDataMethods,
18+
};
1719

1820
#[test]
1921
fn scope_func() -> Result<()> {
@@ -57,17 +59,62 @@ fn scope_drop() -> Result<()> {
5759

5860
lua.scope(|scope| {
5961
lua.globals()
60-
.set("test", scope.create_userdata(MyUserdata(rc.clone()))?)?;
62+
.set("static_ud", scope.create_userdata(MyUserdata(rc.clone()))?)?;
6163
assert_eq!(Rc::strong_count(&rc), 2);
6264
Ok(())
6365
})?;
6466
assert_eq!(Rc::strong_count(&rc), 1);
6567

66-
match lua.load("test:method()").exec() {
67-
Err(Error::CallbackError { .. }) => {}
68+
match lua.load("static_ud:method()").exec() {
69+
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
70+
Error::CallbackDestructed => {}
71+
e => panic!("expected CallbackDestructed, got {:?}", e),
72+
},
6873
r => panic!("improper return for destructed userdata: {:?}", r),
6974
};
7075

76+
let static_ud = lua.globals().get::<_, AnyUserData>("static_ud")?;
77+
match static_ud.borrow::<MyUserdata>() {
78+
Ok(_) => panic!("borrowed destructed userdata"),
79+
Err(Error::UserDataDestructed) => {}
80+
Err(e) => panic!("expected UserDataDestructed, got {:?}", e),
81+
}
82+
83+
// Check non-static UserData drop
84+
struct MyUserDataRef<'a>(&'a Cell<i64>);
85+
86+
impl<'a> UserData for MyUserDataRef<'a> {
87+
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
88+
methods.add_method("inc", |_, data, ()| {
89+
data.0.set(data.0.get() + 1);
90+
Ok(())
91+
});
92+
}
93+
}
94+
95+
let i = Cell::new(1);
96+
lua.scope(|scope| {
97+
lua.globals().set(
98+
"nonstatic_ud",
99+
scope.create_nonstatic_userdata(MyUserDataRef(&i))?,
100+
)
101+
})?;
102+
103+
match lua.load("nonstatic_ud:inc(1)").exec() {
104+
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
105+
Error::CallbackDestructed => {}
106+
e => panic!("expected CallbackDestructed, got {:?}", e),
107+
},
108+
r => panic!("improper return for destructed userdata: {:?}", r),
109+
};
110+
111+
let nonstatic_ud = lua.globals().get::<_, AnyUserData>("nonstatic_ud")?;
112+
match nonstatic_ud.borrow::<MyUserDataRef>() {
113+
Ok(_) => panic!("borrowed destructed userdata"),
114+
Err(Error::UserDataDestructed) => {}
115+
Err(e) => panic!("expected UserDataDestructed, got {:?}", e),
116+
}
117+
71118
Ok(())
72119
}
73120

tests/serde.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,19 @@ fn test_serialize_in_scope() -> LuaResult<()> {
104104
Err(e) => panic!("expected destructed error, got {}", e),
105105
}
106106

107+
struct MyUserDataRef<'a>(&'a ());
108+
109+
impl<'a> UserData for MyUserDataRef<'a> {}
110+
111+
lua.scope(|scope| {
112+
let ud = scope.create_nonstatic_userdata(MyUserDataRef(&()))?;
113+
match serde_json::to_value(&ud) {
114+
Ok(v) => panic!("expected serialization error, got {}", v),
115+
Err(serde_json::Error { .. }) => {}
116+
};
117+
Ok(())
118+
})?;
119+
107120
Ok(())
108121
}
109122

0 commit comments

Comments
 (0)