luau-vm 0.732.0

Pure-Rust Luau virtual machine, garbage collector, and standard libraries
Documentation
use luau_common::flags;

use crate::Table;
use crate::call::ThreadStack;
use crate::function::{Closure, Proto};
use crate::gc::GCS_SWEEP;
use crate::gc::GcObject;
use crate::handle::RawHandle;
use crate::memory::LuaPage;
use crate::state::{GlobalState, ThreadState};
use crate::thread::Thread;
use crate::types;
use crate::userdata::TypedUserdataAccess;
use crate::value::{RAW_TVALUE_NIL, TValue};
use crate::{Class, Object};

impl GlobalState {
    /// `validateobjref`
    pub(super) unsafe fn validate_object_ref(&self, from: GcObject, to: GcObject) {
        unsafe {
            debug_assert!(!self.is_dead(to));

            if self.keep_invariant() {
                debug_assert!(!(from.is_black() && to.is_white()));
            }
        }
    }

    /// `validateref`
    pub(super) unsafe fn validate_ref(&self, from: GcObject, value: TValue) {
        if value.is_collectable() {
            unsafe {
                let object = value.gc_value();
                debug_assert_eq!(
                    value.tt() as u8,
                    object.as_ptr().as_ref().unwrap_unchecked().tt
                );
                self.validate_object_ref(from, object);
            }
        }
    }

    /// `checkliveness`
    pub(in crate::gc) unsafe fn validate_liveness(&self, value: TValue) {
        if value.is_collectable() {
            unsafe {
                let object = value.gc_value();
                debug_assert_eq!(
                    value.tt() as u8,
                    object.as_ptr().as_ref().unwrap_unchecked().tt
                );
                debug_assert!(!self.is_dead(object));
            }
        }
    }

    /// `validatetable`
    unsafe fn validate_table(&self, table: Table) {
        unsafe {
            let table_ref = table.as_ptr().as_ref().unwrap_unchecked();
            let table_object: GcObject = table.into();
            let size_node = table.node_count() as i32;

            debug_assert!(table.as_ptr().as_ref().unwrap_unchecked().free.last_free <= size_node);

            if let Some(metatable) = table.metatable() {
                self.validate_object_ref(table_object, metatable.into());
            }

            for index in 0..table_ref.size_array as usize {
                self.validate_ref(table_object, table.array_slot(index));
            }

            for index in 0..size_node as usize {
                let node = table.node(index as i32);

                debug_assert!(
                    node.key().tt() != types::LUA_TDEADKEY || node.value_unchecked().is_nil()
                );

                let next_index = index as i32 + node.next();
                debug_assert!((0..size_node).contains(&next_index));

                if !node.value_unchecked().is_nil() {
                    let mut key = RAW_TVALUE_NIL;
                    node.write_key_to_value(TValue::from_mut(&mut key));
                    self.validate_ref(table_object, TValue::from_ref(&key));
                    self.validate_ref(table_object, node.value_unchecked());
                }
            }
        }
    }

    /// `validateclosure`
    unsafe fn validate_closure(&self, closure: Closure) {
        unsafe {
            let closure_ref = closure.as_ptr().as_ref().unwrap_unchecked();
            let closure_object: GcObject = closure.into();

            self.validate_object_ref(closure_object, closure.env().into());

            if closure.is_native() {
                if flags::LuauManagedDebugNames.get()
                    && let Some(debug_name) = closure.native_debug_name()
                    && let crate::string::LuaStringRepr::Interned(debug_name) = debug_name.0
                {
                    self.validate_object_ref(closure_object, debug_name.into());
                }
                for index in 0..closure_ref.n_upvalues as usize {
                    self.validate_ref(closure_object, closure.native_upvalue(index));
                }
            } else {
                let proto = closure.proto().unwrap_unchecked();

                debug_assert_eq!(
                    closure_ref.n_upvalues,
                    proto.as_ptr().as_ref().unwrap_unchecked().n_ups
                );
                self.validate_object_ref(closure_object, proto.into());

                for index in 0..closure_ref.n_upvalues as usize {
                    self.validate_ref(closure_object, closure.lua_upvalue_ref(index));
                }
            }
        }
    }

    /// `validatestack`
    unsafe fn validate_stack(&self, thread: &Thread) {
        unsafe {
            let thread_object: GcObject = thread.into();
            let stack_start = thread.restore_stack(0);
            let stack_top = thread.stack_top();
            let stack_last = thread.stack_last();

            self.validate_object_ref(thread_object, thread.globals().into());

            let mut call_info_cursor = thread.base_call_info_cursor();
            let current_call_info_cursor = thread.current_call_info_cursor();
            while call_info_cursor <= current_call_info_cursor {
                let call_info = call_info_cursor.call_info_unchecked();
                debug_assert!(stack_start <= call_info.base());
                debug_assert!(
                    call_info.function() <= call_info.base() && call_info.base() <= call_info.top()
                );
                debug_assert!(call_info.top() <= stack_last);
                call_info_cursor = call_info_cursor.add(1);
            }

            let mut slot = stack_start;
            while slot < stack_top {
                self.validate_liveness(slot.value_unchecked());
                slot = slot.add(1);
            }

            if let Some(name_call) = thread.name_call() {
                self.validate_object_ref(thread_object, name_call.into());
            }

            let mut upvalue = thread.open_upvalue();
            while let Some(current_upvalue) = upvalue {
                let open = current_upvalue.open_data();
                let object: GcObject = current_upvalue.into();

                debug_assert_eq!(
                    object.as_ptr().as_ref().unwrap_unchecked().tt,
                    types::LUA_TUPVALUE as u8
                );
                debug_assert!(current_upvalue.is_open());
                debug_assert!(open.next().open_data().prev() == current_upvalue);
                debug_assert!(open.prev().open_data().next() == current_upvalue);
                debug_assert!(!object.is_black());

                upvalue = open.thread_next();
            }
        }
    }

