use core::ptr::NonNull;
use luau_common::{BStr, BString, ByteSlice};
use super::append_decimal;
use crate::Table;
use crate::buffer::Buffer;
use crate::call::ThreadStack;
use crate::function::{Closure, Proto, UpVal};
use crate::gc::GcObject;
use crate::gc::{GcHeapEdge, GcHeapNode};
use crate::handle::RawHandle;
use crate::memory::LuaPage;
use crate::metamethod::TmEvent;
use crate::number::{LUAI_MAXNUM2STR, num_to_str};
use crate::state::{ThreadLifecycle, ThreadState};
use crate::string::{LuaString, TString};
use crate::thread::Thread;
use crate::types;
use crate::userdata::{TypedUserdataAccess, Userdata};
use crate::value::TValueCursor;
use crate::{Class, Object};
impl GcObject {
unsafe fn enum_pointer(&self) -> *mut () {
if unsafe { self.as_ptr().as_ref().unwrap_unchecked().tt } == types::LUA_TUSERDATA as u8 {
unsafe { self.to_userdata().data_mut_ptr().cast() }
} else {
self.as_ptr().cast()
}
}
}
pub(in crate::gc) struct HeapEnumContext<'thread> {
pub(in crate::gc) thread: &'thread Thread,
pub(in crate::gc) context: *mut (),
pub(in crate::gc) node: GcHeapNode,
pub(in crate::gc) edge: GcHeapEdge,
}
impl HeapEnumContext<'_> {
fn node(&mut self, object: GcObject, size: usize, name: Option<&BStr>) {
unsafe {
let object_ref = object.as_ptr().as_ref().unwrap_unchecked();
(self.node)(
self.context,
object.enum_pointer(),
object_ref.tt,
object_ref.memcat,
size,
name,
);
}
}
fn edge(&mut self, from: GcObject, to: GcObject, name: &BStr) {
unsafe {
(self.edge)(self.context, from.enum_pointer(), to.enum_pointer(), name);
}
}
fn edges(&mut self, from: GcObject, data: TValueCursor, size: usize, name: &BStr) {
for index in 0..size {
let value = unsafe { data.add(index) };
if unsafe { value.value_unchecked() }.is_collectable() {
self.edge(from, unsafe { value.value_unchecked().gc_value() }, name);
}
}
}
fn enum_string(&mut self, string: TString) {
self.node(
string.into(),
TString::size_string(unsafe {
string.as_ptr().as_ref().unwrap_unchecked().len as usize
}),
None,
);
}
fn enum_table(&mut self, table: Table) {
let table_ref = unsafe { table.as_ptr().as_ref().unwrap_unchecked() };
let registry = unsafe { self.thread.global().registry() };
let registry_name = if registry.is_table() && table == registry.table_value() {
Some(b"registry".as_bstr())
} else {
None
};
self.node(
table.into(),
unsafe { table.allocation_size() },
registry_name,
);
if table_ref.node != Table::dummy_node_ptr() {
let mut weak_key = false;
let mut weak_value = false;
if let Some(metatable) = NonNull::new(table_ref.metatable) {
let event_name = unsafe { self.thread.global().tm_name(TmEvent::Mode as usize) };
if let Some(mode) =
unsafe { Table::from_raw(metatable).get_tm(TmEvent::Mode, event_name) }
&& mode.is_string()
{
let mode_string = mode.string_value();
let bytes = unsafe { mode_string.as_bytes() };
weak_key = bytes.contains(&b'k');
weak_value = bytes.contains(&b'v');
}
}
for index in 0..unsafe { table.node_count() } {
let node = unsafe { table.node(index as i32) };
if node.value_unchecked().is_nil()
|| (!node.key().is_collectable() && !node.value_unchecked().is_collectable())
{
continue;
}
if !weak_key && node.key().is_collectable() {
self.edge(table.into(), node.key().gc_value(), b"[key]".as_bstr());
}
if !weak_value && node.value_unchecked().is_collectable() {
let value = node.value_unchecked().gc_value();
if node.key().tt() == types::LUA_TSTRING {
let key = unsafe { node.key().gc_value().to_string() };
self.edge(table.into(), value, unsafe { key.as_bytes().as_bstr() });
} else if node.key().tt() == types::LUA_TNUMBER {
let mut buf = [0u8; LUAI_MAXNUM2STR];
let len = num_to_str(&mut buf, node.key().number_value());
self.edge(table.into(), value, buf[..len].as_bstr());
} else {
let mut name = BString::from(b"[".as_slice());
let type_name =
unsafe { self.thread.global().type_name(node.key().tt() as usize) };
name.extend_from_slice(unsafe { type_name.as_bytes() });
name.push(b']');
self.edge(table.into(), value, name.as_bstr());
}
}
}
}
if table_ref.size_array > 0 {
self.edges(
table.into(),
unsafe { table.array_cursor() },
table_ref.size_array as usize,
b"array".as_bstr(),
);
}
if let Some(metatable) = unsafe { table.metatable() } {
self.edge(table.into(), metatable.into(), b"metatable".as_bstr());
}
}
fn enum_closure(&mut self, closure: Closure) {
let closure_ref = unsafe { closure.as_ptr().as_ref().unwrap_unchecked() };
if unsafe { closure.is_native() } {
let debug_name = unsafe { closure.native_debug_name() };
let debug_name = debug_name.as_ref().map(LuaString::as_bstr);
self.node(closure.into(), unsafe { closure.size() }, debug_name);
} else {
let proto = unsafe { closure.proto().unwrap_unchecked() };
let mut name = BString::default();
if let Some(debug_name) = unsafe { proto.debug_name() } {
name.extend_from_slice(unsafe { debug_name.as_bytes() });
} else {
name.extend_from_slice(b"unnamed");
}
name.push(b':');
append_decimal(&mut name, unsafe {
proto.as_ptr().as_ref().unwrap_unchecked().line_defined
});
if let Some(source) = unsafe { proto.source() } {
name.push(b' ');
name.extend_from_slice(unsafe { source.as_bytes() });
}
self.node(
closure.into(),
unsafe { closure.size() },
Some(name.as_bstr()),
);
}
self.edge(
closure.into(),
unsafe { closure.env().into() },
b"env".as_bstr(),
);
if unsafe { closure.is_native() } {
if closure_ref.n_upvalues > 0 {
self.edges(
closure.into(),
unsafe { closure.native_upvalues() },
closure_ref.n_upvalues as usize,
b"upvalue".as_bstr(),
);
}
} else {
let proto = unsafe { closure.proto().unwrap_unchecked() };
self.edge(closure.into(), proto.into(), b"proto".as_bstr());
if closure_ref.n_upvalues > 0 {
self.edges(
closure.into(),
unsafe { closure.lua_upvalue_refs() },
closure_ref.n_upvalues as usize,
b"upvalue".as_bstr(),
);
}
}
}
fn enum_userdata(&mut self, userdata: Userdata) {
let mut name_string = None;
if let Some(metatable) = unsafe { userdata.metatable() } {
let metatable_ref = unsafe { metatable.as_ptr().as_ref().unwrap_unchecked() };
if metatable_ref.node != Table::dummy_node_ptr() {
for index in 0..unsafe { metatable.node_count() } {
let node = unsafe { metatable.node(index as i32) };
if node.key().tt() == types::LUA_TSTRING && node.value_unchecked().is_string() {
let key_string = unsafe { node.key().gc_value().to_string() };
if unsafe { key_string.as_bytes() } == b"__type" {
name_string = Some(node.value_unchecked().string_value());
break;
}
}
}
}
}
let name = name_string
.as_ref()
.map(|string| unsafe { string.as_bytes().as_bstr() });
self.node(userdata.into(), unsafe { userdata.allocation_size() }, name);
if let Some(metatable) = unsafe { userdata.metatable() } {
self.edge(userdata.into(), metatable.into(), b"metatable".as_bstr());
}
if let Some(typed) = unsafe { self.thread.typed_userdata(userdata) } {
let value = unsafe { self.thread.typed_userdata_value(typed) };
if value.is_collectable() {
self.edge(userdata.into(), value.gc_value(), b"value".as_bstr());
}
}
}
fn enum_thread(&mut self, thread: &Thread) {
let mut top_closure = None;
let mut call_info_cursor = unsafe { thread.base_call_info_cursor() };
let current_call_info_cursor = unsafe { thread.current_call_info_cursor() };
while call_info_cursor <= current_call_info_cursor {
let function = unsafe {
call_info_cursor
.call_info_unchecked()
.function()
.value_unchecked()
};
if function.is_function() {
top_closure = Some(function.closure_value());
break;
}
call_info_cursor = unsafe { call_info_cursor.add(1) };
}
let mut name: Option<BString> = None;
if let Some(closure) = top_closure
&& unsafe { closure.is_lua() }
{
let proto = unsafe { closure.proto().unwrap_unchecked() };
if let Some(source) = unsafe { proto.source() } {
let mut value = BString::from(b"thread at ".as_slice());
if let Some(debug_name) = unsafe { proto.debug_name() } {
value.extend_from_slice(unsafe { debug_name.as_bytes() });
} else {
value.extend_from_slice(b"unnamed");
}
value.push(b':');
append_decimal(&mut value, unsafe {
proto.as_ptr().as_ref().unwrap_unchecked().line_defined
});
value.push(b' ');
value.extend_from_slice(unsafe { source.as_bytes() });
name = Some(value);
}
}
self.node(
thread.into(),
unsafe { thread.allocation_size() },
name.as_ref().map(|name| name.as_bstr()),
);
self.edge(
thread.into(),
unsafe { thread.globals().into() },
b"globals".as_bstr(),
);
let stack_start = unsafe { thread.restore_stack(0) };
let stack_top = unsafe { thread.stack_top() };
if stack_top > stack_start {
self.edges(
thread.into(),
stack_start,
unsafe { stack_top.offset_from(stack_start) as usize },
b"stack".as_bstr(),
);
}
}
fn enum_buffer(&mut self, buffer: Buffer) {
self.node(
buffer.into(),
Buffer::size_buffer(unsafe {
buffer.as_ptr().as_ref().unwrap_unchecked().len as usize
}),
None,
);
}
fn enum_proto(&mut self, proto: Proto) {
let proto_ref = unsafe { proto.as_ptr().as_ref().unwrap_unchecked() };
if !proto_ref.exec_data.is_null()
&& let Some(get_memory_size) = unsafe { self.thread.global().execution_memory_size() }
{
let native_size = unsafe { get_memory_size(self.thread, proto) };
(self.node)(
self.context,
proto_ref.exec_data.cast(),
u8::MAX,
proto_ref.memcat,
native_size,
None,
);
(self.edge)(
self.context,
unsafe { GcObject::from(proto).enum_pointer() },
proto_ref.exec_data.cast(),
b"[native]".as_bstr(),
);
}
let mut name = BString::from(b"proto ".as_slice());
if let Some(debug_name) = unsafe { proto.debug_name() } {
name.extend_from_slice(unsafe { debug_name.as_bytes() });
} else {
name.extend_from_slice(b"unnamed");
}
name.push(b':');
append_decimal(&mut name, proto_ref.line_defined);
if let Some(source) = unsafe { proto.source() } {
name.push(b' ');
name.extend_from_slice(unsafe { source.as_bytes() });
}
self.node(proto.into(), unsafe { proto.size() }, Some(name.as_bstr()));
if proto_ref.size_k > 0 {
self.edges(
proto.into(),
unsafe { proto.constants() },
proto_ref.size_k as usize,
b"constants".as_bstr(),
);
}
for index in 0..proto_ref.size_p as usize {
self.edge(
proto.into(),
unsafe { proto.child_proto(index).unwrap_unchecked().into() },
b"protos".as_bstr(),
);
}
}
fn enum_upvalue(&mut self, upvalue: UpVal) {
self.node(upvalue.into(), UpVal::allocation_size(), None);
let value = unsafe { upvalue.value() };
if value.is_collectable() {
self.edge(upvalue.into(), value.gc_value(), b"value".as_bstr());
}
}
fn enum_class(&mut self, class_object: Class) {
let class_ref = unsafe { class_object.as_ptr().as_ref().unwrap_unchecked() };
let mut name = BString::from(b"class object ".as_slice());
let class_name = unsafe { class_object.name() };
name.extend_from_slice(unsafe { class_name.as_bytes() });
self.node(
class_object.into(),
unsafe { class_object.allocation_size() },
Some(name.as_bstr()),
);
self.edge(
class_object.into(),
unsafe { class_object.name().into() },
b"classname".as_bstr(),
);
self.edge(
class_object.into(),
unsafe { class_object.members_to_offset().into() },
b"classoffsets".as_bstr(),
);
let static_member_count =
(class_ref.number_of_all_members - class_ref.number_of_instance_members) as usize;
for index in 0..static_member_count {
let value = unsafe { class_object.static_member(index) };
if !value.is_collectable() {
continue;
}
let mut member_name = BString::default();
let member = unsafe {
class_object.offset_to_member(index + class_ref.number_of_instance_members as usize)
};
member_name.extend_from_slice(unsafe { member.as_bytes() });
self.edge(class_object.into(), value.gc_value(), member_name.as_bstr());
}
for index in 0..class_ref.number_of_all_members as usize {
self.edge(
class_object.into(),
unsafe { class_object.offset_to_member(index).into() },
b"membername".as_bstr(),
);
}
if let Some(metatable) = unsafe { class_object.metatable() } {
self.edge(
class_object.into(),
metatable.into(),
b"metatable".as_bstr(),
);
}
}
fn enum_object_instance(&mut self, object_instance: Object) {
let class_object = unsafe { object_instance.class() };
let class_ref = unsafe { class_object.as_ptr().as_ref().unwrap_unchecked() };
let mut name = BString::from(b"object ".as_slice());
let class_name = unsafe { class_object.name() };
name.extend_from_slice(unsafe { class_name.as_bytes() });
self.node(
object_instance.into(),
unsafe { object_instance.allocation_size() },
Some(name.as_bstr()),
);
for index in 0..class_ref.number_of_instance_members as usize {
let value = unsafe { object_instance.member(index) };
if !value.is_collectable() {
continue;
}
let mut member_name = BString::default();
let member = unsafe { class_object.offset_to_member(index) };
member_name.extend_from_slice(unsafe { member.as_bytes() });
self.edge(
object_instance.into(),
value.gc_value(),
member_name.as_bstr(),
);
}
}
pub(in crate::gc) fn enum_object(&mut self, object: GcObject) {
match unsafe { object.as_ptr().as_ref().unwrap_unchecked().tt } as i32 {
x if x == types::LUA_TSTRING => self.enum_string(unsafe { object.to_string() }),
x if x == types::LUA_TTABLE => self.enum_table(unsafe { object.to_table() }),
x if x == types::LUA_TFUNCTION => self.enum_closure(unsafe { object.to_closure() }),
x if x == types::LUA_TUSERDATA => self.enum_userdata(unsafe { object.to_userdata() }),
x if x == types::LUA_TTHREAD => {
let thread = unsafe { object.to_state() };
self.enum_thread(&thread);
}
x if x == types::LUA_TBUFFER => self.enum_buffer(unsafe { object.to_buffer() }),
x if x == types::LUA_TCLASS => self.enum_class(unsafe { object.to_class() }),
x if x == types::LUA_TOBJECT => {
self.enum_object_instance(unsafe { object.to_object() })
}
x if x == types::LUA_TPROTO => self.enum_proto(unsafe { object.to_proto() }),
x if x == types::LUA_TUPVALUE => self.enum_upvalue(unsafe { object.to_upvalue() }),
other => unreachable!("unexpected object type in gc enum: {}", other),
}
}
}
pub(in crate::gc) unsafe fn enum_heap_gco_visitor(
context: *mut (),
_page: LuaPage,
gco: GcObject,
) -> bool {
let context = unsafe { &mut *context.cast::<HeapEnumContext>() };
context.enum_object(gco);
false
}