Skip to content

Commit 53ff494

Browse files
committed
Prepare for custom (Luau) userdata destructors.
We need to add logic later to prevent calling any Lua functions when UserData destructor is running.
1 parent 1cb081d commit 53ff494

File tree

12 files changed

+86
-47
lines changed

12 files changed

+86
-47
lines changed

mlua-sys/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ cfg-if = "1.0"
4040
pkg-config = "0.3.17"
4141
lua-src = { version = ">= 547.0.0, < 547.1.0", optional = true }
4242
luajit-src = { version = ">= 210.5.0, < 210.6.0", optional = true }
43-
luau0-src = { version = "0.13.0", optional = true }
43+
luau0-src = { git = "https://github.com/mlua-rs/luau-src-rs", optional = true }
4444

4545
[lints.rust]
4646
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] }

mlua-sys/src/luau/compat.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ pub unsafe fn luaL_loadbufferenv(
372372
fn free(p: *mut c_void);
373373
}
374374

375-
unsafe extern "C-unwind" fn data_dtor(data: *mut c_void) {
375+
unsafe extern "C-unwind" fn data_dtor(_: *mut lua_State, data: *mut c_void) {
376376
free(*(data as *mut *mut c_char) as *mut c_void);
377377
}
378378

mlua-sys/src/luau/lua.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ pub type lua_CFunction = unsafe extern "C-unwind" fn(L: *mut lua_State) -> c_int
8484
pub type lua_Continuation = unsafe extern "C-unwind" fn(L: *mut lua_State, status: c_int) -> c_int;
8585

8686
/// Type for userdata destructor functions.
87-
pub type lua_Udestructor = unsafe extern "C-unwind" fn(*mut c_void);
8887
pub type lua_Destructor = unsafe extern "C-unwind" fn(L: *mut lua_State, *mut c_void);
8988

9089
/// Type for memory-allocation functions.
@@ -191,7 +190,7 @@ extern "C-unwind" {
191190
pub fn lua_pushlightuserdatatagged(L: *mut lua_State, p: *mut c_void, tag: c_int);
192191
pub fn lua_newuserdatatagged(L: *mut lua_State, sz: usize, tag: c_int) -> *mut c_void;
193192
pub fn lua_newuserdatataggedwithmetatable(L: *mut lua_State, sz: usize, tag: c_int) -> *mut c_void;
194-
pub fn lua_newuserdatadtor(L: *mut lua_State, sz: usize, dtor: lua_Udestructor) -> *mut c_void;
193+
pub fn lua_newuserdatadtor(L: *mut lua_State, sz: usize, dtor: lua_Destructor) -> *mut c_void;
195194

196195
pub fn lua_newbuffer(L: *mut lua_State, sz: usize) -> *mut c_void;
197196

@@ -345,12 +344,14 @@ pub unsafe fn lua_newuserdata(L: *mut lua_State, sz: usize) -> *mut c_void {
345344
}
346345

347346
#[inline(always)]
348-
pub unsafe fn lua_newuserdata_t<T>(L: *mut lua_State) -> *mut T {
349-
unsafe extern "C-unwind" fn destructor<T>(ud: *mut c_void) {
347+
pub unsafe fn lua_newuserdata_t<T>(L: *mut lua_State, data: T) -> *mut T {
348+
unsafe extern "C-unwind" fn destructor<T>(_: *mut lua_State, ud: *mut c_void) {
350349
ptr::drop_in_place(ud as *mut T);
351350
}
352351

353-
lua_newuserdatadtor(L, mem::size_of::<T>(), destructor::<T>) as *mut T
352+
let ud_ptr = lua_newuserdatadtor(L, const { mem::size_of::<T>() }, destructor::<T>) as *mut T;
353+
ptr::write(ud_ptr, data);
354+
ud_ptr
354355
}
355356

356357
// TODO: lua_strlen

src/luau/package.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,7 @@ unsafe extern "C-unwind" fn lua_require(state: *mut ffi::lua_State) -> c_int {
124124
ffi::lua_pop(state, 1); // remove nil
125125

126126
// load the module
127-
let err_buf = ffi::lua_newuserdata_t::<StdString>(state);
128-
err_buf.write(StdString::new());
127+
let err_buf = ffi::lua_newuserdata_t(state, StdString::new());
129128
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADERS")); // _LOADERS is at index 3
130129
for i in 1.. {
131130
if ffi::lua_rawgeti(state, -1, i) == ffi::LUA_TNIL {

src/scope.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
168168
#[cfg(feature = "luau")]
169169
let ud_ptr = {
170170
let data = UserDataStorage::new_scoped(data);
171-
util::push_userdata::<UserDataStorage<T>>(state, data, protect)?
171+
util::push_userdata(state, data, protect)?
172172
};
173173
#[cfg(not(feature = "luau"))]
174174
let ud_ptr = util::push_uninit_userdata::<UserDataStorage<T>>(state, protect)?;
@@ -216,7 +216,7 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
216216
#[cfg(feature = "luau")]
217217
let ud_ptr = {
218218
let data = UserDataStorage::new_scoped(data);
219-
util::push_userdata::<UserDataStorage<T>>(state, data, protect)?
219+
util::push_userdata(state, data, protect)?
220220
};
221221
#[cfg(not(feature = "luau"))]
222222
let ud_ptr = util::push_uninit_userdata::<UserDataStorage<T>>(state, protect)?;

src/userdata.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ pub use r#ref::{UserDataRef, UserDataRefMut};
3030
pub use registry::UserDataRegistry;
3131
pub(crate) use registry::{RawUserDataRegistry, UserDataProxy};
3232
pub(crate) use util::{
33-
borrow_userdata_scoped, borrow_userdata_scoped_mut, init_userdata_metatable, TypeIdHints,
33+
borrow_userdata_scoped, borrow_userdata_scoped_mut, collect_userdata, init_userdata_metatable,
34+
TypeIdHints,
3435
};
3536

3637
/// Kinds of metamethods that can be overridden.

src/userdata/registry.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ impl<T> UserDataRegistry<T> {
9797
meta_methods: Vec::new(),
9898
#[cfg(feature = "async")]
9999
async_meta_methods: Vec::new(),
100-
destructor: super::util::userdata_destructor::<T>,
100+
destructor: super::util::destroy_userdata_storage::<T>,
101101
type_id: r#type.type_id(),
102102
type_name: short_type_name::<T>(),
103103
};

src/userdata/util.rs

+24-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::any::TypeId;
22
use std::cell::Cell;
33
use std::marker::PhantomData;
44
use std::os::raw::c_int;
5+
use std::ptr;
56

67
use super::UserDataStorage;
78
use crate::error::{Error, Result};
@@ -423,7 +424,29 @@ unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Result
423424
})
424425
}
425426

426-
pub(super) unsafe extern "C-unwind" fn userdata_destructor<T>(state: *mut ffi::lua_State) -> c_int {
427+
// This method is called by Lua GC when it's time to collect the userdata.
428+
//
429+
// This method is usually used to collect internal userdata.
430+
#[cfg(not(feature = "luau"))]
431+
pub(crate) unsafe extern "C-unwind" fn collect_userdata<T>(state: *mut ffi::lua_State) -> c_int {
432+
let ud = get_userdata::<T>(state, -1);
433+
ptr::drop_in_place(ud);
434+
0
435+
}
436+
437+
// This method is called by Luau GC when it's time to collect the userdata.
438+
#[cfg(feature = "luau")]
439+
pub(crate) unsafe extern "C-unwind" fn collect_userdata<T>(
440+
_state: *mut ffi::lua_State,
441+
ud: *mut std::os::raw::c_void,
442+
) {
443+
ptr::drop_in_place(ud as *mut T);
444+
}
445+
446+
// This method can be called by user or Lua GC to destroy the userdata.
447+
// It checks if the userdata is safe to destroy and sets the "destroyed" metatable
448+
// to prevent further GC collection.
449+
pub(super) unsafe extern "C-unwind" fn destroy_userdata_storage<T>(state: *mut ffi::lua_State) -> c_int {
427450
let ud = get_userdata::<UserDataStorage<T>>(state, -1);
428451
if (*ud).is_safe_to_destroy() {
429452
take_userdata::<UserDataStorage<T>>(state);

src/util/error.rs

+4-14
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@ use std::sync::Arc;
99
use crate::error::{Error, Result};
1010
use crate::memory::MemoryState;
1111
use crate::util::{
12-
check_stack, get_internal_metatable, get_internal_userdata, init_internal_metatable,
13-
push_internal_userdata, push_string, push_table, rawset_field, to_string, TypeKey,
14-
DESTRUCTED_USERDATA_METATABLE,
12+
check_stack, get_internal_userdata, init_internal_metatable, push_internal_userdata, push_string,
13+
push_table, rawset_field, to_string, TypeKey, DESTRUCTED_USERDATA_METATABLE,
1514
};
1615

1716
static WRAPPED_FAILURE_TYPE_KEY: u8 = 0;
@@ -31,12 +30,8 @@ impl TypeKey for WrappedFailure {
3130

3231
impl WrappedFailure {
3332
pub(crate) unsafe fn new_userdata(state: *mut ffi::lua_State) -> *mut Self {
34-
#[cfg(feature = "luau")]
35-
let ud = ffi::lua_newuserdata_t::<Self>(state);
36-
#[cfg(not(feature = "luau"))]
37-
let ud = ffi::lua_newuserdata(state, std::mem::size_of::<Self>()) as *mut Self;
38-
ptr::write(ud, WrappedFailure::None);
39-
ud
33+
// Unprotected calls always return `Ok`
34+
push_internal_userdata(state, WrappedFailure::None, false).unwrap()
4035
}
4136
}
4237

@@ -90,16 +85,11 @@ where
9085
let cause = Arc::new(err);
9186
let wrapped_error = WrappedFailure::Error(Error::CallbackError { traceback, cause });
9287
ptr::write(ud, wrapped_error);
93-
get_internal_metatable::<WrappedFailure>(state);
94-
ffi::lua_setmetatable(state, -2);
95-
9688
ffi::lua_error(state)
9789
}
9890
Err(p) => {
9991
ffi::lua_settop(state, 1);
10092
ptr::write(ud, WrappedFailure::Panic(Some(p)));
101-
get_internal_metatable::<WrappedFailure>(state);
102-
ffi::lua_setmetatable(state, -2);
10393
ffi::lua_error(state)
10494
}
10595
}

src/util/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ pub(crate) use short_names::short_type_name;
1313
pub(crate) use types::TypeKey;
1414
pub(crate) use userdata::{
1515
get_destructed_userdata_metatable, get_internal_metatable, get_internal_userdata, get_userdata,
16-
init_internal_metatable, push_internal_userdata, take_userdata, DESTRUCTED_USERDATA_METATABLE,
16+
init_internal_metatable, push_internal_userdata, push_userdata, take_userdata,
17+
DESTRUCTED_USERDATA_METATABLE,
1718
};
1819

1920
#[cfg(not(feature = "luau"))]
2021
pub(crate) use userdata::push_uninit_userdata;
21-
pub(crate) use userdata::push_userdata;
2222

2323
// Checks that Lua has enough free stack space for future stack operations. On failure, this will
2424
// panic with an internal error message.

src/util/userdata.rs

+41-16
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use std::os::raw::{c_int, c_void};
2-
use std::ptr;
2+
use std::{mem, ptr};
33

44
use crate::error::Result;
5+
use crate::userdata::collect_userdata;
56
use crate::util::{check_stack, get_metatable_ptr, push_table, rawset_field, TypeKey};
67

78
// Pushes the userdata and attaches a metatable with __gc method.
@@ -10,11 +11,30 @@ pub(crate) unsafe fn push_internal_userdata<T: TypeKey>(
1011
state: *mut ffi::lua_State,
1112
t: T,
1213
protect: bool,
13-
) -> Result<()> {
14-
push_userdata(state, t, protect)?;
14+
) -> Result<*mut T> {
15+
#[cfg(not(feature = "luau"))]
16+
let ud_ptr = if protect {
17+
protect_lua!(state, 0, 1, move |state| {
18+
let ud_ptr = ffi::lua_newuserdata(state, const { mem::size_of::<T>() }) as *mut T;
19+
ptr::write(ud_ptr, t);
20+
ud_ptr
21+
})?
22+
} else {
23+
let ud_ptr = ffi::lua_newuserdata(state, const { mem::size_of::<T>() }) as *mut T;
24+
ptr::write(ud_ptr, t);
25+
ud_ptr
26+
};
27+
28+
#[cfg(feature = "luau")]
29+
let ud_ptr = if protect {
30+
protect_lua!(state, 0, 1, move |state| ffi::lua_newuserdata_t::<T>(state, t))?
31+
} else {
32+
ffi::lua_newuserdata_t::<T>(state, t)
33+
};
34+
1535
get_internal_metatable::<T>(state);
1636
ffi::lua_setmetatable(state, -2);
17-
Ok(())
37+
Ok(ud_ptr)
1838
}
1939

2040
#[track_caller]
@@ -35,12 +55,7 @@ pub(crate) unsafe fn init_internal_metatable<T: TypeKey>(
3555

3656
#[cfg(not(feature = "luau"))]
3757
{
38-
unsafe extern "C-unwind" fn userdata_destructor<T>(state: *mut ffi::lua_State) -> c_int {
39-
take_userdata::<T>(state);
40-
0
41-
}
42-
43-
ffi::lua_pushcfunction(state, userdata_destructor::<T>);
58+
ffi::lua_pushcfunction(state, collect_userdata::<T>);
4459
rawset_field(state, -2, "__gc")?;
4560
}
4661

@@ -86,24 +101,34 @@ pub(crate) unsafe fn get_internal_userdata<T: TypeKey>(
86101
pub(crate) unsafe fn push_uninit_userdata<T>(state: *mut ffi::lua_State, protect: bool) -> Result<*mut T> {
87102
if protect {
88103
protect_lua!(state, 0, 1, |state| {
89-
ffi::lua_newuserdata(state, std::mem::size_of::<T>()) as *mut T
104+
ffi::lua_newuserdata(state, const { mem::size_of::<T>() }) as *mut T
90105
})
91106
} else {
92-
Ok(ffi::lua_newuserdata(state, std::mem::size_of::<T>()) as *mut T)
107+
Ok(ffi::lua_newuserdata(state, const { mem::size_of::<T>() }) as *mut T)
93108
}
94109
}
95110

96111
// Internally uses 3 stack spaces, does not call checkstack.
97112
#[inline]
98113
pub(crate) unsafe fn push_userdata<T>(state: *mut ffi::lua_State, t: T, protect: bool) -> Result<*mut T> {
114+
let size = const { mem::size_of::<T>() };
115+
99116
#[cfg(not(feature = "luau"))]
100-
let ud_ptr = push_uninit_userdata(state, protect)?;
117+
let ud_ptr = if protect {
118+
protect_lua!(state, 0, 1, move |state| ffi::lua_newuserdata(state, size))?
119+
} else {
120+
ffi::lua_newuserdata(state, size)
121+
} as *mut T;
122+
101123
#[cfg(feature = "luau")]
102124
let ud_ptr = if protect {
103-
protect_lua!(state, 0, 1, |state| { ffi::lua_newuserdata_t::<T>(state) })?
125+
protect_lua!(state, 0, 1, |state| {
126+
ffi::lua_newuserdatadtor(state, size, collect_userdata::<T>)
127+
})?
104128
} else {
105-
ffi::lua_newuserdata_t::<T>(state)
106-
};
129+
ffi::lua_newuserdatadtor(state, size, collect_userdata::<T>)
130+
} as *mut T;
131+
107132
ptr::write(ud_ptr, t);
108133
Ok(ud_ptr)
109134
}

tests/userdata.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ fn test_gc_userdata() -> Result<()> {
264264
impl UserData for MyUserdata {
265265
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
266266
methods.add_method("access", |_, this, ()| {
267-
assert!(this.id == 123);
267+
assert_eq!(this.id, 123);
268268
Ok(())
269269
});
270270
}

0 commit comments

Comments
 (0)