Fix userdata memleak in edge case.

This can happen if we unable to push metatable with `__gc` metamethod after pushing userdata.
In this case Lua will never execute drop.
Instead, we will push metatable first and then userdata.
This commit is contained in:
Alex Orlenko 2021-06-18 17:45:20 +01:00
parent bf286751fa
commit 4e92ea341b
2 changed files with 11 additions and 5 deletions

View File

@ -1977,8 +1977,11 @@ impl Lua {
let _sg = StackGuard::new(self.state);
check_stack(self.state, 2)?;
push_userdata(self.state, data)?;
// If we unable to push metatable, then we should not push userdata.
// Otherwise we can have a memory leak.
self.push_userdata_metatable::<T>()?;
push_userdata(self.state, data)?;
ffi::lua_rotate(self.state, -2, 1);
ffi::lua_setmetatable(self.state, -2);
Ok(AnyUserData(self.pop_ref()))

View File

@ -3,6 +3,7 @@ use std::cell::{Cell, RefCell};
use std::marker::PhantomData;
use std::mem;
use std::os::raw::{c_int, c_void};
use std::ptr;
use std::rc::Rc;
#[cfg(feature = "serialize")]
@ -17,7 +18,7 @@ use crate::userdata::{
AnyUserData, MetaMethod, UserData, UserDataCell, UserDataFields, UserDataMethods,
};
use crate::util::{
assert_stack, check_stack, get_userdata, init_userdata_metatable, push_table, push_userdata,
assert_stack, check_stack, get_userdata, init_userdata_metatable, protect_lua, push_table,
rawset_field, take_userdata, StackGuard,
};
use crate::value::{FromLua, FromLuaMulti, MultiValue, ToLua, ToLuaMulti, Value};
@ -321,9 +322,9 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 13)?;
push_userdata(lua.state, UserDataCell::new(data.clone()))?;
let data_ptr = ffi::lua_touserdata(lua.state, -1);
let data_ptr = protect_lua(lua.state, 0, 1, |state| {
ffi::lua_newuserdata(state, mem::size_of::<UserDataCell<Rc<RefCell<T>>>>())
})?;
// Prepare metatable, add meta methods first and then meta fields
let meta_methods_nrec = ud_methods.meta_methods.len() + ud_fields.meta_fields.len() + 1;
push_table(lua.state, 0, meta_methods_nrec as c_int)?;
@ -390,6 +391,8 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
ffi::lua_pop(lua.state, count);
let mt_id = ffi::lua_topointer(lua.state, -1);
// Write userdata just before attaching metatable with `__gc` metamethod
ptr::write(data_ptr as _, UserDataCell::new(data.clone()));
ffi::lua_setmetatable(lua.state, -2);
let ud = AnyUserData(lua.pop_ref());
lua.register_userdata_metatable(mt_id as isize);