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 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 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#[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#[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 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}