mlua_codemp_patch/util/
userdata.rs

1use std::ffi::CStr;
2use std::os::raw::{c_int, c_void};
3use std::{ptr, str};
4
5use crate::error::Result;
6use crate::util::{check_stack, push_string, push_table, rawset_field, TypeKey};
7
8// Pushes the userdata and attaches a metatable with __gc method.
9// Internally uses 3 stack spaces, does not call checkstack.
10pub(crate) unsafe fn push_internal_userdata<T: TypeKey>(
11    state: *mut ffi::lua_State,
12    t: T,
13    protect: bool,
14) -> Result<()> {
15    push_userdata(state, t, protect)?;
16    get_internal_metatable::<T>(state);
17    ffi::lua_setmetatable(state, -2);
18    Ok(())
19}
20
21#[track_caller]
22pub(crate) unsafe fn get_internal_metatable<T: TypeKey>(state: *mut ffi::lua_State) {
23    ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, T::type_key());
24    debug_assert!(ffi::lua_isnil(state, -1) == 0, "internal metatable not found");
25}
26
27// Initialize the internal metatable for a type T (with __gc method).
28// Uses 6 stack spaces and calls checkstack.
29pub(crate) unsafe fn init_internal_metatable<T: TypeKey>(
30    state: *mut ffi::lua_State,
31    customize_fn: Option<fn(*mut ffi::lua_State) -> Result<()>>,
32) -> Result<()> {
33    check_stack(state, 6)?;
34
35    push_table(state, 0, 3, true)?;
36
37    #[cfg(not(feature = "luau"))]
38    {
39        ffi::lua_pushcfunction(state, userdata_destructor::<T>);
40        rawset_field(state, -2, "__gc")?;
41    }
42
43    ffi::lua_pushboolean(state, 0);
44    rawset_field(state, -2, "__metatable")?;
45
46    if let Some(f) = customize_fn {
47        f(state)?;
48    }
49
50    protect_lua!(state, 1, 0, |state| {
51        ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, T::type_key());
52    })?;
53
54    Ok(())
55}
56
57// Uses 2 stack spaces, does not call checkstack
58pub(crate) unsafe fn get_internal_userdata<T: TypeKey>(
59    state: *mut ffi::lua_State,
60    index: c_int,
61    type_mt_ptr: *const c_void,
62) -> *mut T {
63    let ud = ffi::lua_touserdata(state, index) as *mut T;
64    if ud.is_null() || ffi::lua_getmetatable(state, index) == 0 {
65        return ptr::null_mut();
66    }
67    if !type_mt_ptr.is_null() {
68        let ud_mt_ptr = ffi::lua_topointer(state, -1);
69        ffi::lua_pop(state, 1);
70        if ud_mt_ptr != type_mt_ptr {
71            return ptr::null_mut();
72        }
73    } else {
74        get_internal_metatable::<T>(state);
75        let res = ffi::lua_rawequal(state, -1, -2);
76        ffi::lua_pop(state, 2);
77        if res == 0 {
78            return ptr::null_mut();
79        }
80    }
81    ud
82}
83
84// Internally uses 3 stack spaces, does not call checkstack.
85#[inline]
86pub(crate) unsafe fn push_userdata<T>(state: *mut ffi::lua_State, t: T, protect: bool) -> Result<()> {
87    #[cfg(not(feature = "luau"))]
88    let ud = if protect {
89        protect_lua!(state, 0, 1, |state| {
90            ffi::lua_newuserdata(state, std::mem::size_of::<T>()) as *mut T
91        })?
92    } else {
93        ffi::lua_newuserdata(state, std::mem::size_of::<T>()) as *mut T
94    };
95    #[cfg(feature = "luau")]
96    let ud = if protect {
97        protect_lua!(state, 0, 1, |state| { ffi::lua_newuserdata_t::<T>(state) })?
98    } else {
99        ffi::lua_newuserdata_t::<T>(state)
100    };
101    ptr::write(ud, t);
102    Ok(())
103}
104
105// Internally uses 3 stack spaces, does not call checkstack.
106#[cfg(feature = "lua54")]
107#[inline]
108pub(crate) unsafe fn push_userdata_uv<T>(
109    state: *mut ffi::lua_State,
110    t: T,
111    nuvalue: c_int,
112    protect: bool,
113) -> Result<()> {
114    let ud = if protect {
115        protect_lua!(state, 0, 1, |state| {
116            ffi::lua_newuserdatauv(state, std::mem::size_of::<T>(), nuvalue) as *mut T
117        })?
118    } else {
119        ffi::lua_newuserdatauv(state, std::mem::size_of::<T>(), nuvalue) as *mut T
120    };
121    ptr::write(ud, t);
122    Ok(())
123}
124
125#[inline]
126pub(crate) unsafe fn get_userdata<T>(state: *mut ffi::lua_State, index: c_int) -> *mut T {
127    let ud = ffi::lua_touserdata(state, index) as *mut T;
128    mlua_debug_assert!(!ud.is_null(), "userdata pointer is null");
129    ud
130}
131
132// Pops the userdata off of the top of the stack and returns it to rust, invalidating the lua
133// userdata and gives it the special "destructed" userdata metatable. Userdata must not have been
134// previously invalidated, and this method does not check for this.
135// Uses 1 extra stack space and does not call checkstack.
136pub(crate) unsafe fn take_userdata<T>(state: *mut ffi::lua_State) -> T {
137    // We set the metatable of userdata on __gc to a special table with no __gc method and with
138    // metamethods that trigger an error on access. We do this so that it will not be double
139    // dropped, and also so that it cannot be used or identified as any particular userdata type
140    // after the first call to __gc.
141    get_destructed_userdata_metatable(state);
142    ffi::lua_setmetatable(state, -2);
143    let ud = get_userdata::<T>(state, -1);
144
145    // Update userdata tag to disable destructor and mark as destructed
146    #[cfg(feature = "luau")]
147    ffi::lua_setuserdatatag(state, -1, 1);
148
149    ffi::lua_pop(state, 1);
150    ptr::read(ud)
151}
152
153pub(crate) unsafe fn get_destructed_userdata_metatable(state: *mut ffi::lua_State) {
154    let key = &DESTRUCTED_USERDATA_METATABLE as *const u8 as *const c_void;
155    ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, key);
156}
157
158// Populates the given table with the appropriate members to be a userdata metatable for the given
159// type. This function takes the given table at the `metatable` index, and adds an appropriate
160// `__gc` member to it for the given type and a `__metatable` entry to protect the table from script
161// access. The function also, if given a `field_getters` or `methods` tables, will create an
162// `__index` metamethod (capturing previous one) to lookup in `field_getters` first, then `methods`
163// and falling back to the captured `__index` if no matches found.
164// The same is also applicable for `__newindex` metamethod and `field_setters` table.
165// Internally uses 9 stack spaces and does not call checkstack.
166pub(crate) unsafe fn init_userdata_metatable(
167    state: *mut ffi::lua_State,
168    metatable: c_int,
169    field_getters: Option<c_int>,
170    field_setters: Option<c_int>,
171    methods: Option<c_int>,
172    extra_init: Option<fn(*mut ffi::lua_State) -> Result<()>>,
173) -> Result<()> {
174    ffi::lua_pushvalue(state, metatable);
175
176    if field_getters.is_some() || methods.is_some() {
177        // Push `__index` generator function
178        init_userdata_metatable_index(state)?;
179
180        push_string(state, b"__index", true)?;
181        let index_type = ffi::lua_rawget(state, -3);
182        match index_type {
183            ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => {
184                for &idx in &[field_getters, methods] {
185                    if let Some(idx) = idx {
186                        ffi::lua_pushvalue(state, idx);
187                    } else {
188                        ffi::lua_pushnil(state);
189                    }
190                }
191
192                // Generate `__index`
193                protect_lua!(state, 4, 1, fn(state) ffi::lua_call(state, 3, 1))?;
194            }
195            _ => mlua_panic!("improper __index type {}", index_type),
196        }
197
198        rawset_field(state, -2, "__index")?;
199    }
200
201    if let Some(field_setters) = field_setters {
202        // Push `__newindex` generator function
203        init_userdata_metatable_newindex(state)?;
204
205        push_string(state, b"__newindex", true)?;
206        let newindex_type = ffi::lua_rawget(state, -3);
207        match newindex_type {
208            ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => {
209                ffi::lua_pushvalue(state, field_setters);
210                // Generate `__newindex`
211                protect_lua!(state, 3, 1, fn(state) ffi::lua_call(state, 2, 1))?;
212            }
213            _ => mlua_panic!("improper __newindex type {}", newindex_type),
214        }
215
216        rawset_field(state, -2, "__newindex")?;
217    }
218
219    // Additional initialization
220    if let Some(extra_init) = extra_init {
221        extra_init(state)?;
222    }
223
224    ffi::lua_pushboolean(state, 0);
225    rawset_field(state, -2, "__metatable")?;
226
227    ffi::lua_pop(state, 1);
228
229    Ok(())
230}
231
232unsafe extern "C-unwind" fn lua_error_impl(state: *mut ffi::lua_State) -> c_int {
233    ffi::lua_error(state);
234}
235
236unsafe extern "C-unwind" fn lua_isfunction_impl(state: *mut ffi::lua_State) -> c_int {
237    ffi::lua_pushboolean(state, ffi::lua_isfunction(state, -1));
238    1
239}
240
241unsafe extern "C-unwind" fn lua_istable_impl(state: *mut ffi::lua_State) -> c_int {
242    ffi::lua_pushboolean(state, ffi::lua_istable(state, -1));
243    1
244}
245
246unsafe fn init_userdata_metatable_index(state: *mut ffi::lua_State) -> Result<()> {
247    let index_key = &USERDATA_METATABLE_INDEX as *const u8 as *const _;
248    if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, index_key) == ffi::LUA_TFUNCTION {
249        return Ok(());
250    }
251    ffi::lua_pop(state, 1);
252
253    // Create and cache `__index` generator
254    let code = cstr!(
255        r#"
256            local error, isfunction, istable = ...
257            return function (__index, field_getters, methods)
258                -- Common case: has field getters and index is a table
259                if field_getters ~= nil and methods == nil and istable(__index) then
260                    return function (self, key)
261                        local field_getter = field_getters[key]
262                        if field_getter ~= nil then
263                            return field_getter(self)
264                        end
265                        return __index[key]
266                    end
267                end
268
269                return function (self, key)
270                    if field_getters ~= nil then
271                        local field_getter = field_getters[key]
272                        if field_getter ~= nil then
273                            return field_getter(self)
274                        end
275                    end
276
277                    if methods ~= nil then
278                        local method = methods[key]
279                        if method ~= nil then
280                            return method
281                        end
282                    end
283
284                    if isfunction(__index) then
285                        return __index(self, key)
286                    elseif __index == nil then
287                        error("attempt to get an unknown field '"..key.."'")
288                    else
289                        return __index[key]
290                    end
291                end
292            end
293    "#
294    );
295    let code_len = CStr::from_ptr(code).to_bytes().len();
296    protect_lua!(state, 0, 1, |state| {
297        let ret = ffi::luaL_loadbuffer(state, code, code_len, cstr!("__mlua_index"));
298        if ret != ffi::LUA_OK {
299            ffi::lua_error(state);
300        }
301        ffi::lua_pushcfunction(state, lua_error_impl);
302        ffi::lua_pushcfunction(state, lua_isfunction_impl);
303        ffi::lua_pushcfunction(state, lua_istable_impl);
304        ffi::lua_call(state, 3, 1);
305
306        #[cfg(feature = "luau-jit")]
307        if ffi::luau_codegen_supported() != 0 {
308            ffi::luau_codegen_compile(state, -1);
309        }
310
311        // Store in the registry
312        ffi::lua_pushvalue(state, -1);
313        ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, index_key);
314    })
315}
316
317unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Result<()> {
318    let newindex_key = &USERDATA_METATABLE_NEWINDEX as *const u8 as *const _;
319    if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, newindex_key) == ffi::LUA_TFUNCTION {
320        return Ok(());
321    }
322    ffi::lua_pop(state, 1);
323
324    // Create and cache `__newindex` generator
325    let code = cstr!(
326        r#"
327            local error, isfunction = ...
328            return function (__newindex, field_setters)
329                return function (self, key, value)
330                    if field_setters ~= nil then
331                        local field_setter = field_setters[key]
332                        if field_setter ~= nil then
333                            field_setter(self, value)
334                            return
335                        end
336                    end
337
338                    if isfunction(__newindex) then
339                        __newindex(self, key, value)
340                    elseif __newindex == nil then
341                        error("attempt to set an unknown field '"..key.."'")
342                    else
343                        __newindex[key] = value
344                    end
345                end
346            end
347    "#
348    );
349    let code_len = CStr::from_ptr(code).to_bytes().len();
350    protect_lua!(state, 0, 1, |state| {
351        let ret = ffi::luaL_loadbuffer(state, code, code_len, cstr!("__mlua_newindex"));
352        if ret != ffi::LUA_OK {
353            ffi::lua_error(state);
354        }
355        ffi::lua_pushcfunction(state, lua_error_impl);
356        ffi::lua_pushcfunction(state, lua_isfunction_impl);
357        ffi::lua_call(state, 2, 1);
358
359        #[cfg(feature = "luau-jit")]
360        if ffi::luau_codegen_supported() != 0 {
361            ffi::luau_codegen_compile(state, -1);
362        }
363
364        // Store in the registry
365        ffi::lua_pushvalue(state, -1);
366        ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, newindex_key);
367    })
368}
369
370#[cfg(not(feature = "luau"))]
371pub(crate) unsafe extern "C-unwind" fn userdata_destructor<T>(state: *mut ffi::lua_State) -> c_int {
372    // It's probably NOT a good idea to catch Rust panics in finalizer
373    // Lua 5.4 ignores it, other versions generates `LUA_ERRGCMM` without calling message handler
374    take_userdata::<T>(state);
375    0
376}
377
378pub(crate) static DESTRUCTED_USERDATA_METATABLE: u8 = 0;
379static USERDATA_METATABLE_INDEX: u8 = 0;
380static USERDATA_METATABLE_NEWINDEX: u8 = 0;