Skip to main content

luau_vm/userdata/
mod.rs

1use core::mem::MaybeUninit;
2use core::ptr::{self, NonNull};
3
4use crate::Table;
5use crate::VmErrorResult;
6use crate::gc::{GcBarrier, GcObject, GcRuntime};
7use crate::handle::RawHandle;
8use crate::handle::sealed::Sealed;
9use crate::layout::Align8Byte;
10use crate::memory::{LuaPage, MemoryRuntime};
11use crate::native::NativeCallResult;
12use crate::state::{GlobalState, ThreadState};
13use crate::string::TString;
14use crate::table::RawLuaTable;
15use crate::thread::Thread;
16use crate::types::LUA_TUSERDATA;
17use crate::value::{RawTValue, TValue};
18
19mod typed;
20
21pub use typed::UserdataTypeRegistration;
22pub(crate) use typed::UserdataTypeRegistry;
23pub use typed::{
24    TypedUserdata, TypedUserdataAccess, TypedUserdataError, UserdataTypeRegistryAccess,
25};
26
27#[repr(C)]
28pub struct RawUserdata {
29    pub tt: u8,
30    pub marked: u8,
31    pub memcat: u8,
32    pub tag: u8,
33    pub len: i32,
34    pub metatable: *mut RawLuaTable,
35    pub data: [Align8Byte; 1],
36}
37
38#[derive(Clone, Copy, PartialEq, Eq)]
39#[repr(transparent)]
40pub struct Userdata {
41    raw: NonNull<RawUserdata>,
42}
43
44impl Userdata {
45    /// Constructs a non-owning handle from a VM userdata record.
46    ///
47    /// # Safety
48    ///
49    /// `raw` must address a live `RawUserdata` owned by the VM in which this
50    /// handle will be used. The handle does not root the userdata or extend
51    /// its lifetime.
52    pub const unsafe fn from_raw(raw: NonNull<RawUserdata>) -> Self {
53        Self { raw }
54    }
55
56    const fn size_userdata(payload_len: usize) -> usize {
57        core::mem::offset_of!(RawUserdata, data)
58            + if payload_len > 16 {
59                (payload_len + 15) & !15
60            } else {
61                payload_len
62            }
63    }
64
65    pub fn allocation_size_for_payload_len(payload_len: usize) -> Option<usize> {
66        let max_len = i32::MAX as usize - core::mem::size_of::<RawUserdata>();
67        (payload_len <= max_len).then(|| Self::size_userdata(payload_len))
68    }
69
70    /// Returns this userdata's metatable handle, if present.
71    ///
72    /// # Safety
73    ///
74    /// The userdata and its owning VM must be live. The returned handle is
75    /// non-owning and is invalidated when the metatable is freed.
76    pub unsafe fn metatable(&self) -> Option<Table> {
77        unsafe {
78            NonNull::new(self.as_ptr().as_ref().unwrap_unchecked().metatable)
79                .map(|raw| Table::from_raw(raw))
80        }
81    }
82
83    /// Replaces this userdata's metatable pointer.
84    ///
85    /// # Safety
86    ///
87    /// The userdata must be live, and `metatable`, when present, must belong
88    /// to the same VM. The caller must maintain the VM's GC barrier protocol.
89    pub unsafe fn set_metatable(&self, metatable: Option<Table>) {
90        unsafe {
91            self.as_ptr().as_mut().unwrap_unchecked().metatable =
92                metatable.map_or(core::ptr::null_mut(), |table| table.as_ptr())
93        };
94    }
95
96    pub fn total_payload_len(&self) -> usize {
97        unsafe { self.as_ptr().as_ref().unwrap_unchecked().len as usize }
98    }
99
100    pub const fn data_ptr(&self) -> *const u8 {
101        unsafe { (&raw const self.raw.as_ref().data).cast::<u8>() }
102    }
103
104    /// Returns the mutable address of this userdata's inline payload.
105    ///
106    /// # Safety
107    ///
108    /// The userdata must be live, and the caller must enforce bounds and
109    /// aliasing for every access through the returned pointer.
110    pub const unsafe fn data_mut_ptr(&self) -> *mut u8 {
111        unsafe { (&raw mut (*self.raw.as_ptr()).data).cast::<u8>() }
112    }
113
114    unsafe fn inline_destructor_slot(&self, requested_payload_len: usize) -> *mut u8 {
115        unsafe { self.data_mut_ptr().add(requested_payload_len).cast::<u8>() }
116    }
117
118    /// Stores the inline destructor following the requested payload.
119    ///
120    /// # Safety
121    ///
122    /// This must be an inline-destructor userdata whose allocation contains a
123    /// destructor-sized trailer immediately after `requested_payload_len`.
124    pub unsafe fn set_inline_destructor(
125        &self,
126        requested_payload_len: usize,
127        destructor: LuaInlineDestructor,
128    ) {
129        debug_assert_eq!(
130            unsafe { self.as_ptr().as_ref().unwrap_unchecked().tag as usize },
131            USERDATA_TAG_IDTOR
132        );
133
134        let inline_destructor_size = core::mem::size_of::<LuaInlineDestructor>();
135        debug_assert!(self.total_payload_len() >= inline_destructor_size);
136        debug_assert_eq!(
137            self.total_payload_len() - inline_destructor_size,
138            requested_payload_len
139        );
140
141        unsafe {
142            ptr::copy_nonoverlapping(
143                (&raw const destructor).cast::<u8>(),
144                self.inline_destructor_slot(requested_payload_len),
145                inline_destructor_size,
146            );
147        }
148    }
149
150    /// Loads the destructor stored after an inline-destructor payload.
151    ///
152    /// # Safety
153    ///
154    /// This must be a live inline-destructor userdata whose payload trailer
155    /// was initialized with a valid `LuaInlineDestructor`.
156    pub unsafe fn inline_destructor(&self) -> LuaInlineDestructor {
157        debug_assert_eq!(
158            unsafe { self.as_ptr().as_ref().unwrap_unchecked().tag as usize },
159            USERDATA_TAG_IDTOR
160        );
161
162        let mut destructor = MaybeUninit::<LuaInlineDestructor>::uninit();
163        unsafe {
164            let inline_destructor_size = core::mem::size_of::<LuaInlineDestructor>();
165            debug_assert!(self.total_payload_len() >= inline_destructor_size);
166            ptr::copy_nonoverlapping(
167                self.inline_destructor_slot(self.total_payload_len() - inline_destructor_size),
168                destructor.as_mut_ptr().cast::<u8>(),
169                inline_destructor_size,
170            );
171            destructor.assume_init()
172        }
173    }
174
175    /// Returns this userdata allocation's full GC size.
176    ///
177    /// # Safety
178    ///
179    /// The userdata record must be live and its stored payload length must
180    /// describe the allocation that contains it.
181    pub unsafe fn allocation_size(&self) -> usize {
182        Self::size_userdata(self.total_payload_len())
183    }
184}
185
186impl crate::handle::sealed::Sealed for Userdata {}
187
188impl RawHandle for Userdata {
189    type Raw = RawUserdata;
190
191    fn as_ptr(&self) -> *mut Self::Raw {
192        self.raw.as_ptr()
193    }
194}
195
196impl AsRef<Userdata> for Userdata {
197    fn as_ref(&self) -> &Userdata {
198        self
199    }
200}
201
202#[repr(C)]
203pub struct LuaUserdataDirectAccessData {
204    pub index_tm: RawTValue,
205    pub new_index_tm: RawTValue,
206    pub name_call_tm: RawTValue,
207    pub index: Option<LuaUserdataDirectAccess>,
208    pub new_index: Option<LuaUserdataDirectAccess>,
209    pub name_call: Option<LuaUserdataDirectNamecall>,
210}
211
212impl GlobalState {
213    pub fn userdata_direct_field(&self, tag: usize) -> Option<Table> {
214        unsafe {
215            Some(Table::from_raw(NonNull::new(
216                self.as_ptr()
217                    .as_ref()
218                    .unwrap_unchecked()
219                    .userdata_direct_fields[tag],
220            )?))
221        }
222    }
223
224    pub fn set_userdata_direct_field(&self, tag: usize, table: Option<Table>) {
225        unsafe {
226            self.as_ptr()
227                .as_mut()
228                .unwrap_unchecked()
229                .userdata_direct_fields[tag] =
230                table.map_or(core::ptr::null_mut(), |table| table.as_ptr());
231        }
232    }
233}
234
235/// Unstable userdata allocation and destruction capability.
236///
237/// # Safety
238///
239/// The thread, userdata, and page handles must be live and belong to the same
240/// VM. Tags, payload sizes, destructor storage, and deallocation pages must
241/// match the original allocation, with rooting and GC barriers preserved.
242#[allow(
243    clippy::missing_safety_doc,
244    reason = "all methods share the capability-level safety contract"
245)]
246pub trait UserdataRuntime: Sealed {
247    unsafe fn new_userdata_tagged_internal(&self, size: usize, tag: i32) -> VmErrorResult<*mut ()>;
248
249    /// `luaU_newudata`
250    unsafe fn new_userdata_internal(&self, size: usize, tag: i32) -> VmErrorResult<Userdata>;
251
252    /// `luaU_freeudata`
253    unsafe fn free_userdata(&self, userdata: Userdata, page: LuaPage);
254}
255
256/// `LUA_LUTAG_LIMIT`
257pub const LIGHT_USERDATA_TAG_LIMIT: usize = 128;
258
259/// `LUA_UTAG_LIMIT`
260pub const USERDATA_TAG_LIMIT: usize = 128;
261
262pub(crate) const USERDATA_TAG_IDTOR: usize = USERDATA_TAG_LIMIT;
263pub(crate) const USERDATA_TAG_PROXY: usize = USERDATA_TAG_IDTOR + 1;
264pub(crate) const USERDATA_INTERNAL_LIMIT: usize = USERDATA_TAG_PROXY + 1;
265
266pub type LuaDestructor = fn(&Thread, *mut ());
267
268pub type LuaUserdataMark = fn(&Thread, *mut ());
269
270pub type LuaInlineDestructor = fn(*mut ());
271
272pub type LuaUserdataDirectAccess = fn(&Thread, *mut (), i32, *mut u16, i32) -> VmErrorResult;
273
274pub type LuaUserdataDirectNamecall = fn(&Thread, *mut (), i32, *mut u16, i32) -> NativeCallResult;
275
276pub type LuaUserdataDirectFieldGet = fn(*mut (), &mut UserdataDirectFieldResult);
277
278impl GlobalState {
279    pub fn userdata_metatable(&self, tag: usize) -> Option<Table> {
280        unsafe {
281            Some(Table::from_raw(NonNull::new(
282                self.as_ptr().as_ref().unwrap_unchecked().userdata_mt[tag],
283            )?))
284        }
285    }
286
287    pub fn set_userdata_metatable(&self, tag: usize, table: Option<Table>) {
288        unsafe {
289            (*self.as_ptr()).userdata_mt[tag] =
290                table.map_or(core::ptr::null_mut(), |table| table.as_ptr());
291        }
292    }
293
294    pub fn userdata_dtor(&self, tag: usize) -> Option<LuaDestructor> {
295        unsafe { (*self.as_ptr()).userdata_gc[tag] }
296    }
297
298    pub fn set_userdata_dtor(&self, tag: usize, destructor: Option<LuaDestructor>) {
299        unsafe {
300            (*self.as_ptr()).userdata_gc[tag] = destructor;
301        }
302    }
303
304    pub fn userdata_mark(&self, tag: usize) -> Option<LuaUserdataMark> {
305        unsafe { (*self.as_ptr()).userdata_mark[tag] }
306    }
307
308    pub fn set_userdata_mark(&self, tag: usize, mark: Option<LuaUserdataMark>) {
309        unsafe {
310            (*self.as_ptr()).userdata_mark[tag] = mark;
311        }
312    }
313
314    pub fn light_userdata_name(&self, tag: usize) -> Option<TString> {
315        unsafe {
316            NonNull::new((*self.as_ptr()).light_userdata_name[tag])
317                .map(|raw| TString::from_raw(raw))
318        }
319    }
320
321    pub fn set_light_userdata_name(&self, tag: usize, name: Option<TString>) {
322        unsafe {
323            (*self.as_ptr()).light_userdata_name[tag] =
324                name.map_or(core::ptr::null_mut(), |name| name.as_ptr());
325        }
326    }
327}
328
329#[repr(transparent)]
330pub struct UserdataDirectFieldResult(pub(crate) TValue);
331
332impl UserdataRuntime for Thread {
333    unsafe fn new_userdata_tagged_internal(&self, size: usize, tag: i32) -> VmErrorResult<*mut ()> {
334        assert!((tag as u32) < USERDATA_INTERNAL_LIMIT as u32);
335        unsafe {
336            self.check_gc()?;
337            self.thread_barrier();
338            self.ensure_stack(self, 1)?;
339
340            let userdata = self.new_userdata_internal(size, tag)?;
341            let data = userdata.data_mut_ptr();
342            let top = self.stack_top();
343            top.value_unchecked().set_userdata_value(userdata);
344            debug_assert!(top < self.current_call_info().top());
345            self.set_stack_top(top.add(1));
346
347            Ok(data.cast())
348        }
349    }
350
351    /// `luaU_newudata`
352    unsafe fn new_userdata_internal(
353        &self,
354        payload_len: usize,
355        tag: i32,
356    ) -> VmErrorResult<Userdata> {
357        let Some(allocation_size) = Userdata::allocation_size_for_payload_len(payload_len) else {
358            return unsafe { self.too_big() };
359        };
360
361        unsafe {
362            let userdata = self.new_gco::<Userdata>(
363                allocation_size,
364                self.as_ptr().as_ref().unwrap_unchecked().active_memcat,
365            )?;
366            GcObject::from(userdata).init_header(self, LUA_TUSERDATA as u8);
367            userdata.as_ptr().as_mut().unwrap_unchecked().len = payload_len as i32;
368            userdata.set_metatable(None);
369            assert!((tag as u32) < USERDATA_INTERNAL_LIMIT as u32);
370            userdata.as_ptr().as_mut().unwrap_unchecked().tag = tag as u8;
371
372            Ok(userdata)
373        }
374    }
375
376    /// `luaU_freeudata`
377    unsafe fn free_userdata(&self, userdata: Userdata, page: LuaPage) {
378        let userdata_ref = unsafe { userdata.as_ptr().as_ref().unwrap_unchecked() };
379        let tag = userdata_ref.tag;
380        let memcat = userdata_ref.memcat;
381        let data = unsafe { userdata.data_mut_ptr() };
382
383        unsafe {
384            if tag < USERDATA_TAG_LIMIT as u8 {
385                if let Some(dtor) = self.global().userdata_dtor(tag as usize) {
386                    dtor(self, data.cast());
387                }
388            } else if tag == USERDATA_TAG_IDTOR as u8 {
389                let dtor = userdata.inline_destructor();
390                dtor(data.cast());
391            }
392
393            self.free_gco(userdata.into(), userdata.allocation_size(), memcat, page);
394        }
395    }
396}
397
398impl UserdataDirectFieldResult {
399    /// `lua_userdatadirectfield_setnumber`
400    pub fn set_number(&mut self, value: f64) {
401        self.0.set_number(value);
402    }
403
404    /// `lua_userdatadirectfield_setvector`
405    pub fn set_vector(&mut self, value: [f32; crate::types::LUA_VECTOR_SIZE]) {
406        self.0.set_vector(value);
407    }
408
409    /// `lua_userdatadirectfield_setboolean`
410    pub fn set_boolean(&mut self, value: i32) {
411        self.0.set_boolean(value);
412    }
413
414    /// `lua_userdatadirectfield_setinteger64`
415    pub fn set_integer64(&mut self, value: i64) {
416        self.0.set_integer(value);
417    }
418
419    /// `lua_userdatadirectfield_setnil`
420    pub fn set_nil(&mut self) {
421        self.0.set_nil();
422    }
423}