Skip to main content

luau_vm/userdata/
typed.rs

1use core::any::TypeId;
2use core::cell::RefCell;
3use core::ptr::{self, NonNull};
4use core::slice;
5
6use crate::Table;
7use crate::VmErrorResult;
8use crate::gc::{GcBarrier, GcObject};
9use crate::handle::RawHandle;
10use crate::handle::sealed::Sealed;
11use crate::memory::MemoryRuntime;
12use crate::native::{NativeCallContext, NativeCallResult};
13use crate::state::GlobalState;
14use crate::table::RawLuaTable;
15use crate::thread::stack::RawStackAccess;
16use crate::thread::{StackGuard, Thread};
17use crate::types::{LUA_TTABLE, LUA_TUSERDATA};
18use crate::value::{RAW_TVALUE_NIL, RawTValue, TValue};
19
20use super::{LuaInlineDestructor, USERDATA_TAG_IDTOR, Userdata};
21
22const USERDATA_REGISTRY_MEMCAT: u8 = 0;
23
24#[derive(Clone, Copy)]
25pub struct UserdataTypeRegistration {
26    type_id: TypeId,
27    metatable: Table,
28}
29
30#[repr(C)]
31struct UserdataTypeEntry {
32    type_id: TypeId,
33    metatable: *mut RawLuaTable,
34    destroy: unsafe fn(&Thread, Userdata, Table) -> Result<(), TypedUserdataError>,
35    // Only the current registration is a registry root. Older metatables are
36    // recognized while existing userdata keep them alive.
37    current: bool,
38}
39
40#[repr(C)]
41pub(crate) struct UserdataTypeRegistry {
42    entries: *mut UserdataTypeEntry,
43    len: usize,
44    cap: usize,
45    destructed_metatable: *mut RawLuaTable,
46}
47
48impl UserdataTypeEntry {
49    #[inline]
50    unsafe fn registration(&self) -> UserdataTypeRegistration {
51        unsafe {
52            UserdataTypeRegistration {
53                type_id: self.type_id,
54                metatable: Table::from_raw(NonNull::new_unchecked(self.metatable)),
55            }
56        }
57    }
58}
59
60impl UserdataTypeRegistry {
61    #[inline]
62    pub(crate) const fn new() -> Self {
63        Self {
64            entries: ptr::null_mut(),
65            len: 0,
66            cap: 0,
67            destructed_metatable: ptr::null_mut(),
68        }
69    }
70
71    #[inline]
72    pub(crate) fn get<T: 'static>(&self) -> Option<UserdataTypeRegistration> {
73        self.entries()
74            .iter()
75            .find(|entry| entry.current && entry.type_id == TypeId::of::<T>())
76            .map(|entry| unsafe { entry.registration() })
77    }
78
79    fn typed_userdata(&self, userdata: Userdata) -> Option<TypedUserdata> {
80        unsafe {
81            if userdata.as_ptr().as_ref().unwrap_unchecked().tag as usize != USERDATA_TAG_IDTOR {
82                return None;
83            }
84
85            let metatable = userdata.metatable()?;
86            if self.destructed_metatable == metatable.as_ptr() {
87                return Some(TypedUserdata {
88                    userdata,
89                    type_id: None,
90                });
91            }
92            let type_id = self
93                .entries()
94                .iter()
95                .find(|entry| entry.metatable == metatable.as_ptr())?
96                .type_id;
97            Some(TypedUserdata {
98                userdata,
99                type_id: Some(type_id),
100            })
101        }
102    }
103
104    pub(crate) unsafe fn register<T: 'static>(
105        registry: *mut Self,
106        thread: &Thread,
107        metatable: Table,
108    ) -> VmErrorResult<UserdataTypeRegistration> {
109        unsafe {
110            if (*registry).len == (*registry).cap {
111                Self::grow(registry, thread)?;
112            }
113
114            for entry in (*registry).entries_mut() {
115                if entry.type_id == TypeId::of::<T>() {
116                    entry.current = false;
117                }
118            }
119
120            let entry = UserdataTypeEntry {
121                type_id: TypeId::of::<T>(),
122                metatable: metatable.as_ptr(),
123                destroy: destroy_typed_userdata_payload::<T>,
124                current: true,
125            };
126            let entry_slot = (*registry).entries.add((*registry).len);
127            entry_slot.write(entry);
128            (*registry).len += 1;
129            Ok((*entry_slot).registration())
130        }
131    }
132
133    pub(crate) fn retire<T: 'static>(&mut self) {
134        let type_id = TypeId::of::<T>();
135        for entry in self.entries_mut() {
136            if entry.type_id == type_id {
137                entry.current = false;
138            }
139        }
140    }
141
142    #[inline]
143    pub(crate) fn rooted_metatables(&self) -> impl Iterator<Item = Table> + '_ {
144        self.entries()
145            .iter()
146            .filter(|entry| entry.current)
147            .filter_map(|entry| unsafe {
148                NonNull::new(entry.metatable).map(|raw| Table::from_raw(raw))
149            })
150            .chain(
151                NonNull::new(self.destructed_metatable).map(|raw| unsafe { Table::from_raw(raw) }),
152            )
153    }
154
155    #[inline]
156    pub(crate) fn recognized_metatables(&self) -> impl Iterator<Item = Table> + '_ {
157        self.entries().iter().filter_map(|entry| unsafe {
158            NonNull::new(entry.metatable).map(|raw| Table::from_raw(raw))
159        })
160    }
161
162    pub(crate) unsafe fn remove_unreachable_metatables(&mut self) {
163        let mut destination = 0;
164
165        for source in 0..self.len {
166            unsafe {
167                let entry = self.entries.add(source);
168                let metatable = Table::from_raw(NonNull::new_unchecked((*entry).metatable));
169                // Atomic marking has reached every live userdata and therefore
170                // every historical metatable that still identifies one.
171                if !(*entry).current && GcObject::from(metatable).is_white() {
172                    ptr::drop_in_place(entry);
173                    continue;
174                }
175
176                if destination != source {
177                    self.entries.add(destination).write(entry.read());
178                }
179                destination += 1;
180            }
181        }
182
183        self.len = destination;
184    }
185
186    unsafe fn ensure_destructed_metatable(registry: *mut Self, thread: &Thread) -> VmErrorResult {
187        if unsafe { !(*registry).destructed_metatable.is_null() } {
188            return Ok(());
189        }
190
191        unsafe {
192            let _stack = StackGuard::new(thread);
193            thread.create_table(0, 24)?;
194            let metatable = thread.to_object(-1).unwrap_unchecked().table_value();
195
196            thread.push_native_function(destructed_userdata_error, Some("destructed userdata"))?;
197            for name in [
198                "__add",
199                "__sub",
200                "__mul",
201                "__div",
202                "__idiv",
203                "__mod",
204                "__pow",
205                "__unm",
206                "__eq",
207                "__lt",
208                "__le",
209                "__len",
210                "__concat",
211                "__index",
212                "__newindex",
213                "__call",
214                "__tostring",
215                "__todebugstring",
216                "__namecall",
217                "__iter",
218            ] {
219                thread.push_value(-1)?;
220                thread.raw_set_field(-3, name)?;
221            }
222            thread.pop(1);
223            thread.push_boolean(0)?;
224            thread.raw_set_field(-2, "__metatable")?;
225            thread.set_readonly(-1, 1);
226            (*registry).destructed_metatable = metatable.as_ptr();
227            Ok(())
228        }
229    }
230
231    fn destroy(&self, thread: &Thread, userdata: TypedUserdata) -> Result<(), TypedUserdataError> {
232        let type_id = userdata.type_id.ok_or(TypedUserdataError::Destructed)?;
233        let entry = self
234            .entries()
235            .iter()
236            .find(|entry| entry.type_id == type_id)
237            .ok_or(TypedUserdataError::TypeMismatch)?;
238        let metatable =
239            unsafe { Table::from_raw(NonNull::new_unchecked(self.destructed_metatable)) };
240        unsafe { (entry.destroy)(thread, userdata.userdata, metatable) }
241    }
242
243    pub(crate) unsafe fn free(registry: *mut Self, thread: &Thread) {
244        let entries = unsafe { (*registry).entries };
245        let len = unsafe { (*registry).len };
246        let cap = unsafe { (*registry).cap };
247        unsafe { *registry = Self::new() };
248        if entries.is_null() {
249            return;
250        }
251
252        unsafe {
253            for entry in slice::from_raw_parts_mut(entries, len) {
254                ptr::drop_in_place(entry);
255            }
256            thread.free_array(entries, cap, USERDATA_REGISTRY_MEMCAT);
257        }
258    }
259
260    unsafe fn grow(registry: *mut Self, thread: &Thread) -> VmErrorResult {
261        let cap = unsafe { (*registry).cap };
262        let new_cap = if cap == 0 {
263            4
264        } else {
265            let Some(new_cap) = cap.checked_mul(2) else {
266                return unsafe { thread.too_big() };
267            };
268            new_cap
269        };
270
271        unsafe {
272            let entries = thread.realloc_array(
273                (*registry).entries,
274                cap,
275                new_cap,
276                USERDATA_REGISTRY_MEMCAT,
277            )?;
278            (*registry).entries = entries;
279            (*registry).cap = new_cap;
280        }
281
282        Ok(())
283    }
284
285    #[inline]
286    fn entries(&self) -> &[UserdataTypeEntry] {
287        if self.len == 0 {
288            &[]
289        } else {
290            unsafe { slice::from_raw_parts(self.entries, self.len) }
291        }
292    }
293
294    #[inline]
295    fn entries_mut(&mut self) -> &mut [UserdataTypeEntry] {
296        if self.len == 0 {
297            &mut []
298        } else {
299            unsafe { slice::from_raw_parts_mut(self.entries, self.len) }
300        }
301    }
302}
303
304impl GlobalState {
305    pub(crate) fn userdata_type_registry_ptr(&self) -> *mut UserdataTypeRegistry {
306        unsafe {
307            self.as_ptr()
308                .as_ref()
309                .unwrap_unchecked()
310                .userdata_type_registry
311                .get()
312        }
313    }
314}
315
316#[derive(Clone, Copy, PartialEq, Eq)]
317pub struct TypedUserdata {
318    userdata: Userdata,
319    type_id: Option<TypeId>,
320}
321
322#[derive(Clone, Copy, Debug, PartialEq, Eq)]
323pub enum TypedUserdataError {
324    TypeMismatch,
325    Destructed,
326    Borrowed,
327}
328
329/// Unstable registration capability for Rust values stored in VM userdata.
330///
331/// Registered types are available to VM-native callbacks and to higher-level
332/// embedding layers. Raw and tagged userdata remain independent of this
333/// registry.
334///
335/// # Safety
336///
337/// The thread must be live, stack indices must be valid, registrations must
338/// belong to the same VM, and callers must serialize registry mutation and
339/// keep referenced metatables rooted. Each registered metatable must identify
340/// exactly one Rust payload type and must not be reassigned to userdata with a
341/// different payload layout.
342#[allow(
343    clippy::missing_safety_doc,
344    reason = "all methods share the capability-level safety contract"
345)]
346pub trait UserdataTypeRegistryAccess: Sealed {
347    unsafe fn userdata_type<T: 'static>(&self) -> Option<UserdataTypeRegistration>;
348
349    unsafe fn retire_userdata_type<T: 'static>(&self);
350
351    unsafe fn register_userdata_type<T: 'static>(
352        &self,
353        metatable_index: i32,
354    ) -> VmErrorResult<UserdataTypeRegistration>;
355}
356
357/// Unstable access capability for registered Rust userdata.
358///
359/// This provides the storage and validation mechanics used by VM-native
360/// callbacks and higher-level embedding layers; it does not root returned
361/// userdata descriptors.
362///
363/// # Safety
364///
365/// The thread and registration must be live and belong to the same VM. Stack
366/// indices must be valid. Returned descriptors do not root their userdata;
367/// callers must keep the object rooted and must not replace its registered
368/// metatable or replace its payload while a descriptor or borrow remains live.
369/// The lifecycle operations require exclusive payload access. They remove the
370/// Rust payload but leave the associated value slot intact. Values returned
371/// from that slot are non-owning VM views and remain valid only while their
372/// userdata and referenced object remain live.
373#[allow(
374    clippy::missing_safety_doc,
375    reason = "all methods share the capability-level safety contract"
376)]
377pub trait TypedUserdataAccess: Sealed {
378    unsafe fn typed_userdata(&self, userdata: Userdata) -> Option<TypedUserdata>;
379
380    unsafe fn typed_userdata_at(&self, index: i32) -> Option<TypedUserdata>;
381
382    unsafe fn push_typed_userdata<T: 'static>(
383        &self,
384        value: T,
385        registration: &UserdataTypeRegistration,
386    ) -> VmErrorResult;
387
388    unsafe fn typed_userdata_value(&self, userdata: TypedUserdata) -> TValue;
389
390    unsafe fn set_typed_userdata_value(&self, userdata: TypedUserdata, value: TValue);
391
392    unsafe fn take_typed_userdata<T: 'static>(
393        &self,
394        userdata: TypedUserdata,
395    ) -> Result<T, TypedUserdataError>;
396
397    unsafe fn destroy_typed_userdata(
398        &self,
399        userdata: TypedUserdata,
400    ) -> Result<(), TypedUserdataError>;
401}
402
403impl UserdataTypeRegistryAccess for Thread {
404    unsafe fn userdata_type<T: 'static>(&self) -> Option<UserdataTypeRegistration> {
405        unsafe { (&*self.global().userdata_type_registry_ptr()).get::<T>() }
406    }
407
408    unsafe fn retire_userdata_type<T: 'static>(&self) {
409        unsafe { (&mut *self.global().userdata_type_registry_ptr()).retire::<T>() }
410    }
411
412    unsafe fn register_userdata_type<T: 'static>(
413        &self,
414        metatable_index: i32,
415    ) -> VmErrorResult<UserdataTypeRegistration> {
416        unsafe {
417            if self.type_of(metatable_index) != LUA_TTABLE {
418                return crate::error!(self, "userdata metatable must be a table");
419            }
420
421            let metatable = self
422                .to_object(metatable_index)
423                .unwrap_unchecked()
424                .table_value();
425            UserdataTypeRegistry::ensure_destructed_metatable(
426                self.global().userdata_type_registry_ptr(),
427                self,
428            )?;
429            UserdataTypeRegistry::register::<T>(
430                self.global().userdata_type_registry_ptr(),
431                self,
432                metatable,
433            )
434        }
435    }
436}
437
438impl TypedUserdataAccess for Thread {
439    unsafe fn typed_userdata(&self, userdata: Userdata) -> Option<TypedUserdata> {
440        unsafe { (&*self.global().userdata_type_registry_ptr()).typed_userdata(userdata) }
441    }
442
443    unsafe fn typed_userdata_at(&self, index: i32) -> Option<TypedUserdata> {
444        unsafe {
445            let object = self.to_object(index)?;
446            (object.tt() == LUA_TUSERDATA)
447                .then(|| object.userdata_value())
448                .and_then(|userdata| self.typed_userdata(userdata))
449        }
450    }
451
452    unsafe fn push_typed_userdata<T: 'static>(
453        &self,
454        value: T,
455        registration: &UserdataTypeRegistration,
456    ) -> VmErrorResult {
457        unsafe {
458            let mut stack = StackGuard::new(self);
459            self.lua_check_stack(2, None)?;
460
461            if registration.type_id != TypeId::of::<T>() {
462                return crate::error!(self, "userdata registration does not match payload type");
463            }
464
465            let Some(payload_len) = typed_userdata_payload_len::<T>() else {
466                return self.too_big();
467            };
468            let data = self.new_userdata_dtor(payload_len, drop_typed_userdata::<T>)?;
469            typed_userdata_value_ptr(data.cast()).write(RAW_TVALUE_NIL);
470            typed_userdata_cell_ptr::<T>(data.cast()).write(RefCell::new(Some(value)));
471
472            self.thread_barrier();
473            self.push_table(registration.metatable)?;
474            self.set_metatable(-2)?;
475            stack.keep(1);
476        }
477        Ok(())
478    }
479
480    unsafe fn typed_userdata_value(&self, userdata: TypedUserdata) -> TValue {
481        unsafe { userdata.associated_value() }
482    }
483
484    unsafe fn set_typed_userdata_value(&self, userdata: TypedUserdata, value: TValue) {
485        unsafe {
486            userdata.associated_value().set_obj(value);
487            self.barrier_value(userdata.userdata.into(), value);
488        }
489    }
490
491    unsafe fn take_typed_userdata<T: 'static>(
492        &self,
493        userdata: TypedUserdata,
494    ) -> Result<T, TypedUserdataError> {
495        unsafe {
496            if userdata.type_id.is_none() {
497                return Err(TypedUserdataError::Destructed);
498            }
499            let Some(cell) = userdata.cell_ptr::<T>() else {
500                return Err(TypedUserdataError::TypeMismatch);
501            };
502            let mut value = (&*cell)
503                .try_borrow_mut()
504                .map_err(|_| TypedUserdataError::Borrowed)?;
505            let value = value.take().ok_or(TypedUserdataError::Destructed)?;
506            let registry = &*self.global().userdata_type_registry_ptr();
507            let metatable = Table::from_raw(NonNull::new_unchecked(registry.destructed_metatable));
508            mark_userdata_destructed(self, userdata.userdata, metatable);
509            Ok(value)
510        }
511    }
512
513    unsafe fn destroy_typed_userdata(
514        &self,
515        userdata: TypedUserdata,
516    ) -> Result<(), TypedUserdataError> {
517        unsafe { (&*self.global().userdata_type_registry_ptr()).destroy(self, userdata) }
518    }
519}
520
521impl TypedUserdata {
522    pub fn is<T: 'static>(&self) -> bool {
523        self.type_id == Some(TypeId::of::<T>())
524    }
525
526    pub const fn is_destructed(&self) -> bool {
527        self.type_id.is_none()
528    }
529
530    unsafe fn associated_value(&self) -> TValue {
531        unsafe {
532            TValue::from_raw(NonNull::new_unchecked(typed_userdata_value_ptr(
533                self.userdata.data_mut_ptr(),
534            )))
535        }
536    }
537
538    /// Returns the typed userdata cell address for `T`.
539    ///
540    /// # Safety
541    ///
542    /// The userdata object that produced this descriptor must remain alive for
543    /// any access through the returned pointer. `TypedUserdata` is not a root.
544    pub unsafe fn cell_ptr<T: 'static>(&self) -> Option<*mut RefCell<Option<T>>> {
545        if !self.is::<T>() {
546            return None;
547        }
548
549        let payload_len = self
550            .userdata
551            .total_payload_len()
552            .checked_sub(core::mem::size_of::<LuaInlineDestructor>())?;
553        if payload_len < typed_userdata_payload_len::<T>()? {
554            return None;
555        }
556
557        Some(unsafe { typed_userdata_cell_ptr::<T>(self.userdata.data_mut_ptr()) })
558    }
559}
560
561fn typed_userdata_payload_len<T>() -> Option<usize> {
562    (core::mem::align_of::<RawTValue>() - 1)
563        .checked_add(core::mem::size_of::<RawTValue>())?
564        .checked_add(core::mem::align_of::<RefCell<Option<T>>>() - 1)?
565        .checked_add(core::mem::size_of::<RefCell<Option<T>>>())
566}
567
568unsafe fn typed_userdata_value_ptr(data: *mut u8) -> *mut RawTValue {
569    let offset = data.align_offset(core::mem::align_of::<RawTValue>());
570    debug_assert_ne!(offset, usize::MAX);
571    unsafe { data.add(offset).cast() }
572}
573
574unsafe fn typed_userdata_cell_ptr<T>(data: *mut u8) -> *mut RefCell<Option<T>> {
575    let value_end = unsafe {
576        typed_userdata_value_ptr(data)
577            .cast::<u8>()
578            .add(core::mem::size_of::<RawTValue>())
579    };
580    let offset = value_end.align_offset(core::mem::align_of::<RefCell<Option<T>>>());
581    debug_assert_ne!(offset, usize::MAX);
582    unsafe { value_end.add(offset).cast() }
583}
584
585fn drop_typed_userdata<T>(data: *mut ()) {
586    unsafe {
587        ptr::drop_in_place(typed_userdata_cell_ptr::<T>(data.cast()));
588    }
589}
590
591unsafe fn destroy_typed_userdata_payload<T>(
592    thread: &Thread,
593    userdata: Userdata,
594    metatable: Table,
595) -> Result<(), TypedUserdataError> {
596    unsafe {
597        let cell = &*typed_userdata_cell_ptr::<T>(userdata.data_mut_ptr());
598        let mut value = cell
599            .try_borrow_mut()
600            .map_err(|_| TypedUserdataError::Borrowed)?;
601        let value = value.take().ok_or(TypedUserdataError::Destructed)?;
602        mark_userdata_destructed(thread, userdata, metatable);
603        drop(value);
604        Ok(())
605    }
606}
607
608unsafe fn mark_userdata_destructed(thread: &Thread, userdata: Userdata, metatable: Table) {
609    unsafe {
610        userdata.set_metatable(Some(metatable));
611        thread.object_barrier(userdata.into(), metatable.into());
612    }
613}
614
615fn destructed_userdata_error(context: NativeCallContext<'_>) -> NativeCallResult {
616    unsafe {
617        crate::error!(context.raw_thread(), "userdata has been destructed").map_err(Into::into)
618    }
619}