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 {
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()));
}
}
}
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);
}
}
}
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));
}
}
}
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());
}
}
}
}
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));
}
}
}
}
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();
}
}
}
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());
}
}
}
}
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());
}
}
}
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));
}
}
}
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),
}
}
}
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),
};
}
}
}
}
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;