    /// `validateproto`
    unsafe fn validate_proto(&self, proto: Proto) {
        unsafe {
            let proto_ref = proto.as_ptr().as_ref().unwrap_unchecked();
            let proto_object: GcObject = proto.into();

            if let Some(source) = proto.source() {
                self.validate_object_ref(proto_object, source.into());
            }

            if let Some(debug_name) = proto.debug_name() {
                self.validate_object_ref(proto_object, debug_name.into());
            }

            for index in 0..proto_ref.size_k as usize {
                self.validate_ref(proto_object, proto.constant(index));
            }

            for index in 0..proto_ref.size_upvalues as usize {
                if let Some(upvalue) = proto.upvalue_name(index) {
                    self.validate_object_ref(proto_object, upvalue.into());
                }
            }

            for index in 0..proto_ref.size_p as usize {
                if let Some(child) = proto.child_proto(index) {
                    self.validate_object_ref(proto_object, child.into());
                }
            }

            for index in 0..proto_ref.size_loc_vars as usize {
                if let Some(local) = proto.loc_var(index)
                    && let Some(var_name) = local.name()
                {
                    self.validate_object_ref(proto_object, var_name.into());
                }
            }
        }
    }

    /// `validateclass`
    unsafe fn validate_class(&self, class_object: Class) {
        unsafe {
            let class_ref = class_object.as_ptr().as_ref().unwrap_unchecked();
            let object: GcObject = class_object.into();
            self.validate_object_ref(object, class_object.name().into());
            self.validate_object_ref(object, class_object.members_to_offset().into());
            for index in 0..class_ref.number_of_all_members as usize {
                self.validate_object_ref(object, class_object.offset_to_member(index).into());
                if index >= class_ref.number_of_instance_members as usize {
                    self.validate_ref(
                        object,
                        class_object
                            .static_member(index - class_ref.number_of_instance_members as usize),
                    );
                }
            }

            if let Some(metatable) = class_object.metatable() {
                self.validate_object_ref(object, metatable.into());
            }

            if let Some(instance_metatable) = class_object.instance_metatable() {
                self.validate_object_ref(object, instance_metatable.into());
            }
        }
    }

    /// `validateobject`
    unsafe fn validate_instance(&self, object_instance: Object) {
        unsafe {
            let object_ref = object_instance.as_ptr().as_ref().unwrap_unchecked();
            let object: GcObject = object_instance.into();
            self.validate_object_ref(object, object_instance.class().into());

            for index in 0..object_ref.number_of_members as usize {
                self.validate_ref(object, object_instance.member(index));
            }
        }
    }

    /// `validateobj`
    pub(in crate::gc) unsafe fn validate_object(&self, object: GcObject) {
        unsafe {
            if self.is_dead(object) {
                debug_assert_eq!(self.gc_state(), GCS_SWEEP);
                return;
            }

            match object.as_ptr().as_ref().unwrap_unchecked().tt as i32 {
                x if x == types::LUA_TSTRING || x == types::LUA_TBUFFER => {}
                x if x == types::LUA_TTABLE => self.validate_table(object.to_table()),
                x if x == types::LUA_TFUNCTION => self.validate_closure(object.to_closure()),
                x if x == types::LUA_TUSERDATA => {
                    let userdata = object.to_userdata();
                    if let Some(metatable) = userdata.metatable() {
                        self.validate_object_ref(object, metatable.into());
                    }
                    let thread = self.main_thread();
                    if let Some(userdata) = thread.typed_userdata(userdata) {
                        self.validate_ref(object, thread.typed_userdata_value(userdata));
                    }
                }
                x if x == types::LUA_TTHREAD => {
                    let thread = object.to_state();
                    self.validate_stack(&thread);
                }
                x if x == types::LUA_TPROTO => self.validate_proto(object.to_proto()),
                x if x == types::LUA_TUPVALUE => {
                    self.validate_ref(object, object.to_upvalue().value())
                }
                x if x == types::LUA_TCLASS => self.validate_class(object.to_class()),
                x if x == types::LUA_TOBJECT => self.validate_instance(object.to_object()),
                other => unreachable!("unexpected object type in gc validation: {}", other),
            }
        }
    }

    /// `validategraylist`
    pub(in crate::gc) unsafe fn validate_gray_list(&self, mut object: Option<GcObject>) {
        unsafe {
            if !self.keep_invariant() {
                return;
            }

            while let Some(current) = object {
                debug_assert!(current.is_gray());
                object = match current.as_ptr().as_ref().unwrap_unchecked().tt as i32 {
                    x if x == types::LUA_TFUNCTION => current.to_closure().gc_list(),
                    x if x == types::LUA_TTABLE => current.to_table().gc_list(),
                    x if x == types::LUA_TTHREAD => current.to_state().gc_list(),
                    x if x == types::LUA_TPROTO => current.to_proto().gc_list(),
                    x if x == types::LUA_TCLASS => current.to_class().gc_list(),
                    x if x == types::LUA_TOBJECT => current.to_object().gc_list(),
                    other => unreachable!("unknown object in gray list: {}", other),
                };
            }
        }
    }
}

/// `validategco`
pub(in crate::gc) unsafe fn validate_gco_visitor(
    context: *mut (),
    _page: LuaPage,
    gco: GcObject,
) -> bool {
    unsafe {
        let thread = Thread::from_raw(NonNull::new_unchecked(context.cast()));
        let global = thread.global();
        global.validate_object(gco);
        false
    }
}
use core::ptr::NonNull;