Skip to main content

luau_vm/thread/
userdata.rs

1use core::ptr;
2
3use luau_common::{ByteSlice, flags};
4
5use crate::VmErrorResult;
6use crate::gc::GcObject;
7use crate::gc::{GcBarrier, GcRuntime};
8use crate::handle::RawHandle;
9use crate::metamethod::{MetamethodRuntime, TmEvent};
10use crate::state::{EmbedderGc, ThreadState};
11use crate::string::LuaString;
12use crate::string::StringRuntime;
13use crate::table::TableRuntime;
14use crate::userdata::UserdataRuntime;
15use crate::userdata::{
16    LIGHT_USERDATA_TAG_LIMIT, LuaDestructor, LuaInlineDestructor, LuaUserdataDirectAccess,
17    LuaUserdataDirectFieldGet, LuaUserdataDirectNamecall, LuaUserdataMark, USERDATA_TAG_LIMIT,
18};
19use crate::value::{TValue, nil_object};
20
21use super::Thread;
22use super::stack::RawStackAccess;
23
24impl Thread {
25    /// `lua_newuserdatatagged`
26    pub unsafe fn new_userdata_tagged(&self, size: usize, tag: i32) -> VmErrorResult<*mut ()> {
27        assert!((tag as u32) < crate::userdata::USERDATA_TAG_LIMIT as u32);
28        unsafe { self.new_userdata_tagged_internal(size, tag) }
29    }
30
31    /// `lua_newuserdatataggedwithmetatable`
32    pub unsafe fn new_userdata_tagged_with_metatable(
33        &self,
34        size: usize,
35        tag: i32,
36    ) -> VmErrorResult<*mut ()> {
37        assert!((tag as u32) < crate::userdata::USERDATA_TAG_LIMIT as u32);
38        unsafe {
39            self.check_gc()?;
40            self.thread_barrier();
41            self.ensure_stack(self, 1)?;
42
43            let userdata = self.new_userdata_internal(size, tag)?;
44            debug_assert!(!GcObject::from(userdata).is_black());
45
46            let metatable = self.global().userdata_metatable(tag as usize);
47            userdata.set_metatable(metatable);
48
49            let data = userdata.data_mut_ptr();
50            let top = self.stack_top();
51            top.value_unchecked().set_userdata_value(userdata);
52            debug_assert!(top < self.current_call_info().top());
53            self.set_stack_top(top.add(1));
54
55            Ok(data.cast())
56        }
57    }
58
59    /// `lua_newuserdatadtor`
60    pub unsafe fn new_userdata_dtor(
61        &self,
62        size: usize,
63        destructor: LuaInlineDestructor,
64    ) -> VmErrorResult<*mut ()> {
65        let payload_len = size.saturating_add(core::mem::size_of::<LuaInlineDestructor>());
66        unsafe {
67            self.check_gc()?;
68            self.thread_barrier();
69            self.ensure_stack(self, 1)?;
70
71            let userdata = self
72                .new_userdata_internal(payload_len, crate::userdata::USERDATA_TAG_IDTOR as i32)?;
73            userdata.set_inline_destructor(size, destructor);
74
75            let data = userdata.data_mut_ptr();
76            let top = self.stack_top();
77            top.value_unchecked().set_userdata_value(userdata);
78            debug_assert!(top < self.current_call_info().top());
79            self.set_stack_top(top.add(1));
80
81            Ok(data.cast())
82        }
83    }
84
85    /// `lua_newuserdata`
86    pub unsafe fn new_userdata(&self, size: usize) -> VmErrorResult<*mut ()> {
87        unsafe { self.new_userdata_tagged_internal(size, 0) }
88    }
89
90    /// `lua_tolightuserdata`
91    pub unsafe fn to_light_userdata(&self, index: i32) -> *mut () {
92        let object = unsafe { self.index_to_addr(index) };
93        if object == nil_object() || !object.is_light_userdata() {
94            ptr::null_mut()
95        } else {
96            object.pointer_value()
97        }
98    }
99
100    /// `lua_tolightuserdatatagged`
101    pub unsafe fn to_light_userdata_tagged(&self, index: i32, tag: i32) -> *mut () {
102        let object = unsafe { self.index_to_addr(index) };
103        if object == nil_object()
104            || !object.is_light_userdata()
105            || object.light_userdata_tag() != tag
106        {
107            ptr::null_mut()
108        } else {
109            object.pointer_value()
110        }
111    }
112
113    /// `lua_touserdata`
114    pub unsafe fn to_userdata(&self, index: i32) -> *mut () {
115        let object = unsafe { self.index_to_addr(index) };
116        if object == nil_object() {
117            return ptr::null_mut();
118        }
119
120        if object.is_userdata() {
121            let userdata = object.userdata_value();
122            unsafe { userdata.data_mut_ptr().cast() }
123        } else if object.is_light_userdata() {
124            object.pointer_value()
125        } else {
126            ptr::null_mut()
127        }
128    }
129
130    /// `lua_touserdatatagged`
131    pub unsafe fn to_userdata_tagged(&self, index: i32, tag: i32) -> *mut () {
132        let object = unsafe { self.index_to_addr(index) };
133        if object == nil_object() || !object.is_userdata() {
134            return ptr::null_mut();
135        }
136
137        let userdata = object.userdata_value();
138        if unsafe { userdata.as_ptr().as_ref().unwrap_unchecked().tag as i32 } == tag {
139            unsafe { userdata.data_mut_ptr().cast() }
140        } else {
141            ptr::null_mut()
142        }
143    }
144
145    /// `lua_userdatatag`
146    pub unsafe fn userdata_tag(&self, index: i32) -> i32 {
147        let object = unsafe { self.index_to_addr(index) };
148        if object == nil_object() || !object.is_userdata() {
149            -1
150        } else {
151            unsafe {
152                object
153                    .userdata_value()
154                    .as_ptr()
155                    .as_ref()
156                    .unwrap_unchecked()
157                    .tag as i32
158            }
159        }
160    }
161
162    /// `lua_lightuserdatatag`
163    pub unsafe fn light_userdata_tag(&self, index: i32) -> i32 {
164        let object = unsafe { self.index_to_addr(index) };
165        if object == nil_object() || !object.is_light_userdata() {
166            -1
167        } else {
168            object.light_userdata_tag()
169        }
170    }
171
172    /// `lua_setuserdatatag`
173    pub unsafe fn set_userdata_tag(&self, index: i32, tag: i32) {
174        debug_assert!((tag as u32) < USERDATA_TAG_LIMIT as u32);
175
176        let object = unsafe { self.to_object(index).unwrap_unchecked() };
177        debug_assert!(object.is_userdata());
178
179        unsafe {
180            object
181                .userdata_value()
182                .as_ptr()
183                .as_mut()
184                .unwrap_unchecked()
185                .tag = tag as u8
186        };
187    }
188
189    /// `lua_setuserdatadtor`
190    pub unsafe fn set_userdata_dtor(&self, tag: i32, destructor: LuaDestructor) {
191        debug_assert!((tag as u32) < USERDATA_TAG_LIMIT as u32);
192
193        unsafe {
194            self.global()
195                .set_userdata_dtor(tag as usize, Some(destructor))
196        };
197    }
198
199    /// `lua_getuserdatadtor`
200    pub unsafe fn get_userdata_dtor(&self, tag: i32) -> Option<LuaDestructor> {
201        debug_assert!((tag as u32) < USERDATA_TAG_LIMIT as u32);
202
203        unsafe { self.global().userdata_dtor(tag as usize) }
204    }
205
206    /// `lua_setuserdatamark`
207    ///
208    /// The callback runs during garbage collection and must not perform
209    /// reentrant VM operations. Read-only host state access is permitted.
210    pub unsafe fn set_userdata_mark(&self, tag: i32, mark: Option<LuaUserdataMark>) {
211        debug_assert!(flags::LuauGcTraceUdata.get());
212        debug_assert!((tag as u32) < USERDATA_TAG_LIMIT as u32);
213
214        unsafe { self.global().set_userdata_mark(tag as usize, mark) };
215    }
216
217    /// `lua_setembeddergc`
218    ///
219    /// The callback runs during garbage collection and must not perform
220    /// reentrant VM operations. When `mark` is present, the callback may use
221    /// it to keep embedder-owned weak references alive for this cycle.
222    pub unsafe fn set_embedder_gc(&self, callback: Option<EmbedderGc>) {
223        debug_assert!(flags::LuauGcTraceUdata.get());
224        unsafe { self.global().set_embedder_gc(callback) };
225    }
226
227    /// `lua_setuserdatametatable`
228    pub unsafe fn set_userdata_metatable(&self, tag: i32) {
229        debug_assert!((tag as u32) < USERDATA_TAG_LIMIT as u32);
230        unsafe {
231            let top_cursor = self.stack_top();
232            debug_assert!(top_cursor.offset_from(self.stack_base()) > 0);
233            debug_assert!(self.global().userdata_metatable(tag as usize).is_none());
234            let top = top_cursor.sub(1).value_unchecked();
235            debug_assert!(top.is_table());
236
237            self.global()
238                .set_userdata_metatable(tag as usize, Some(top.table_value()));
239            self.set_stack_top(top_cursor.sub(1));
240        }
241    }
242
243    /// `lua_getuserdatametatable`
244    pub unsafe fn get_userdata_metatable(&self, tag: i32) -> VmErrorResult {
245        unsafe {
246            debug_assert!((tag as u32) < USERDATA_TAG_LIMIT as u32);
247
248            self.thread_barrier();
249            self.ensure_stack(self, 1)?;
250
251            let top = self.stack_top();
252
253            if let Some(metatable) = self.global().userdata_metatable(tag as usize) {
254                top.value_unchecked().set_table_value(metatable);
255            } else {
256                top.value_unchecked().set_nil();
257            }
258
259            debug_assert!(top < self.current_call_info().top());
260            self.set_stack_top(top.add(1));
261        }
262        Ok(())
263    }
264
265    /// `lua_getuserdataname`
266    pub unsafe fn get_userdata_name(&self, tag: i32) -> LuaString {
267        debug_assert!((tag as u32) < USERDATA_TAG_LIMIT as u32);
268
269        unsafe {
270            let global = self.global();
271            if let Some(metatable) = global.userdata_metatable(tag as usize) {
272                let value = metatable.get_str(global.tm_name(TmEvent::Type as usize));
273                if value.is_string() {
274                    return LuaString::from_interned(value.string_value());
275                }
276            }
277
278            LuaString::from_interned(global.type_name(crate::types::LUA_TUSERDATA as usize))
279        }
280    }
281
282    /// `lua_getlightuserdataname`
283    pub unsafe fn get_light_userdata_name(&self, tag: i32) -> Option<LuaString> {
284        debug_assert!((tag as u32) < LIGHT_USERDATA_TAG_LIMIT as u32);
285
286        unsafe {
287            self.global()
288                .light_userdata_name(tag as usize)
289                .map(LuaString::from_interned)
290        }
291    }
292
293    /// `lua_registeruserdatadirectaccess`
294    pub unsafe fn register_userdata_direct_access(
295        &self,
296        tag: i32,
297        get: LuaUserdataDirectAccess,
298        set: LuaUserdataDirectAccess,
299        namecall: Option<LuaUserdataDirectNamecall>,
300    ) -> i32 {
301        debug_assert!((tag as u32) < USERDATA_TAG_LIMIT as u32);
302
303        unsafe {
304            self.thread_barrier();
305
306            let global_handle = self.global();
307            let Some(table) = global_handle.userdata_metatable(tag as usize) else {
308                return 0;
309            };
310
311            let global = global_handle.as_ptr().as_mut().unwrap_unchecked();
312
313            let data = &mut global.userdata_direct[tag as usize];
314            TValue::from_mut(&mut data.index_tm).set_nil();
315            TValue::from_mut(&mut data.new_index_tm).set_nil();
316            TValue::from_mut(&mut data.name_call_tm).set_nil();
317            data.index = None;
318            data.new_index = None;
319            data.name_call = None;
320
321            if let Some(index_tm) = self.fast_tm(Some(table), TmEvent::Index) {
322                TValue::from_mut(&mut data.index_tm).set_obj(index_tm);
323                data.index = Some(get);
324            }
325
326            if let Some(new_index_tm) = self.fast_tm(Some(table), TmEvent::NewIndex) {
327                TValue::from_mut(&mut data.new_index_tm).set_obj(new_index_tm);
328                data.new_index = Some(set);
329            }
330
331            if let Some(name_call_tm) = self.fast_tm(Some(table), TmEvent::NameCall) {
332                TValue::from_mut(&mut data.name_call_tm).set_obj(name_call_tm);
333                data.name_call = namecall;
334            }
335
336            1
337        }
338    }
339
340    /// `lua_registeruserdatadirectfieldget`
341    pub unsafe fn register_userdata_direct_field_get(
342        &self,
343        tag: i32,
344        field: &[u8],
345        get: LuaUserdataDirectFieldGet,
346    ) -> VmErrorResult {
347        unsafe {
348            if !flags::LuauDirectFieldGet.get() {
349                return Ok(());
350            }
351
352            debug_assert!((tag as u32) < USERDATA_TAG_LIMIT as u32);
353
354            let global = self.global();
355            if global.userdata_direct_field(tag as usize).is_none() {
356                let table = self.new_table_internal(0, 1)?;
357                global.set_userdata_direct_field(tag as usize, Some(table));
358            }
359
360            let string = self.intern_string(field.as_bstr())?;
361            string.fix();
362
363            let table = global
364                .userdata_direct_field(tag as usize)
365                .unwrap_unchecked();
366            let node_cursor = self.set_str(table, string)?;
367            node_cursor
368                .node_unchecked()
369                .value_unchecked()
370                .set_light_userdata(get as *const () as *mut (), 0);
371        }
372        Ok(())
373    }
374
375    /// `lua_setlightuserdataname`
376    pub unsafe fn set_light_userdata_name(&self, tag: i32, name: &str) -> VmErrorResult {
377        unsafe {
378            debug_assert!((tag as u32) < LIGHT_USERDATA_TAG_LIMIT as u32);
379            debug_assert!(self.global().light_userdata_name(tag as usize).is_none());
380
381            let global = self.global();
382            if global.light_userdata_name(tag as usize).is_none() {
383                let string = self.intern_string(name.as_bytes().as_bstr())?;
384                string.fix();
385                global.set_light_userdata_name(tag as usize, Some(string));
386            }
387        }
388        Ok(())
389    }
390}