Skip to main content

luau_vm/thread/
table.rs

1use core::mem::MaybeUninit;
2use core::ptr::{self, NonNull};
3
4use luau_common::{ByteSlice, flags};
5
6use super::stack::RawStackAccess;
7use super::{LUA_GLOBALS_INDEX, LUA_NOREF, LUA_REFNIL, LUA_REGISTRY_INDEX, Thread};
8use crate::debug::DebugRuntime;
9use crate::gc::{GcBarrier, GcRuntime};
10use crate::handle::RawHandle;
11use crate::native::{NativeFunction, NativeModule};
12use crate::state::ThreadState;
13use crate::string::StringRuntime;
14use crate::table::TableRuntime;
15use crate::types::{LUA_TFUNCTION, LUA_TNIL, LUA_TOBJECT, LUA_TTABLE, LUA_TTHREAD, LUA_TUSERDATA};
16use crate::value::{RAW_TVALUE_NIL, TValue};
17use crate::vm::VmOperations;
18use crate::{VmErrorResult, VmResult};
19
20// Table stack API
21impl Thread {
22    /// `lua_createtable`
23    pub unsafe fn create_table(&self, array_size: usize, record_size: usize) -> VmErrorResult {
24        let (Ok(array_size), Ok(record_size)) =
25            (i32::try_from(array_size), i32::try_from(record_size))
26        else {
27            return unsafe { crate::run_error!(self, "table overflow") };
28        };
29
30        unsafe {
31            self.check_gc()?;
32            self.thread_barrier();
33            self.ensure_stack(self, 1)?;
34
35            let table = self.new_table_internal(array_size, record_size)?;
36            let top = self.stack_top();
37            top.value_unchecked().set_table_value(table);
38            debug_assert!(top < self.current_call_info().top());
39            self.set_stack_top(top.add(1));
40        }
41        Ok(())
42    }
43
44    /// `lua_newtable`
45    pub unsafe fn new_table(&self) -> VmErrorResult {
46        unsafe { self.create_table(0, 0) }
47    }
48
49    /// `lua_gettable`
50    pub unsafe fn get_table(&self, index: i32) -> VmResult<i32> {
51        unsafe {
52            let top = self.stack_top().sub(1);
53            debug_assert!(top >= self.stack_base());
54
55            self.thread_barrier();
56
57            let table = self.to_object(index).unwrap_unchecked();
58            self.get_table_internal(table, top.value_unchecked(), top)?;
59            Ok(self.stack_top().sub(1).value_unchecked().tt())
60        }
61    }
62
63    /// `lua_getfield`
64    pub unsafe fn get_field(&self, index: i32, key: impl AsRef<[u8]>) -> VmResult<i32> {
65        let key = key.as_ref();
66        let mut key_storage = RAW_TVALUE_NIL;
67        unsafe {
68            self.thread_barrier();
69            self.ensure_stack(self, 1)?;
70
71            let table = self.to_object(index).unwrap_unchecked();
72            let mut table_storage = MaybeUninit::uninit();
73            ptr::copy_nonoverlapping(table.as_ptr(), table_storage.as_mut_ptr(), 1);
74            let table = TValue::from_raw(NonNull::new_unchecked(table_storage.as_mut_ptr()));
75            let key_value = TValue::from_mut(&mut key_storage);
76            key_value.set_string_value(self.intern_string(key.as_bstr())?);
77            let top = self.stack_top();
78            self.get_table_internal(table, key_value, top)?;
79            let top = self.stack_top();
80            debug_assert!(top < self.current_call_info().top());
81            self.set_stack_top(top.add(1));
82            Ok(top.value_unchecked().tt())
83        }
84    }
85
86    /// `lua_getglobal`
87    pub unsafe fn get_global(&self, name: impl AsRef<[u8]>) -> VmResult<i32> {
88        unsafe { self.get_field(LUA_GLOBALS_INDEX, name) }
89    }
90
91    /// `lua_rawgetfield`
92    pub unsafe fn raw_get_field(&self, index: i32, key: impl AsRef<[u8]>) -> VmErrorResult<i32> {
93        let key = key.as_ref();
94        unsafe {
95            self.thread_barrier();
96            self.ensure_stack(self, 1)?;
97
98            let object = self.to_object(index).unwrap_unchecked();
99            debug_assert!(object.is_table());
100            let table = object.table_value();
101            let string = self.intern_string(key.as_bstr())?;
102            let top = self.stack_top();
103            top.value_unchecked().set_obj(table.get_str(string));
104            debug_assert!(top < self.current_call_info().top());
105            self.set_stack_top(top.add(1));
106            Ok(top.value_unchecked().tt())
107        }
108    }
109
110    /// `lua_rawget`
111    pub unsafe fn raw_get(&self, index: i32) -> i32 {
112        unsafe {
113            self.thread_barrier();
114
115            let table = self.to_object(index).unwrap_unchecked();
116            debug_assert!(table.is_table());
117
118            let top = self.stack_top().sub(1);
119            top.value_unchecked()
120                .set_obj(table.table_value().get(top.value_unchecked()));
121            top.value_unchecked().tt()
122        }
123    }
124
125    /// `lua_rawgeti`
126    pub unsafe fn raw_geti(&self, index: i32, key: i32) -> VmErrorResult<i32> {
127        unsafe {
128            self.thread_barrier();
129            self.ensure_stack(self, 1)?;
130
131            let table = self.to_object(index).unwrap_unchecked();
132            debug_assert!(table.is_table());
133
134            let top = self.stack_top();
135            top.value_unchecked()
136                .set_obj(table.table_value().get_num(key));
137            debug_assert!(top < self.current_call_info().top());
138            self.set_stack_top(top.add(1));
139            Ok(top.value_unchecked().tt())
140        }
141    }
142
143    /// `lua_rawgetptagged`
144    pub unsafe fn raw_getp_tagged(&self, index: i32, key: *mut (), tag: i32) -> VmErrorResult<i32> {
145        unsafe {
146            self.thread_barrier();
147            self.ensure_stack(self, 1)?;
148
149            let table = self.to_object(index).unwrap_unchecked();
150            debug_assert!(table.is_table());
151
152            let top = self.stack_top();
153            top.value_unchecked()
154                .set_obj(table.table_value().getp(key, tag));
155            debug_assert!(top < self.current_call_info().top());
156            self.set_stack_top(top.add(1));
157            Ok(top.value_unchecked().tt())
158        }
159    }
160
161    /// `lua_rawgetp`
162    pub unsafe fn raw_getp(&self, index: i32, key: *mut ()) -> VmErrorResult<i32> {
163        unsafe { self.raw_getp_tagged(index, key, 0) }
164    }
165
166    /// `lua_setreadonly`
167    pub unsafe fn set_readonly(&self, index: i32, enabled: i32) {
168        unsafe {
169            let object = self.to_object(index).unwrap_unchecked();
170            debug_assert!(object.is_table());
171
172            let table = object.table_value();
173            let registry = self.global().registry().table_value();
174            debug_assert!(table != registry);
175            table.as_ptr().as_mut().unwrap_unchecked().readonly = u8::from(enabled != 0);
176        }
177    }
178
179    /// `lua_getreadonly`
180    pub unsafe fn get_readonly(&self, index: i32) -> i32 {
181        unsafe {
182            let object = self.to_object(index).unwrap_unchecked();
183            debug_assert!(object.is_table());
184            i32::from(
185                object
186                    .table_value()
187                    .as_ptr()
188                    .as_ref()
189                    .unwrap_unchecked()
190                    .readonly
191                    != 0,
192            )
193        }
194    }
195
196    /// `lua_setsafeenv`
197    pub unsafe fn set_safe_env(&self, index: i32, enabled: i32) {
198        let object = unsafe { self.to_object(index).unwrap_unchecked() };
199        debug_assert!(object.is_table());
200
201        unsafe {
202            object
203                .table_value()
204                .as_ptr()
205                .as_mut()
206                .unwrap_unchecked()
207                .safe_env = u8::from(enabled != 0);
208        }
209    }
210
211    /// `lua_getmetatable`
212    pub unsafe fn get_metatable(&self, index: i32) -> VmErrorResult<i32> {
213        unsafe {
214            self.thread_barrier();
215            self.ensure_stack(self, 1)?;
216
217            let object = self.to_object(index);
218            let metatable = if let Some(object) = object {
219                match object.tt() {
220                    x if x == LUA_TTABLE => object.table_value().metatable(),
221                    x if x == LUA_TUSERDATA => object.userdata_value().metatable(),
222                    x if x == LUA_TOBJECT => object.object_value().class().instance_metatable(),
223                    tag => self.global().metatable(tag as usize),
224                }
225            } else {
226                None
227            };
228
229            if let Some(metatable) = metatable {
230                let top = self.stack_top();
231                top.value_unchecked().set_table_value(metatable);
232                debug_assert!(top < self.current_call_info().top());
233                self.set_stack_top(top.add(1));
234                Ok(1)
235            } else {
236                Ok(0)
237            }
238        }
239    }
240
241    /// `lua_getfenv`
242    pub unsafe fn get_fenv(&self, index: i32) -> VmErrorResult {
243        unsafe {
244            self.thread_barrier();
245            self.ensure_stack(self, 1)?;
246
247            let object = self.to_object(index).unwrap_unchecked();
248            let top = self.stack_top();
249            match object.tt() {
250                x if x == LUA_TFUNCTION => top
251                    .value_unchecked()
252                    .set_table_value(object.closure_value().env()),
253                x if x == LUA_TTHREAD => top
254                    .value_unchecked()
255                    .set_table_value(object.thread_value().globals()),
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_settable`
266    pub unsafe fn set_table(&self, index: i32) -> VmResult {
267        unsafe {
268            let top = self.stack_top();
269            debug_assert!(top.offset_from(self.stack_base()) >= 2);
270
271            let table = self.to_object(index).unwrap_unchecked();
272            let key = top.sub(2).value_unchecked();
273            let value = top.sub(1).value_unchecked();
274            self.set_table_internal(table, key, value)?;
275            self.set_stack_top(self.stack_top().sub(2));
276        }
277        Ok(())
278    }
279
280    /// `lua_setfield`
281    pub unsafe fn set_field(&self, index: i32, key: impl AsRef<[u8]>) -> VmResult {
282        let key = key.as_ref();
283        let mut key_storage = RAW_TVALUE_NIL;
284        unsafe {
285            let top = self.stack_top();
286            debug_assert!(top.offset_from(self.stack_base()) > 0);
287
288            let table = self.to_object(index).unwrap_unchecked();
289            let mut table_storage = MaybeUninit::uninit();
290            ptr::copy_nonoverlapping(table.as_ptr(), table_storage.as_mut_ptr(), 1);
291            let table = TValue::from_raw(NonNull::new_unchecked(table_storage.as_mut_ptr()));
292            let key_value = TValue::from_mut(&mut key_storage);
293            key_value.set_string_value(self.intern_string(key.as_bstr())?);
294            let value = top.sub(1).value_unchecked();
295
296            self.set_table_internal(table, key_value, value)?;
297            self.set_stack_top(self.stack_top().sub(1));
298        }
299        Ok(())
300    }
301
302    /// `lua_rawsetfield`
303    pub unsafe fn raw_set_field(&self, index: i32, key: impl AsRef<[u8]>) -> VmErrorResult {
304        let key = key.as_ref();
305        unsafe {
306            let top = self.stack_top();
307            debug_assert!(top.offset_from(self.stack_base()) > 0);
308
309            let object = self.to_object(index).unwrap_unchecked();
310            debug_assert!(object.is_table());
311            let table = object.table_value();
312            let value = top.sub(1).value_unchecked();
313
314            if table.as_ptr().as_ref().unwrap_unchecked().readonly != 0 {
315                return self.readonly_error();
316            }
317
318            let string = self.intern_string(key.as_bstr())?;
319            let node_cursor = self.set_str(table, string)?;
320            node_cursor
321                .node_unchecked()
322                .value_unchecked()
323                .set_obj(value);
324            if value.is_collectable() {
325                self.barrier_table(table, value.gc_value());
326            }
327
328            self.set_stack_top(top.sub(1));
329        }
330        Ok(())
331    }
332
333    /// `lua_setglobal`
334    pub unsafe fn set_global(&self, name: impl AsRef<[u8]>) -> VmErrorResult {
335        unsafe { self.raw_set_field(LUA_GLOBALS_INDEX, name) }
336    }
337
338    /// `lua_concat`
339    pub unsafe fn concat(&self, count: i32) -> VmResult {
340        debug_assert!(count >= 0);
341
342        if count >= 2 {
343            unsafe {
344                self.check_gc()?;
345                self.thread_barrier();
346                self.concat_internal(count, self.get_top() - 1)?;
347                self.set_stack_top(self.stack_top().sub((count - 1) as usize));
348            }
349        } else if count == 0 {
350            unsafe {
351                self.thread_barrier();
352                self.ensure_stack(self, 1)?;
353                self.push_string("")?;
354            }
355        }
356        Ok(())
357    }
358
359    /// `lua_rawset`
360    pub unsafe fn raw_set(&self, index: i32) -> VmErrorResult {
361        unsafe {
362            let top = self.stack_top();
363            debug_assert!(top.offset_from(self.stack_base()) >= 2);
364
365            let object = self.to_object(index).unwrap_unchecked();
366            debug_assert!(object.is_table());
367            let table = object.table_value();
368            let key = top.sub(2).value_unchecked();
369            let value = top.sub(1).value_unchecked();
370
371            if table.as_ptr().as_ref().unwrap_unchecked().readonly != 0 {
372                return self.readonly_error();
373            }
374
375            let slot = self.set(table, key)?;
376            slot.set_obj(value);
377            if value.is_collectable() {
378                self.barrier_table(table, value.gc_value());
379            }
380
381            self.set_stack_top(top.sub(2));
382        }
383        Ok(())
384    }
385
386    /// `lua_rawseti`
387    pub unsafe fn raw_seti(&self, index: i32, key: i32) -> VmErrorResult {
388        unsafe {
389            let top = self.stack_top();
390            debug_assert!(top.offset_from(self.stack_base()) > 0);
391
392            let object = self.to_object(index).unwrap_unchecked();
393            debug_assert!(object.is_table());
394            let table = object.table_value();
395            let value = top.sub(1).value_unchecked();
396
397            if table.as_ptr().as_ref().unwrap_unchecked().readonly != 0 {
398                return self.readonly_error();
399            }
400
401            let slot = self.set_num(table, key)?;
402            slot.set_obj(value);
403            if value.is_collectable() {
404                self.barrier_table(table, value.gc_value());
405            }
406
407            self.set_stack_top(top.sub(1));
408        }
409        Ok(())
410    }
411
412    /// `lua_rawsetptagged`
413    pub unsafe fn raw_setp_tagged(&self, index: i32, key: *mut (), tag: i32) -> VmErrorResult {
414        unsafe {
415            let top = self.stack_top();
416            debug_assert!(top.offset_from(self.stack_base()) > 0);
417
418            let object = self.to_object(index).unwrap_unchecked();
419            debug_assert!(object.is_table());
420            let table = object.table_value();
421            let value = top.sub(1).value_unchecked();
422
423            if table.as_ptr().as_ref().unwrap_unchecked().readonly != 0 {
424                return self.readonly_error();
425            }
426
427            let slot = self.setp(table, key, tag)?;
428            slot.set_obj(value);
429            if value.is_collectable() {
430                self.barrier_table(table, value.gc_value());
431            }
432
433            self.set_stack_top(top.sub(1));
434        }
435        Ok(())
436    }
437
438    /// `lua_rawsetp`
439    pub unsafe fn raw_setp(&self, index: i32, key: *mut ()) -> VmErrorResult {
440        unsafe { self.raw_setp_tagged(index, key, 0) }
441    }
442
443    /// `lua_setmetatable`
444    pub unsafe fn set_metatable(&self, index: i32) -> VmErrorResult<i32> {
445        unsafe {
446            let top = self.stack_top();
447            debug_assert!(top.offset_from(self.stack_base()) > 0);
448
449            let object = self.to_object(index).unwrap_unchecked();
450            let metatable_value = top.sub(1).value_unchecked();
451
452            let metatable = if metatable_value.is_nil() {
453                None
454            } else {
455                debug_assert!(metatable_value.is_table());
456                Some(metatable_value.table_value())
457            };
458
459            match object.tt() {
460                x if x == LUA_TTABLE => {
461                    let table = object.table_value();
462                    if table.as_ptr().as_ref().unwrap_unchecked().readonly != 0 {
463                        return self.readonly_error();
464                    }
465
466                    table.set_metatable(metatable);
467                    if let Some(metatable) = metatable {
468                        self.object_barrier(table.into(), metatable.into());
469                    }
470                }
471                x if x == LUA_TUSERDATA => {
472                    let userdata = object.userdata_value();
473                    userdata.set_metatable(metatable);
474                    if let Some(metatable) = metatable {
475                        self.object_barrier(userdata.into(), metatable.into());
476                    }
477                }
478                tag => {
479                    self.global().as_ptr().as_mut().unwrap_unchecked().mt[tag as usize] =
480                        metatable.map_or(ptr::null_mut(), |table| table.as_ptr());
481                }
482            }
483
484            self.set_stack_top(top.sub(1));
485            Ok(1)
486        }
487    }
488
489    /// `lua_setfenv`
490    pub unsafe fn set_fenv(&self, index: i32) -> i32 {
491        unsafe {
492            let top = self.stack_top();
493            debug_assert!(top.offset_from(self.stack_base()) > 0);
494            let object = self.to_object(index).unwrap_unchecked();
495            let environment = top.sub(1).value_unchecked();
496            debug_assert!(environment.is_table());
497
498            let environment = environment.table_value();
499            let result = match object.tt() {
500                x if x == LUA_TFUNCTION => {
501                    let closure = object.closure_value();
502                    closure.set_env(environment);
503                    self.object_barrier(closure.into(), environment.into());
504                    1
505                }
506                x if x == LUA_TTHREAD => {
507                    let thread = object.thread_value();
508                    thread.set_globals(environment);
509                    self.object_barrier((&thread).into(), environment.into());
510                    1
511                }
512                _ => 0,
513            };
514
515            self.set_stack_top(top.sub(1));
516            result
517        }
518    }
519
520    /// `lua_next`
521    pub unsafe fn next(&self, index: i32) -> VmErrorResult<i32> {
522        unsafe {
523            let top = self.stack_top();
524            debug_assert!(top.offset_from(self.stack_base()) > 0);
525            self.thread_barrier();
526            self.ensure_stack(self, 1)?;
527
528            let table = self.to_object(index).unwrap_unchecked();
529            debug_assert!(table.is_table());
530            let key = top.sub(1);
531
532            let more = self.next_internal(table.table_value(), key)?;
533            if more != 0 {
534                debug_assert!(top < self.current_call_info().top());
535                self.set_stack_top(top.add(1));
536            } else {
537                self.set_stack_top(top.sub(1));
538            }
539
540            Ok(more)
541        }
542    }
543
544    /// `lua_rawiter`
545    pub unsafe fn raw_iter(&self, index: i32, mut iter: i32) -> VmErrorResult<i32> {
546        unsafe {
547            self.thread_barrier();
548            self.ensure_stack(self, 2)?;
549
550            let table = self.to_object(index).unwrap_unchecked();
551            debug_assert!(table.is_table());
552            debug_assert!(iter >= 0);
553
554            let table = table.table_value();
555            let size_array = table.as_ptr().as_ref().unwrap_unchecked().size_array;
556
557            while (iter as u32) < size_array as u32 {
558                let entry = table.array_slot(iter as usize);
559                if !entry.is_nil() {
560                    let top = self.stack_top();
561                    top.value_unchecked().set_number((iter + 1) as f64);
562                    top.add(1).value_unchecked().set_obj(entry);
563                    debug_assert!(top.add(2) <= self.current_call_info().top());
564                    self.set_stack_top(top.add(2));
565                    return Ok(iter + 1);
566                }
567                iter += 1;
568            }
569
570            let size_node = table.node_count();
571            while ((iter - size_array) as u32) < size_node as u32 {
572                let node = table.node(iter - size_array);
573                if !node.value_unchecked().is_nil() {
574                    let top = self.stack_top();
575                    node.write_key_to_value(top.value_unchecked());
576                    top.add(1).value_unchecked().set_obj(node.value_unchecked());
577                    debug_assert!(top.add(2) <= self.current_call_info().top());
578                    self.set_stack_top(top.add(2));
579                    return Ok(iter + 1);
580                }
581                iter += 1;
582            }
583
584            Ok(-1)
585        }
586    }
587
588    /// `lua_cleartable`
589    pub unsafe fn clear_table(&self, index: i32) -> VmErrorResult {
590        unsafe {
591            let object = self.to_object(index).unwrap_unchecked();
592            debug_assert!(object.is_table());
593
594            let table = object.table_value();
595            if table.as_ptr().as_ref().unwrap_unchecked().readonly != 0 {
596                return self.readonly_error();
597            }
598
599            table.clear();
600        }
601        Ok(())
602    }
603
604    /// `lua_clonetable`
605    pub unsafe fn clone_table(&self, index: i32) -> VmErrorResult {
606        unsafe {
607            if flags::LuauCloneTableFix.get() {
608                self.check_gc()?;
609                self.thread_barrier();
610            }
611            self.ensure_stack(self, 1)?;
612
613            let object = self.to_object(index).unwrap_unchecked();
614            debug_assert!(object.is_table());
615
616            let cloned = self.clone_table_internal(object.table_value())?;
617            let top = self.stack_top();
618            top.value_unchecked().set_table_value(cloned);
619            debug_assert!(top < self.current_call_info().top());
620            self.set_stack_top(top.add(1));
621        }
622        Ok(())
623    }
624}
625
626// Registry references
627impl Thread {
628    unsafe fn ref_in_registry(
629        &self,
630        index: i32,
631        registry: crate::Table,
632        registry_free: i32,
633    ) -> VmErrorResult<(i32, i32)> {
634        let Some(object) = (unsafe { self.to_object(index) }) else {
635            return Ok((LUA_REFNIL, registry_free));
636        };
637        if object.is_nil() {
638            return Ok((LUA_REFNIL, registry_free));
639        }
640
641        unsafe {
642            let reference = if registry_free != 0 {
643                registry_free
644            } else {
645                registry.getn() + 1
646            };
647
648            let slot = self.set_num(registry, reference)?;
649            let next_free = if registry_free != 0 {
650                slot.number_value() as i32
651            } else {
652                registry_free
653            };
654
655            slot.set_obj(object);
656            if object.is_collectable() {
657                self.barrier_table(registry, object.gc_value());
658            }
659
660            Ok((reference, next_free))
661        }
662    }
663
664    unsafe fn unref_in_registry(
665        &self,
666        reference: i32,
667        registry: crate::Table,
668        registry_free: i32,
669    ) -> i32 {
670        if reference <= LUA_REFNIL {
671            return registry_free;
672        }
673
674        unsafe {
675            let slot = registry.get_num(reference);
676            debug_assert!(slot != crate::value::nil_object());
677            slot.set_number(registry_free as f64);
678        }
679
680        reference
681    }
682
683    /// `lua_getref`
684    pub unsafe fn get_ref(&self, reference: i32) -> VmErrorResult<i32> {
685        unsafe { self.raw_geti(LUA_REGISTRY_INDEX, reference) }
686    }
687
688    /// `lua_ref`
689    pub unsafe fn ref_value(&self, index: i32) -> VmErrorResult<i32> {
690        debug_assert!(index != LUA_REGISTRY_INDEX);
691        unsafe {
692            let global = self.global();
693            let (reference, registry_free) = self.ref_in_registry(
694                index,
695                global.registry().table_value(),
696                global.registry_free(),
697            )?;
698            global.set_registry_free(registry_free);
699            Ok(reference)
700        }
701    }
702
703    /// `lua_unref`
704    pub unsafe fn unref_value(&self, reference: i32) -> i32 {
705        unsafe {
706            let global = self.global();
707            let registry_free = self.unref_in_registry(
708                reference,
709                global.registry().table_value(),
710                global.registry_free(),
711            );
712            global.set_registry_free(registry_free);
713        }
714
715        LUA_NOREF
716    }
717
718    /// `lua_weakref`
719    pub unsafe fn weak_ref_value(&self, index: i32) -> VmErrorResult<i32> {
720        debug_assert!(flags::LuauGcTraceUdata.get());
721
722        unsafe {
723            let global = self.global();
724            let (reference, registry_free) = self.ref_in_registry(
725                index,
726                global.weak_registry().table_value(),
727                global.weak_registry_free(),
728            )?;
729            global.set_weak_registry_free(registry_free);
730            Ok(reference)
731        }
732    }
733
734    /// `lua_weakunref`
735    pub unsafe fn weak_unref_value(&self, reference: i32) -> i32 {
736        debug_assert!(flags::LuauGcTraceUdata.get());
737
738        unsafe {
739            let global = self.global();
740            let registry_free = self.unref_in_registry(
741                reference,
742                global.weak_registry().table_value(),
743                global.weak_registry_free(),
744            );
745            global.set_weak_registry_free(registry_free);
746        }
747
748        LUA_NOREF
749    }
750
751    /// `lua_getweakref`
752    pub unsafe fn get_weak_ref(&self, reference: i32) -> VmErrorResult<i32> {
753        debug_assert!(flags::LuauGcTraceUdata.get());
754
755        unsafe {
756            self.thread_barrier();
757            self.ensure_stack(self, 1)?;
758
759            let global = self.global();
760            let registry = global.weak_registry().table_value();
761            let top = self.stack_top();
762            top.value_unchecked().set_obj(registry.get_num(reference));
763            self.set_stack_top(top.add(1));
764            Ok(top.value_unchecked().tt())
765        }
766    }
767}
768
769// Metatables and module registration
770impl Thread {
771    /// `luaL_getmetafield`
772    pub unsafe fn get_metafield(&self, index: i32, event: &str) -> VmErrorResult<i32> {
773        unsafe {
774            if self.get_metatable(index)? == 0 {
775                return Ok(0);
776            }
777
778            self.push_optional_string(Some(event))?;
779            let result = self.raw_get(-2);
780            if result == LUA_TNIL {
781                self.pop(2);
782                Ok(0)
783            } else {
784                self.remove(-2);
785                Ok(1)
786            }
787        }
788    }
789
790    /// `luaL_callmeta`
791    pub unsafe fn call_meta(&self, object: i32, event: &str) -> VmResult<i32> {
792        unsafe {
793            let object = self.abs_index(object);
794            if self.get_metafield(object, event)? == 0 {
795                return Ok(0);
796            }
797
798            self.push_value(object)?;
799            self.call(1, 1)?;
800            Ok(1)
801        }
802    }
803
804    /// `luaL_newmetatable`
805    pub unsafe fn new_metatable(&self, table_name: &str) -> VmErrorResult<i32> {
806        let result = unsafe { self.raw_get_field(LUA_REGISTRY_INDEX, table_name)? };
807        if result != LUA_TNIL {
808            return Ok(0);
809        }
810
811        unsafe {
812            self.pop(1);
813            self.create_table(0, 0)?;
814            self.push_value(-1)?;
815
816            let registry = self.global().registry().table_value();
817            let key = self.intern_string(table_name.as_bytes().as_bstr())?;
818            let node_cursor = self.set_str(registry, key)?;
819            let value = self.stack_top().sub(1);
820            node_cursor
821                .node_unchecked()
822                .value_unchecked()
823                .set_obj(value.value_unchecked());
824            self.barrier_value(registry.into(), value.value_unchecked());
825            self.pop(1);
826        }
827
828        Ok(1)
829    }
830
831    /// `luaL_findtable`
832    pub unsafe fn find_table<'a, T>(
833        &self,
834        index: i32,
835        name: &'a T,
836        size_hint: usize,
837    ) -> VmErrorResult<Option<&'a [u8]>>
838    where
839        T: AsRef<[u8]> + ?Sized,
840    {
841        let mut remainder = name.as_ref();
842        unsafe {
843            self.push_value(index)?;
844
845            loop {
846                let separator = remainder.iter().position(|byte| *byte == b'.');
847                let (field, rest, has_more) = match separator {
848                    Some(pos) => (&remainder[..pos], &remainder[pos + 1..], true),
849                    None => (remainder, &[][..], false),
850                };
851
852                self.push_string(field)?;
853                let result = self.raw_get(-2);
854                if result == LUA_TNIL {
855                    self.pop(1);
856                    self.create_table(0, if has_more { 1 } else { size_hint })?;
857                    self.push_string(field)?;
858                    self.push_value(-2)?;
859                    self.raw_set(-4)?;
860                } else if result != LUA_TTABLE {
861                    self.pop(2);
862                    return Ok(Some(remainder));
863                }
864
865                self.remove(-2);
866                if !has_more {
867                    return Ok(None);
868                }
869
870                remainder = rest;
871            }
872        }
873    }
874
875    /// `luaL_register`
876    pub unsafe fn register(
877        &self,
878        library_name: Option<&str>,
879        functions: &[NativeFunction],
880    ) -> VmResult {
881        unsafe {
882            if let Some(library_name) = library_name {
883                if self.find_table(LUA_REGISTRY_INDEX, "_LOADED", 1)?.is_some() {
884                    return crate::error!(self, "name conflict for module '_LOADED'")
885                        .map_err(Into::into);
886                }
887
888                let result = self.get_field(-1, library_name)?;
889                if result != LUA_TTABLE {
890                    self.pop(1);
891
892                    if let Some(problem) =
893                        self.find_table(LUA_GLOBALS_INDEX, library_name, functions.len())?
894                    {
895                        return crate::error!(self, "name conflict for module '%s'", &problem)
896                            .map_err(Into::into);
897                    }
898
899                    self.push_value(-1)?;
900                    self.set_field(-3, library_name)?;
901                }
902
903                self.remove(-2);
904            }
905
906            for entry in functions {
907                self.push_native_closure_k(entry.function, Some(entry.name), 0, None)?;
908                self.set_field(-2, entry.name)?;
909            }
910        }
911        Ok(())
912    }
913
914    pub unsafe fn register_module(&self, module: &NativeModule) -> VmResult {
915        unsafe { self.register(module.name, module.functions) }
916    }
917}
918
919// Environment sandboxing
920impl Thread {
921    /// `luaL_sandbox`
922    ///
923    /// # Safety
924    ///
925    /// The caller must ensure that the current globals table can be made
926    /// readonly and marked as a safe environment for future loads.
927    pub unsafe fn sandbox(&self) -> VmErrorResult {
928        unsafe {
929            self.push_nil()?;
930            while self.next(LUA_GLOBALS_INDEX)? != 0 {
931                if self.type_of(-1) == LUA_TTABLE {
932                    self.set_readonly(-1, 1);
933                }
934
935                self.pop(1);
936            }
937
938            self.push_string("")?;
939            if self.get_metatable(-1)? != 0 {
940                self.set_readonly(-1, 1);
941                self.pop(2);
942            } else {
943                self.pop(1);
944            }
945
946            self.set_readonly(LUA_GLOBALS_INDEX, 1);
947            self.set_safe_env(LUA_GLOBALS_INDEX, 1);
948        }
949        Ok(())
950    }
951
952    /// `luaL_sandboxthread`
953    ///
954    /// # Safety
955    ///
956    /// The caller must ensure that the thread can have its globals table
957    /// replaced. If bytecode is loaded into the same sandboxed thread
958    /// environment more than once, the caller must clear `safeenv` before the
959    /// later load or install a fresh sandboxed environment first.
960    pub unsafe fn sandbox_thread(&self) -> VmErrorResult {
961        unsafe {
962            self.create_table(0, 0)?;
963            self.create_table(0, 0)?;
964            self.push_value(LUA_GLOBALS_INDEX)?;
965
966            let metatable = self.to_object(-2).unwrap_unchecked().table_value();
967            let key = self.intern_string(b"__index".as_bstr())?;
968            let node_cursor = self.set_str(metatable, key)?;
969            let value = self.stack_top().sub(1);
970
971            node_cursor
972                .node_unchecked()
973                .value_unchecked()
974                .set_obj(value.value_unchecked());
975            self.barrier_value(metatable.into(), value.value_unchecked());
976            self.pop(1);
977            self.set_readonly(-1, 1);
978            self.set_metatable(-2)?;
979            self.replace(LUA_GLOBALS_INDEX);
980            self.set_safe_env(LUA_GLOBALS_INDEX, 1);
981        }
982        Ok(())
983    }
984}