1use core::mem::size_of;
2use core::ptr::{self, NonNull};
3
4use luau_bytecode::model::Instruction;
5use luau_bytecode::opcodes::Opcode;
6use luau_common::{BStr, BString, ByteSlice};
7
8use super::stack::RawStackAccess;
9use super::{LuaStringBuilder, LuaStringBuilderStorage, Thread};
10use crate::VmErrorResult;
11use crate::debug::{
12 DebugRuntime, LuaCounterFunction, LuaCounterValue, LuaCoverage, LuaDebug, chunk_id, current_pc,
13 lua_proto,
14};
15use crate::function::{Closure, Proto};
16use crate::gc::GcBarrier;
17use crate::handle::RawHandle;
18use crate::memory::MemoryRuntime;
19use crate::state::{CallInfo, RawCallInfo, ThreadState};
20use crate::string::LuaString;
21use crate::value::TValue;
22use crate::vm::VmCallFrame;
23
24fn get_func_name(closure: Closure) -> Option<LuaString> {
26 if unsafe { closure.is_native() } {
27 unsafe { closure.native_debug_name() }
28 } else {
29 let proto = unsafe { closure.proto().unwrap_unchecked() };
30 unsafe { proto.debug_name().map(LuaString::from_interned) }
31 }
32}
33
34fn aux_get_info(
36 what: &[u8],
37 ar: &mut LuaDebug,
38 closure: Closure,
39 call_info: Option<*mut RawCallInfo>,
40) -> bool {
41 let mut push_function = false;
42
43 for byte in what {
44 match *byte {
45 b's' => {
46 if unsafe { closure.is_native() } {
47 ar.source = LuaString::from_static(b"=[C]".as_bstr());
48 ar.what = LuaString::from_static(b"C".as_bstr());
49 ar.linedefined = -1;
50 ar.set_short_src(b"[C]");
51 } else {
52 let proto = unsafe { closure.proto().unwrap_unchecked() };
53 let source = unsafe { proto.source().unwrap_unchecked() };
54 ar.source = LuaString::from_interned(source);
55 ar.what = LuaString::from_static(b"Lua".as_bstr());
56 ar.linedefined =
57 unsafe { proto.as_ptr().as_ref().unwrap_unchecked().line_defined };
58
59 let mut short_src = [0u8; crate::debug::LUA_ID_SIZE];
60 let short_src = chunk_id(&mut short_src, unsafe { source.as_bytes() });
61 ar.set_short_src(short_src);
62 }
63 }
64 b'l' => {
65 ar.currentline = if let Some(call_info) = call_info {
66 let call_info =
67 unsafe { CallInfo::from_raw(NonNull::new_unchecked(call_info)) };
68 if let Some(proto) = lua_proto(call_info) {
69 unsafe { proto.get_line(current_pc(call_info, proto)) }
70 } else {
71 -1
72 }
73 } else if unsafe { closure.is_native() } {
74 -1
75 } else {
76 unsafe {
77 closure
78 .proto()
79 .unwrap_unchecked()
80 .as_ptr()
81 .as_ref()
82 .unwrap_unchecked()
83 .line_defined
84 }
85 };
86 }
87 b'u' => {
88 ar.nupvals = unsafe { closure.as_ptr().as_ref().unwrap_unchecked().n_upvalues };
89 }
90 b'a' => {
91 if unsafe { closure.is_native() } {
92 ar.is_vararg = true;
93 ar.nparams = 0;
94 } else {
95 let proto = unsafe { closure.proto().unwrap_unchecked() };
96 let proto = unsafe { proto.as_ptr().as_ref().unwrap_unchecked() };
97 ar.is_vararg = proto.is_vararg != 0;
98 ar.nparams = proto.num_params;
99 }
100 }
101 b'n' => {
102 ar.name = get_func_name(closure);
103 }
104 b'f' => {
105 push_function = true;
106 }
107 _ => {}
108 }
109 }
110
111 push_function
112}
113
114fn aux_upvalue(function: TValue, upvalue_index: i32) -> Option<(LuaString, TValue)> {
116 if !function.is_function() {
117 return None;
118 }
119
120 let closure = function.closure_value();
121 if unsafe { closure.is_native() } {
122 if !(1..=i32::from(unsafe { closure.as_ptr().as_ref().unwrap_unchecked().n_upvalues }))
123 .contains(&upvalue_index)
124 {
125 return None;
126 }
127
128 let value = unsafe { closure.native_upvalue((upvalue_index - 1) as usize) };
129 return Some((LuaString::from_static(b"".as_bstr()), value));
130 }
131
132 let proto = unsafe { closure.proto().unwrap_unchecked() };
133 if !(1..=i32::from(unsafe { proto.as_ptr().as_ref().unwrap_unchecked().n_ups }))
134 .contains(&upvalue_index)
135 {
136 return None;
137 }
138
139 let upref = unsafe { closure.lua_upvalue_ref((upvalue_index - 1) as usize) };
140 let value = if upref.is_upvalue() {
141 unsafe { upref.upvalue_value().value() }
142 } else {
143 upref
144 };
145
146 let name = if let Some(name) = unsafe { proto.upvalue_name((upvalue_index - 1) as usize) } {
147 LuaString::from_interned(name)
148 } else {
149 LuaString::from_static(b"".as_bstr())
150 };
151
152 Some((name, value))
153}
154
155fn append_limited(output: &mut BString, data: &[u8], limit: usize) {
157 if output.len() >= limit {
158 return;
159 }
160
161 let remaining = limit - output.len();
162 output.extend_from_slice(&data[..data.len().min(remaining)]);
163}
164
165fn get_counters(
167 thread: &Thread,
168 proto: Proto,
169 context: *mut (),
170 function_visit: LuaCounterFunction,
171 counter_visit: LuaCounterValue,
172) {
173 let get_counter_data = unsafe { thread.global() }.execution_counter_data();
174 let proto_ref = unsafe { proto.as_ptr().as_ref().unwrap_unchecked() };
175 if !proto_ref.exec_data.is_null()
176 && let Some(get_counter_data) = get_counter_data
177 {
178 let mut count = 0usize;
179 let mut data = unsafe { get_counter_data(thread, proto, &mut count) };
180
181 if !data.is_null() && count != 0 {
182 let debug_name_string = unsafe { proto.debug_name() };
183 let debug_name = debug_name_string
184 .as_ref()
185 .map(|string| unsafe { string.as_bytes().as_bstr() });
186 let line_defined = proto_ref.line_defined;
187
188 function_visit(context, debug_name, line_defined);
189
190 for _ in 0..count {
191 let kind = unsafe { ptr::read_unaligned(data.cast::<u32>()) } as i32;
192 data = unsafe { data.add(size_of::<u32>()) };
193
194 let pc_pos = unsafe { ptr::read_unaligned(data.cast::<u32>()) };
195 data = unsafe { data.add(size_of::<u32>()) };
196
197 let hits = unsafe { ptr::read_unaligned(data.cast::<u64>()) };
198 data = unsafe { data.add(size_of::<u64>()) };
199
200 let line = if pc_pos == u32::MAX {
201 proto_ref.line_defined
202 } else {
203 unsafe { proto.get_line(pc_pos as i32) }
204 };
205
206 counter_visit(context, kind, line, hits);
207 }
208 }
209 }
210
211 for index in 0..proto_ref.size_p as usize {
212 let child = unsafe { proto.child_proto(index).unwrap_unchecked() };
213 get_counters(thread, child, context, function_visit, counter_visit);
214 }
215}
216
217fn get_max_line(proto: Proto) -> i32 {
219 let mut result = -1;
220
221 let proto_ref = unsafe { proto.as_ptr().as_ref().unwrap_unchecked() };
222 for index in 0..proto_ref.size_code as usize {
223 let line = unsafe { proto.get_line(index as i32) };
224 result = result.max(line);
225 }
226
227 for index in 0..proto_ref.size_p as usize {
228 let child = unsafe { proto.child_proto(index).unwrap_unchecked() };
229 result = result.max(get_max_line(child));
230 }
231
232 result
233}
234
235fn get_next_line(proto: Proto, line: i32) -> i32 {
237 let mut closest = -1;
238
239 let proto_ref = unsafe { proto.as_ptr().as_ref().unwrap_unchecked() };
240 if !proto_ref.line_info.is_null() {
241 for index in 0..proto_ref.size_code as usize {
242 let instruction = Instruction::new(unsafe { *proto_ref.code.add(index) });
243 if unsafe { instruction.opcode_unchecked() } == Opcode::PrepVarargs {
244 continue;
245 }
246
247 let candidate = unsafe { proto.get_line(index as i32) };
248 if candidate == line {
249 return line;
250 }
251
252 if candidate > line && (closest == -1 || candidate < closest) {
253 closest = candidate;
254 }
255 }
256 }
257
258 for index in 0..proto_ref.size_p as usize {
259 let child = unsafe { proto.child_proto(index).unwrap_unchecked() };
260 let candidate = get_next_line(child, line);
261 if candidate == line {
262 return line;
263 }
264
265 if candidate > line && (closest == -1 || candidate < closest) {
266 closest = candidate;
267 }
268 }
269
270 closest
271}
272
273fn get_coverage_recursive(
275 proto: Proto,
276 depth: i32,
277 buffer: &mut [i32],
278 context: *mut (),
279 callback: LuaCoverage,
280) {
281 buffer.fill(-1);
282
283 let proto_ref = unsafe { proto.as_ptr().as_ref().unwrap_unchecked() };
284 for index in 0..proto_ref.size_code as usize {
285 let instruction = Instruction::new(unsafe { *proto_ref.code.add(index) });
286 if unsafe { instruction.opcode_unchecked() } != Opcode::Coverage {
287 continue;
288 }
289
290 let line = unsafe { proto.get_line(index as i32) };
291 let hits = instruction.e();
292 debug_assert!((line as usize) < buffer.len());
293
294 let entry = &mut buffer[line as usize];
295 *entry = (*entry).max(hits);
296 }
297
298 let debug_name_string = unsafe { proto.debug_name() };
299 let debug_name = debug_name_string
300 .as_ref()
301 .map(|string| unsafe { string.as_bytes().as_bstr() });
302 let line_defined = proto_ref.line_defined;
303 callback(context, debug_name, line_defined, depth, buffer);
304
305 for index in 0..proto_ref.size_p as usize {
306 let child = unsafe { proto.child_proto(index).unwrap_unchecked() };
307 get_coverage_recursive(child, depth + 1, buffer, context, callback);
308 }
309}
310
311impl Thread {
312 pub unsafe fn call_hook<F>(&self, hook: F, userdata: *mut ()) -> crate::VmResult
314 where
315 F: FnOnce(&Thread, &mut LuaDebug) -> crate::VmResult,
316 {
317 debug_assert!(unsafe { self.current_call_info() != self.base_call_info() });
318 unsafe { <Self as VmCallFrame>::call_hook(self, hook, userdata) }
319 }
320
321 pub unsafe fn stack_depth(&self) -> i32 {
323 unsafe {
324 self.current_call_info_cursor()
325 .offset_from(self.base_call_info_cursor()) as i32
326 }
327 }
328
329 pub unsafe fn get_info(&self, level: i32, what: &str, ar: &mut LuaDebug) -> VmErrorResult<i32> {
331 unsafe {
332 let mut closure = None;
333 let mut call_info = None;
334 let mut stack_function = None;
335
336 if level < 0 {
337 let available = self.stack_top().offset_from(self.stack_base()) as i32;
338 if -level > available {
339 return Ok(0);
340 }
341
342 let function = self.stack_top().offset(level as isize);
343 if !function.value_unchecked().is_function() {
344 return Ok(0);
345 }
346
347 stack_function = Some(function);
348 closure = Some(function.value_unchecked().closure_value());
349 } else if (level as usize)
350 < self
351 .current_call_info_cursor()
352 .offset_from(self.base_call_info_cursor()) as usize
353 {
354 let ci = self
355 .current_call_info_cursor()
356 .sub(level as usize)
357 .call_info_unchecked();
358 call_info = Some(ci);
359 closure = Some(ci.function_closure());
360 }
361
362 let Some(closure) = closure else {
363 return Ok(0);
364 };
365
366 if aux_get_info(
367 what.as_bytes(),
368 ar,
369 closure,
370 call_info.map(|ci| ci.as_ptr()),
371 ) {
372 self.thread_barrier();
373
374 if let Some(call_info) = call_info {
375 self.push_value_internal(call_info.function().value_unchecked())?;
376 } else {
377 self.push_value_internal(stack_function.unwrap_unchecked().value_unchecked())?;
378 }
379 }
380
381 Ok(1)
382 }
383 }
384
385 pub unsafe fn get_argument(&self, level: i32, argument: i32) -> VmErrorResult<i32> {
387 unsafe {
388 let depth = self.stack_depth();
389 if (level as u32) >= (depth as u32) {
390 return Ok(0);
391 }
392
393 let call_info = self
394 .current_call_info_cursor()
395 .sub(level as usize)
396 .call_info_unchecked();
397 if call_info.as_ptr().as_ref().unwrap_unchecked().flags
398 & crate::state::LUA_CALLINFO_NATIVE
399 != 0
400 {
401 return Ok(0);
402 }
403
404 let Some(proto) = lua_proto(call_info) else {
405 return Ok(0);
406 };
407
408 if argument <= 0 {
409 return Ok(0);
410 }
411
412 if argument <= i32::from(proto.as_ptr().as_ref().unwrap_unchecked().num_params) {
413 self.thread_barrier();
414 self.push_value_internal(
415 call_info
416 .base()
417 .add((argument - 1) as usize)
418 .value_unchecked(),
419 )?;
420 Ok(1)
421 } else if proto.as_ptr().as_ref().unwrap_unchecked().is_vararg != 0
422 && argument < call_info.base().offset_from(call_info.function()) as i32
423 {
424 self.thread_barrier();
425 self.push_value_internal(
426 call_info
427 .function()
428 .add(argument as usize)
429 .value_unchecked(),
430 )?;
431 Ok(1)
432 } else {
433 Ok(0)
434 }
435 }
436 }
437
438 pub unsafe fn get_local(&self, level: i32, local: i32) -> VmErrorResult<Option<LuaString>> {
440 unsafe {
441 let depth = self.stack_depth();
442 if (level as u32) >= (depth as u32) {
443 return Ok(None);
444 }
445
446 let call_info = self
447 .current_call_info_cursor()
448 .sub(level as usize)
449 .call_info_unchecked();
450 if call_info.as_ptr().as_ref().unwrap_unchecked().flags
451 & crate::state::LUA_CALLINFO_NATIVE
452 != 0
453 {
454 return Ok(None);
455 }
456
457 let Some(proto) = lua_proto(call_info) else {
458 return Ok(None);
459 };
460 let Some(var) = proto.get_local(local, current_pc(call_info, proto)) else {
461 return Ok(None);
462 };
463
464 self.thread_barrier();
465 self.push_value_internal(
466 call_info
467 .base()
468 .add(var.as_ptr().as_ref().unwrap_unchecked().reg as usize)
469 .value_unchecked(),
470 )?;
471
472 let name = var.name();
473 debug_assert!(name.is_some());
474 Ok(Some(LuaString::from_interned(name.unwrap_unchecked())))
475 }
476 }
477
478 pub unsafe fn set_local(&self, level: i32, local: i32) -> Option<LuaString> {
480 unsafe {
481 debug_assert!(self.stack_top() > self.stack_base());
482
483 let depth = self.stack_depth();
484 if (level as u32) >= (depth as u32) {
485 return None;
486 }
487
488 let call_info = self
489 .current_call_info_cursor()
490 .sub(level as usize)
491 .call_info_unchecked();
492 if call_info.as_ptr().as_ref().unwrap_unchecked().flags
493 & crate::state::LUA_CALLINFO_NATIVE
494 != 0
495 {
496 return None;
497 }
498
499 let proto = lua_proto(call_info)?;
500 let var = proto.get_local(local, current_pc(call_info, proto))?;
501 call_info
502 .base()
503 .add(var.as_ptr().as_ref().unwrap_unchecked().reg as usize)
504 .value_unchecked()
505 .set_obj(self.stack_top().sub(1).value_unchecked());
506 self.set_stack_top(self.stack_top().sub(1));
507
508 let name = var.name();
509 debug_assert!(name.is_some());
510 Some(LuaString::from_interned(name.unwrap_unchecked()))
511 }
512 }
513
514 pub unsafe fn get_upvalue(
516 &self,
517 function_index: i32,
518 upvalue_index: i32,
519 ) -> VmErrorResult<Option<LuaString>> {
520 unsafe {
521 self.thread_barrier();
522 self.ensure_stack(self, 1)?;
523
524 let function = self.to_object(function_index).unwrap_unchecked();
525 let Some((name, value)) = aux_upvalue(function, upvalue_index) else {
526 return Ok(None);
527 };
528
529 self.push_value_internal(value)?;
530 Ok(Some(name))
531 }
532 }
533
534 pub unsafe fn set_upvalue(&self, function_index: i32, upvalue_index: i32) -> Option<LuaString> {
536 unsafe {
537 let function = self.to_object(function_index).unwrap_unchecked();
538 let (name, value) = aux_upvalue(function, upvalue_index)?;
539
540 self.set_stack_top(self.stack_top().sub(1));
541 value.set_obj(self.stack_top().value_unchecked());
542
543 let closure = function.closure_value();
544 let written = self.stack_top().value_unchecked();
545 if written.is_collectable() {
546 let object = closure.into();
547 let child = written.gc_value();
548 self.barrier_forward(object, child);
549 }
550
551 Some(name)
552 }
553 }
554
555 pub unsafe fn single_step(&self, enabled: i32) {
557 unsafe { self.as_ptr().as_mut().unwrap_unchecked().single_step = enabled != 0 };
558 }
559
560 pub unsafe fn breakpoint(
562 &self,
563 function_index: i32,
564 line: i32,
565 enabled: i32,
566 ) -> VmErrorResult<i32> {
567 unsafe {
568 let function = self.to_object(function_index).unwrap_unchecked();
569 debug_assert!(function.is_function());
570
571 let closure = function.closure_value();
572 debug_assert!(closure.is_lua());
573
574 let proto = closure.proto().unwrap_unchecked();
575
576 let target = get_next_line(proto, line);
577 if target != -1 {
578 self.breakpoint_internal(proto, target, enabled != 0)?;
579 }
580
581 Ok(target)
582 }
583 }
584
585 pub unsafe fn has_custom_execution(&self, level: i32) -> i32 {
587 unsafe { self.has_native(level) }
588 }
589
590 pub unsafe fn in_custom_execution(&self, level: i32) -> i32 {
592 unsafe { self.is_native(level) }
593 }
594
595 pub unsafe fn at_breakpoint(&self) -> i32 {
597 i32::from(unsafe { self.on_break() })
598 }
599
600 pub unsafe fn get_coverage(
602 &self,
603 function_index: i32,
604 context: *mut (),
605 callback: LuaCoverage,
606 ) -> VmErrorResult {
607 unsafe {
608 let function = self.to_object(function_index).unwrap_unchecked();
609 debug_assert!(function.is_function());
610
611 let closure = function.closure_value();
612 debug_assert!(closure.is_lua());
613
614 let proto = closure.proto().unwrap_unchecked();
615
616 let size = get_max_line(proto) + 1;
617 if size <= 0 {
618 return Ok(());
619 }
620
621 let buffer = self.new_array::<i32>(size as usize, 0)?;
622 let buffer = core::slice::from_raw_parts_mut(buffer, size as usize);
623
624 get_coverage_recursive(proto, 0, buffer, context, callback);
625
626 self.free_array(buffer.as_mut_ptr(), size as usize, 0);
627 }
628 Ok(())
629 }
630
631 pub unsafe fn get_counters(
633 &self,
634 function_index: i32,
635 context: *mut (),
636 function_visit: LuaCounterFunction,
637 counter_visit: LuaCounterValue,
638 ) {
639 unsafe {
640 let Some(function) = self.to_object(function_index) else {
641 return;
642 };
643 if !function.is_function() {
644 return;
645 }
646
647 if self.global().execution_counter_data().is_none() {
648 return;
649 }
650
651 let closure = function.closure_value();
652 if closure.is_native() {
653 return;
654 }
655
656 let proto = closure.proto().unwrap_unchecked();
657 get_counters(self, proto, context, function_visit, counter_visit);
658 }
659 }
660
661 pub unsafe fn debug_trace(&self) -> VmErrorResult<BString> {
663 const BUFFER_SIZE: usize = 4096;
664 const LIMIT_1: i32 = 10;
665 const LIMIT_2: i32 = 10;
666 const BYTE_LIMIT: usize = BUFFER_SIZE - 1;
667
668 unsafe {
669 let depth = self.stack_depth();
670 let mut output = BString::new(Vec::new());
671 let mut ar = LuaDebug::default();
672 let mut level = 0;
673
674 while self.get_info(level, "sln", &mut ar)? != 0 {
675 if !ar.source.as_bytes().is_empty() {
676 append_limited(&mut output, ar.short_src(), BYTE_LIMIT);
677 }
678
679 if ar.currentline > 0 {
680 append_limited(&mut output, b":", BYTE_LIMIT);
681 let mut line = [0u8; crate::number::LUAI_MAXINT2STR];
682 let len = crate::number::int_to_str(&mut line, i64::from(ar.currentline));
683 append_limited(&mut output, &line[..len], BYTE_LIMIT);
684 }
685
686 if let Some(name) = ar.name {
687 append_limited(&mut output, b" function ", BYTE_LIMIT);
688 append_limited(&mut output, name.as_bytes(), BYTE_LIMIT);
689 }
690
691 append_limited(&mut output, b"\n", BYTE_LIMIT);
692
693 if depth > LIMIT_1 + LIMIT_2 && level == LIMIT_1 - 1 {
694 append_limited(&mut output, b"... (+", BYTE_LIMIT);
695 let mut skipped = [0u8; crate::number::LUAI_MAXINT2STR];
696 let skipped_frames = i64::from(depth - LIMIT_1 - LIMIT_2);
697 let len = crate::number::int_to_str(&mut skipped, skipped_frames);
698 append_limited(&mut output, &skipped[..len], BYTE_LIMIT);
699 append_limited(&mut output, b" frames)\n", BYTE_LIMIT);
700 level = depth - LIMIT_2 - 1;
701 }
702
703 level += 1;
704 ar = LuaDebug::default();
705 }
706
707 Ok(output)
708 }
709 }
710}
711
712impl Thread {
714 pub unsafe fn traceback(
716 &self,
717 source: Option<&Thread>,
718 message: Option<&BStr>,
719 level: i32,
720 ) -> VmErrorResult {
721 debug_assert!(level >= 0);
722
723 let source = source.unwrap_or(self);
724 unsafe {
725 let mut buffer_storage = LuaStringBuilderStorage::uninit();
726 let mut buffer = LuaStringBuilder::new(self, &mut buffer_storage);
727
728 if let Some(message) = message {
729 buffer.push_bytes(message.as_bytes())?;
730 buffer.push_byte(b'\n')?;
731 }
732
733 let mut frame = level;
734 let mut ar = LuaDebug::default();
735 while source.get_info(frame, "sln", &mut ar)? != 0 {
736 frame += 1;
737
738 if ar.what.as_bytes() == b"C" {
739 ar = LuaDebug::default();
740 continue;
741 }
742
743 if !ar.source.as_bytes().is_empty() {
744 buffer.push_bytes(ar.short_src())?;
745 }
746
747 let mut line_digits = None;
748 if ar.currentline > 0 {
749 let mut digits = [0u8; 32];
750 let mut end = digits.len();
751 let mut line = ar.currentline as u32;
752 while line > 0 {
753 end -= 1;
754 digits[end] = b'0' + (line % 10) as u8;
755 line /= 10;
756 }
757 line_digits = Some((digits, end));
758 }
759
760 if let Some((digits, end)) = line_digits {
761 buffer.push_byte(b':')?;
762 buffer.push_bytes(&digits[end..])?;
763 }
764
765 if let Some(name) = ar.name {
766 buffer.push_bytes(b" function ")?;
767 buffer.push_bytes(name.as_bytes())?;
768 }
769
770 buffer.push_byte(b'\n')?;
771 ar = LuaDebug::default();
772 }
773
774 buffer.finish()?;
775 }
776 Ok(())
777 }
778}