use core::ptr::NonNull;
use luau_common::BString;
use super::{append_decimal, append_dump_refs, append_dump_string_data, append_ref};
use crate::Table;
use crate::buffer::Buffer;
use crate::call::ThreadStack;
use crate::function::{Closure, Proto, UpVal};
use crate::gc::GcObject;
use crate::handle::RawHandle;
use crate::memory::LuaPage;
use crate::state::{ThreadLifecycle, ThreadState};
use crate::string::TString;
use crate::thread::Thread;
use crate::types;
use crate::userdata::{TypedUserdataAccess, Userdata};
use crate::value::TValue;
use crate::{Class, Object};
unsafe fn dump_string(output: &mut BString, string: TString) {
let string_ref = unsafe { string.as_ptr().as_ref().unwrap_unchecked() };
output.extend_from_slice(b"{\"type\":\"string\",\"cat\":");
append_decimal(output, string_ref.memcat as usize);
output.extend_from_slice(b",\"size\":");
append_decimal(output, TString::size_string(string_ref.len as usize));
output.extend_from_slice(b",\"data\":\"");
append_dump_string_data(output, unsafe { string.as_bytes() });
output.extend_from_slice(b"\"}");
}
unsafe fn dump_table(output: &mut BString, table: Table) {
unsafe {
let table_ref = table.as_ptr().as_ref().unwrap_unchecked();
output.extend_from_slice(b"{\"type\":\"table\",\"cat\":");
append_decimal(output, table_ref.memcat as usize);
output.extend_from_slice(b",\"size\":");
append_decimal(output, table.allocation_size());
if table_ref.node != Table::dummy_node_ptr() {
output.extend_from_slice(b",\"pairs\":[");
let mut first = true;
for index in 0..table.node_count() {
let node = table.node(index as i32);
if node.value_unchecked().is_nil()
|| (!node.key().is_collectable() && !node.value_unchecked().is_collectable())
{
continue;
}
if !first {
output.push(b',');
}
first = false;
if node.key().is_collectable() {
append_ref(output, node.key().gc_value());
} else {
output.extend_from_slice(b"null");
}
output.push(b',');
if node.value_unchecked().is_collectable() {
append_ref(output, node.value_unchecked().gc_value());
} else {
output.extend_from_slice(b"null");
}
}
output.push(b']');
}
if table_ref.size_array > 0 {
output.extend_from_slice(b",\"array\":[");
append_dump_refs(output, table.array_cursor(), table_ref.size_array as usize);
output.push(b']');
}
if let Some(metatable) = table.metatable() {
output.extend_from_slice(b",\"metatable\":");
append_ref(output, metatable.into());
}
output.push(b'}');
}
}
unsafe fn dump_closure(output: &mut BString, closure: Closure) {
unsafe {
let closure_ref = closure.as_ptr().as_ref().unwrap_unchecked();
output.extend_from_slice(b"{\"type\":\"function\",\"cat\":");
append_decimal(output, closure_ref.memcat as usize);
output.extend_from_slice(b",\"size\":");
append_decimal(output, closure.size());
output.extend_from_slice(b",\"env\":");
append_ref(output, closure.env().into());
if closure.is_native() {
if let Some(debug_name) = closure.native_debug_name() {
output.extend_from_slice(b",\"name\":\"");
output.extend_from_slice(debug_name.as_bytes());
output.push(b'"');
}
if closure_ref.n_upvalues > 0 {
output.extend_from_slice(b",\"upvalues\":[");
append_dump_refs(
output,
closure.native_upvalues(),
closure_ref.n_upvalues as usize,
);
output.push(b']');
}
} else {
let proto = closure.proto().unwrap_unchecked();
if let Some(debug_name) = proto.debug_name() {
output.extend_from_slice(b",\"name\":\"");
output.extend_from_slice(debug_name.as_bytes());
output.push(b'"');
}
output.extend_from_slice(b",\"proto\":");
append_ref(output, proto.into());
if closure_ref.n_upvalues > 0 {
output.extend_from_slice(b",\"upvalues\":[");
append_dump_refs(
output,
closure.lua_upvalue_refs(),
closure_ref.n_upvalues as usize,
);
output.push(b']');
}
}
}
output.push(b'}');
}
unsafe fn dump_userdata(output: &mut BString, thread: &Thread, userdata: Userdata) {
unsafe {
let userdata_ref = userdata.as_ptr().as_ref().unwrap_unchecked();
output.extend_from_slice(b"{\"type\":\"userdata\",\"cat\":");
append_decimal(output, userdata_ref.memcat as usize);
output.extend_from_slice(b",\"size\":");
append_decimal(output, userdata.allocation_size());
output.extend_from_slice(b",\"tag\":");
append_decimal(output, userdata_ref.tag as usize);
if let Some(metatable) = userdata.metatable() {
output.extend_from_slice(b",\"metatable\":");
append_ref(output, metatable.into());
}
if let Some(userdata) = thread.typed_userdata(userdata) {
let value = thread.typed_userdata_value(userdata);
if value.is_collectable() {
output.extend_from_slice(b",\"uservalue\":");
append_ref(output, value.gc_value());
}
}
}
output.push(b'}');
}
unsafe fn dump_thread(output: &mut BString, thread: &Thread) {
unsafe {
let thread_ref = thread.as_ptr().as_ref().unwrap_unchecked();
output.extend_from_slice(b"{\"type\":\"thread\",\"cat\":");
append_decimal(output, thread_ref.memcat as usize);
output.extend_from_slice(b",\"size\":");
append_decimal(output, thread.allocation_size());
output.extend_from_slice(b",\"env\":");
append_ref(output, thread.globals().into());
let mut top_closure = None;
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 function = call_info_cursor
.call_info_unchecked()
.function()
.value_unchecked();
if function.is_function() {
top_closure = Some(function.closure_value());
break;
}
call_info_cursor = call_info_cursor.add(1);
}
if let Some(closure) = top_closure
&& closure.is_lua()
{
let proto = closure.proto().unwrap_unchecked();
if let Some(source) = proto.source() {
output.extend_from_slice(b",\"source\":\"");
append_dump_string_data(output, source.as_bytes());
output.extend_from_slice(b"\",\"line\":");
append_decimal(
output,
proto.as_ptr().as_ref().unwrap_unchecked().line_defined,
);
}
}
let stack_start = thread.restore_stack(0);
let stack_top = thread.stack_top();
if stack_top > stack_start {
output.extend_from_slice(b",\"stack\":[");
append_dump_refs(
output,
stack_start,
stack_top.offset_from(stack_start) as usize,
);
output.push(b']');
output.extend_from_slice(b",\"stacknames\":[");
let mut call_info_cursor = thread.base_call_info_cursor();
let current_call_info_cursor = thread.current_call_info_cursor();
let mut first = true;
let mut slot = stack_start;
while slot < stack_top {
let value = slot.value_unchecked();
if !value.is_collectable() {
slot = slot.add(1);
continue;
}
while call_info_cursor < current_call_info_cursor
&& slot >= call_info_cursor.add(1).call_info_unchecked().function()
{
call_info_cursor = call_info_cursor.add(1);
}
if !first {
output.push(b',');
}
first = false;
let call_info = call_info_cursor.call_info_unchecked();
if slot == call_info.function() {
let closure = call_info.function_closure();
if closure.is_native() {
output.extend_from_slice(b"\"frame:");
if let Some(debug_name) = closure.native_debug_name() {
output.extend_from_slice(debug_name.as_bytes());
} else {
output.extend_from_slice(b"[C]");
}
output.push(b'"');
} else {
let proto = closure.proto().unwrap_unchecked();
output.extend_from_slice(b"\"frame:");
if let Some(source) = proto.source() {
append_dump_string_data(output, source.as_bytes());
}
output.push(b':');
append_decimal(
output,
proto.as_ptr().as_ref().unwrap_unchecked().line_defined,
);
output.push(b':');
if let Some(debug_name) = proto.debug_name() {
output.extend_from_slice(debug_name.as_bytes());
}
output.push(b'"');
}
} else if call_info.is_lua() {
let closure = call_info.function_closure();
let proto = closure.proto().unwrap_unchecked();
let pc = proto.pc_rel(call_info.saved_pc());
let local_reg = slot.offset_from(call_info.base()) as i32;
if let Some(local) = proto.find_local(local_reg, pc)
&& let Some(var_name) = local.name()
{
output.push(b'"');
output.extend_from_slice(var_name.as_bytes());
output.push(b'"');
} else {
output.extend_from_slice(b"null");
}
} else {
output.extend_from_slice(b"null");
}
slot = slot.add(1);
}
output.push(b']');
}
output.push(b'}');
}
}
unsafe fn dump_buffer(output: &mut BString, buffer: Buffer) {
let buffer_ref = unsafe { buffer.as_ptr().as_ref().unwrap_unchecked() };
output.extend_from_slice(b"{\"type\":\"buffer\",\"cat\":");
append_decimal(output, buffer_ref.memcat as usize);
output.extend_from_slice(b",\"size\":");
append_decimal(output, Buffer::size_buffer(buffer_ref.len as usize));
output.push(b'}');
}
unsafe fn dump_proto(output: &mut BString, proto: Proto) {
unsafe {
let proto_ref = proto.as_ptr().as_ref().unwrap_unchecked();
output.extend_from_slice(b"{\"type\":\"proto\",\"cat\":");
append_decimal(output, proto_ref.memcat as usize);
output.extend_from_slice(b",\"size\":");
append_decimal(output, proto.size());
if let Some(source) = proto.source() {
output.extend_from_slice(b",\"source\":\"");
append_dump_string_data(output, source.as_bytes());
output.extend_from_slice(b"\",\"line\":");
let line = if proto_ref.abs_line_info.is_null() {
0
} else {
*proto_ref.abs_line_info
};
append_decimal(output, line);
}
if proto_ref.size_k > 0 {
output.extend_from_slice(b",\"constants\":[");
append_dump_refs(output, proto.constants(), proto_ref.size_k as usize);
output.push(b']');
}
if proto_ref.size_p > 0 {
output.extend_from_slice(b",\"protos\":[");
for index in 0..proto_ref.size_p as usize {
if index != 0 {
output.push(b',');
}
append_ref(output, proto.child_proto(index).unwrap_unchecked().into());
}
output.push(b']');
}
output.push(b'}');
}
}
unsafe fn dump_upvalue(output: &mut BString, upvalue: UpVal) {
unsafe {
let upvalue_ref = upvalue.as_ptr().as_ref().unwrap_unchecked();
output.extend_from_slice(b"{\"type\":\"upvalue\",\"cat\":");
append_decimal(output, upvalue_ref.memcat as usize);
output.extend_from_slice(b",\"size\":");
append_decimal(output, UpVal::allocation_size());
output.extend_from_slice(b",\"open\":");
output.extend_from_slice(if upvalue.is_open() { b"true" } else { b"false" });
let value = TValue::from_raw(NonNull::new_unchecked(upvalue_ref.value));
if value.is_collectable() {
output.extend_from_slice(b",\"object\":");
append_ref(output, value.gc_value());
}
}
output.push(b'}');
}
unsafe fn dump_class(output: &mut BString, class_object: Class) {
unsafe {
let class_ref = class_object.as_ptr().as_ref().unwrap_unchecked();
output.extend_from_slice(b"{\"type\":\"class\",\"cat\":");
append_decimal(output, class_ref.memcat as usize);
output.extend_from_slice(b",\"size\":");
append_decimal(output, class_object.allocation_size());
output.extend_from_slice(b",\"name\":");
append_dump_string_data(output, class_object.name().as_bytes());
output.extend_from_slice(b",\"membernames\":[");
for index in 0..class_ref.number_of_all_members as usize {
if index != 0 {
output.push(b',');
}
append_ref(output, class_object.offset_to_member(index).into());
}
output.extend_from_slice(b"],\"staticmembers\":[");
let mut first = true;
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 = class_object.static_member(index);
if value.is_collectable() {
if !first {
output.push(b',');
}
first = false;
append_ref(output, value.gc_value());
}
}
output.extend_from_slice(b"],\"metatable\":");
if let Some(metatable) = class_object.metatable() {
append_ref(output, metatable.into());
} else {
output.extend_from_slice(b"null");
}
output.extend_from_slice(b",\"instancemetatable\":");
if let Some(instance_metatable) = class_object.instance_metatable() {
append_ref(output, instance_metatable.into());
} else {
output.extend_from_slice(b"null");
}
output.extend_from_slice(b",\"memberstooffset\":");
append_ref(output, class_object.members_to_offset().into());
output.push(b'}');
}
}
unsafe fn dump_instance(output: &mut BString, object_instance: Object) {
unsafe {
let object_ref = object_instance.as_ptr().as_ref().unwrap_unchecked();
output.extend_from_slice(b"{\"type\":\"object\",\"cat\":");
append_decimal(output, object_ref.memcat as usize);
output.extend_from_slice(b",\"size\":");
append_decimal(output, object_instance.allocation_size());
output.extend_from_slice(b",\"class\":");
append_ref(output, object_instance.class().into());
output.extend_from_slice(b",\"members\":[");
let mut first = true;
for index in 0..object_ref.number_of_members as usize {
let value = object_instance.member(index);
if value.is_collectable() {
if !first {
output.push(b',');
}
first = false;
append_ref(output, value.gc_value());
}
}
}
output.extend_from_slice(b"]}");
}
unsafe fn dump_object(output: &mut BString, thread: &Thread, object: GcObject) {
unsafe {
match object.as_ptr().as_ref().unwrap_unchecked().tt as i32 {
x if x == types::LUA_TSTRING => dump_string(output, object.to_string()),
x if x == types::LUA_TTABLE => dump_table(output, object.to_table()),
x if x == types::LUA_TFUNCTION => dump_closure(output, object.to_closure()),
x if x == types::LUA_TUSERDATA => dump_userdata(output, thread, object.to_userdata()),
x if x == types::LUA_TTHREAD => {
let thread = object.to_state();
dump_thread(output, &thread);
}
x if x == types::LUA_TBUFFER => dump_buffer(output, object.to_buffer()),
x if x == types::LUA_TCLASS => dump_class(output, object.to_class()),
x if x == types::LUA_TOBJECT => dump_instance(output, object.to_object()),
x if x == types::LUA_TPROTO => dump_proto(output, object.to_proto()),
x if x == types::LUA_TUPVALUE => dump_upvalue(output, object.to_upvalue()),
other => unreachable!("unexpected object type in gc dump: {}", other),
}
}
}
pub(in crate::gc) unsafe fn dump_gco(output: &mut BString, thread: &Thread, object: GcObject) {
unsafe {
append_ref(output, object);
output.push(b':');
dump_object(output, thread, object);
}
output.extend_from_slice(b",\n");
}
pub(in crate::gc) struct DumpContext<'a> {
pub(in crate::gc) thread: &'a Thread,
pub(in crate::gc) output: &'a mut BString,
}
pub(in crate::gc) unsafe fn dump_gco_visitor(
context: *mut (),
_page: LuaPage,
gco: GcObject,
) -> bool {
let context = unsafe { &mut *context.cast::<DumpContext<'_>>() };
unsafe { dump_gco(context.output, context.thread, gco) };
false
}