1use std::cell::RefCell;
30#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
31use std::io::{self, BufRead, Write};
32use std::rc::Rc;
33
34use crate::state_stub::{LuaDebug as DebugInfo, LuaState, LuaStateStubExt as _};
35use lua_types::{GcRef, LuaError, LuaString, LuaType, LuaValue, LuaVersion};
36
37const HOOKKEY: &[u8] = b"_HOOKKEY";
42
43const HOOKNAMES: &[&[u8]; 5] = &[b"call", b"return", b"line", b"count", b"tail call"];
47
48const MASK_CALL: u32 = 1 << 0;
50const MASK_RET: u32 = 1 << 1;
51const MASK_LINE: u32 = 1 << 2;
52const MASK_COUNT: u32 = 1 << 3;
53
54pub(crate) type LibFn = fn(&mut LuaState) -> Result<usize, LuaError>;
58
59#[expect(
65 dead_code,
66 reason = "ported stdlib helper; not yet wired into the runtime"
67)]
68pub(crate) type HookFn = fn(&mut LuaState, i32, i32) -> Result<(), LuaError>;
69
70type UpvalId = usize;
79
80#[derive(Clone)]
81enum DebugThreadTarget {
82 Current,
83 Other(Rc<RefCell<LuaState>>),
84 Unavailable,
85}
86
87fn resolve_debug_thread_target(
88 state: &LuaState,
89 target_thread: &Option<GcRef<lua_types::value::LuaThread>>,
90) -> DebugThreadTarget {
91 let Some(thread) = target_thread else {
92 return DebugThreadTarget::Current;
93 };
94
95 if thread.id == state.cached_thread_id {
96 return DebugThreadTarget::Current;
97 }
98
99 let g = state.global();
100 if thread.id == g.main_thread_id {
101 DebugThreadTarget::Unavailable
102 } else {
103 g.threads
104 .get(&thread.id)
105 .map(|entry| DebugThreadTarget::Other(entry.state.clone()))
106 .unwrap_or(DebugThreadTarget::Unavailable)
107 }
108}
109
110fn check_cross_thread_stack(
120 state: &mut LuaState,
121 target_is_self: bool,
122 n: i32,
123) -> Result<(), LuaError> {
124 if !target_is_self {
125 state.ensure_stack(n, "stack overflow")?;
129 }
130 Ok(())
131}
132
133fn getthread(state: &mut LuaState) -> (i32, Option<GcRef<lua_types::value::LuaThread>>) {
137 if state.type_at(1) == LuaType::Thread {
138 let thread = state.to_thread_at(1);
139 return (1, thread);
140 }
141 (0, None)
142}
143
144fn settabss(state: &mut LuaState, k: &[u8], v: Option<&[u8]>) -> Result<(), LuaError> {
147 match v {
148 Some(s) => {
149 let ls = state.intern_str(s)?;
150 state.push(LuaValue::Str(ls));
151 }
152 None => {
153 state.push(LuaValue::Nil);
154 }
155 }
156 state.set_field(-2, k)
157}
158
159fn settabsi(state: &mut LuaState, k: &[u8], v: i32) -> Result<(), LuaError> {
162 state.push(LuaValue::Int(v as i64));
163 state.set_field(-2, k)
164}
165
166fn settabsb(state: &mut LuaState, k: &[u8], v: bool) -> Result<(), LuaError> {
169 state.push(LuaValue::Bool(v));
170 state.set_field(-2, k)
171}
172
173fn treat_stack_option(
180 state: &mut LuaState,
181 target_is_self: bool,
182 fname: &[u8],
183) -> Result<(), LuaError> {
184 if target_is_self {
185 state.rotate(-2, 1)?;
186 } else {
187 state.push(LuaValue::Nil);
191 }
192 state.set_field(-2, fname)
193}
194
195fn move_stack_option_from_target(
196 state: &mut LuaState,
197 target: &mut LuaState,
198 fname: &[u8],
199) -> Result<(), LuaError> {
200 let val = target.get_at(target.top_idx() - 1);
201 target.pop_n(1);
202 state.push(val);
203 state.set_field(-2, fname)
204}
205
206pub(crate) fn get_registry(state: &mut LuaState) -> Result<usize, LuaError> {
211 state.push_registry()?;
212 Ok(1)
213}
214
215pub(crate) fn get_metatable(state: &mut LuaState) -> Result<usize, LuaError> {
218 state.check_arg_any(1)?;
219 if !state.get_metatable(1)? {
220 state.push(LuaValue::Nil);
221 }
222 Ok(1)
223}
224
225pub(crate) fn set_metatable(state: &mut LuaState) -> Result<usize, LuaError> {
229 let t = state.type_at(2);
230 if !(t == LuaType::Nil || t == LuaType::Table) {
231 let got = state.arg(2);
232 return Err(LuaError::type_arg_error(2, "nil or table", &got));
233 }
234 lua_vm::api::set_top(state, 2)?;
235 state.set_metatable(1)?;
236 Ok(1)
237}
238
239pub(crate) fn get_uservalue(state: &mut LuaState) -> Result<usize, LuaError> {
244 let n = state.opt_arg_integer(2, 1)? as i32;
245 if state.type_at(1) != LuaType::UserData {
246 state.push_fail()?;
247 return Ok(1);
248 }
249 let ty = state.get_iuservalue(1, n)?;
250 if ty != LuaType::None {
251 state.push(LuaValue::Bool(true));
252 return Ok(2);
253 }
254 Ok(1)
255}
256
257pub(crate) fn set_uservalue(state: &mut LuaState) -> Result<usize, LuaError> {
261 let n = state.opt_arg_integer(3, 1)? as i32;
262 state.check_arg_type(1, LuaType::UserData)?;
263 state.check_arg_any(2)?;
264 lua_vm::api::set_top(state, 2)?;
265 if !state.set_iuservalue(1, n)? {
266 state.push_fail()?;
267 }
268 Ok(1)
269}
270
271pub(crate) fn get_info(state: &mut LuaState) -> Result<usize, LuaError> {
276 let mut ar = DebugInfo::default();
277
278 let (arg, other_thread) = getthread(state);
279 let target_is_self = other_thread.is_none();
280 let target_state = resolve_debug_thread_target(state, &other_thread);
281
282 let raw_opts: Vec<u8> = state.opt_arg_string(arg + 2, b"flnSrtu")?.to_vec();
284
285 check_cross_thread_stack(state, target_is_self, 3)?;
286
287 if raw_opts.first() == Some(&b'>') {
288 return Err(lua_vm::debug::arg_error_impl(
289 state,
290 arg + 2,
291 b"invalid option '>'",
292 ));
293 }
294
295 let options: Vec<u8>;
297 let info_target_owner: Option<Rc<RefCell<LuaState>>>;
298 let mut info_target: Option<crate::coro_lib::RootedThreadBorrow<'_>> = None;
299 let mut info_target_is_self = target_is_self;
300
301 if state.type_at(arg + 1) == LuaType::Function {
302 let mut prefixed = Vec::with_capacity(raw_opts.len() + 1);
303 prefixed.push(b'>');
304 prefixed.extend_from_slice(&raw_opts);
305 options = prefixed;
306
307 if target_is_self {
308 state.push_value_at(arg + 1)?;
309 } else {
310 }
314
315 if state.get_debug_info(&options, &mut ar).is_err() {
317 return Err(lua_vm::debug::arg_error_impl(
318 state,
319 arg + 2,
320 b"invalid option",
321 ));
322 }
323 } else {
324 options = raw_opts;
325
326 let level = state.check_arg_integer(arg + 1)? as i32;
327 match target_state {
328 DebugThreadTarget::Current | DebugThreadTarget::Unavailable => {
329 info_target_is_self = true;
330 if !state.get_stack_level(level, &mut ar) {
331 state.push_fail()?;
332 return Ok(1);
333 }
334
335 if state.get_debug_info(&options, &mut ar).is_err() {
336 return Err(lua_vm::debug::arg_error_impl(
337 state,
338 arg + 2,
339 b"invalid option",
340 ));
341 }
342 }
343 DebugThreadTarget::Other(target_state) => {
344 info_target_owner = Some(target_state);
345 let mut target = crate::coro_lib::borrow_thread_rooted(
346 state,
347 info_target_owner
348 .as_ref()
349 .expect("target owner just stored"),
350 );
351 if !target.get_stack_level(level, &mut ar) {
352 state.push_fail()?;
353 return Ok(1);
354 }
355 if target.get_debug_info(&options, &mut ar).is_err() {
356 return Err(lua_vm::debug::arg_error_impl(
357 state,
358 arg + 2,
359 b"invalid option",
360 ));
361 }
362 target.resnapshot();
363 info_target = Some(target);
364 }
365 }
366 }
367
368 let result_tbl = state.new_table();
369 state.push(LuaValue::Table(result_tbl));
370
371 if options.contains(&b'S') {
372 let src = state.intern_str(ar.source_bytes())?;
373 state.push(LuaValue::Str(src));
374 state.set_field(-2, b"source")?;
375
376 settabss(state, b"short_src", Some(ar.short_src_bytes()))?;
377 settabsi(state, b"linedefined", ar.linedefined)?;
378 settabsi(state, b"lastlinedefined", ar.lastlinedefined)?;
379 settabss(state, b"what", Some(ar.what_bytes()))?;
380 }
381 if options.contains(&b'l') {
382 settabsi(state, b"currentline", ar.currentline)?;
383 }
384 if options.contains(&b'u') {
385 settabsi(state, b"nups", ar.nups as i32)?;
386 if !matches!(state.global().lua_version, LuaVersion::V51) {
387 settabsi(state, b"nparams", ar.nparams as i32)?;
388 settabsb(state, b"isvararg", ar.isvararg)?;
389 }
390 }
391 if options.contains(&b'n') {
392 let name_opt: Option<&[u8]> = ar.name.as_deref();
393 settabss(state, b"name", name_opt)?;
394 settabss(state, b"namewhat", Some(ar.namewhat_bytes()))?;
395 }
396 if options.contains(&b'r') {
397 settabsi(state, b"ftransfer", ar.ftransfer as i32)?;
398 settabsi(state, b"ntransfer", ar.ntransfer as i32)?;
399 }
400 if options.contains(&b't') {
401 settabsb(state, b"istailcall", ar.istailcall)?;
402 if matches!(state.global().lua_version, LuaVersion::V55) {
403 settabsi(state, b"extraargs", ar.extraargs as i32)?;
404 }
405 }
406 if options.contains(&b'L') {
411 if info_target_is_self {
412 treat_stack_option(state, true, b"activelines")?;
413 } else if let Some(target) = info_target.as_mut() {
414 move_stack_option_from_target(state, &mut **target, b"activelines")?;
415 } else {
416 state.push(LuaValue::Nil);
417 state.set_field(-2, b"activelines")?;
418 }
419 }
420 if options.contains(&b'f') {
421 if info_target_is_self {
422 treat_stack_option(state, true, b"func")?;
423 } else if let Some(target) = info_target.as_mut() {
424 move_stack_option_from_target(state, &mut **target, b"func")?;
425 } else {
426 state.push(LuaValue::Nil);
427 state.set_field(-2, b"func")?;
428 }
429 }
430
431 Ok(1)
432}
433
434fn function_arg_form_supported(state: &LuaState) -> bool {
445 !matches!(state.global().lua_version, LuaVersion::V51)
446}
447
448pub(crate) fn get_local(state: &mut LuaState) -> Result<usize, LuaError> {
456 let (arg, other_thread) = getthread(state);
457 let target_state = resolve_debug_thread_target(state, &other_thread);
458
459 let nvar = state.check_arg_integer(arg + 2)? as i32;
460
461 if function_arg_form_supported(state) && state.type_at(arg + 1) == LuaType::Function {
462 state.push_value_at(arg + 1)?;
463 let name = state.get_param_name(0, nvar)?;
464 match name {
465 Some(n) => {
466 let ls = state.intern_str(&n)?;
467 state.push(LuaValue::Str(ls));
468 }
469 None => {
470 state.push(LuaValue::Nil);
471 }
472 }
473 return Ok(1);
474 }
475
476 let level = state.check_arg_integer(arg + 1)? as i32;
478 let mut ar = DebugInfo::default();
479
480 let name = match target_state {
481 DebugThreadTarget::Current | DebugThreadTarget::Unavailable => {
482 if !state.get_stack_level(level, &mut ar) {
483 return Err(lua_vm::debug::arg_error_impl(
484 state,
485 arg + 1,
486 b"level out of range",
487 ));
488 }
489 check_cross_thread_stack(state, true, 1)?;
490 state.get_local_at(&ar, nvar)?
492 }
493 DebugThreadTarget::Other(target_state) => {
494 let mut target = crate::coro_lib::borrow_thread_rooted(state, &target_state);
495 if !target.get_stack_level(level, &mut ar) {
496 return Err(lua_vm::debug::arg_error_impl(
497 state,
498 arg + 1,
499 b"level out of range",
500 ));
501 }
502 check_cross_thread_stack(state, false, 1)?;
503 let name = target.get_local_at(&ar, nvar)?;
504 if name.is_some() {
505 let val = target.get_at(target.top_idx() - 1);
506 target.pop_n(1);
507 state.push(val);
508 }
509 name
510 }
511 };
512
513 if let Some(n) = name {
514 let ls = state.intern_str(&n)?;
515 state.push(LuaValue::Str(ls));
516 state.rotate(-2, 1)?;
517 Ok(2)
518 } else {
519 state.push_fail()?;
520 Ok(1)
521 }
522}
523
524pub(crate) fn set_local(state: &mut LuaState) -> Result<usize, LuaError> {
529 let (arg, other_thread) = getthread(state);
530 let target_state = resolve_debug_thread_target(state, &other_thread);
531
532 let level = state.check_arg_integer(arg + 1)? as i32;
533 let nvar = state.check_arg_integer(arg + 2)? as i32;
534
535 let mut ar = DebugInfo::default();
536
537 state.check_arg_any(arg + 3)?;
538 lua_vm::api::set_top(state, arg + 3)?;
539
540 let name = match target_state {
541 DebugThreadTarget::Current | DebugThreadTarget::Unavailable => {
542 if !state.get_stack_level(level, &mut ar) {
543 return Err(lua_vm::debug::arg_error_impl(
544 state,
545 arg + 1,
546 b"level out of range",
547 ));
548 }
549 check_cross_thread_stack(state, true, 1)?;
550 let name = state.set_local_at(&ar, nvar)?;
551 if name.is_none() {
552 state.pop_n(1);
553 }
554 name
555 }
556 DebugThreadTarget::Other(target_state) => {
557 let new_val = state.get_at(state.top_idx() - 1);
558 let mut target = crate::coro_lib::borrow_thread_rooted(state, &target_state);
559 if !target.get_stack_level(level, &mut ar) {
560 return Err(lua_vm::debug::arg_error_impl(
561 state,
562 arg + 1,
563 b"level out of range",
564 ));
565 }
566 check_cross_thread_stack(state, false, 1)?;
567 target.push(new_val);
568 let name = target.set_local_at(&ar, nvar)?;
569 if name.is_none() {
570 target.pop_n(1);
571 }
572 state.pop_n(1);
573 name
574 }
575 };
576
577 match name {
578 Some(n) => {
579 let ls = state.intern_str(&n)?;
580 state.push(LuaValue::Str(ls));
581 }
582 None => {
583 state.push(LuaValue::Nil);
584 }
585 }
586 Ok(1)
587}
588
589fn aux_upvalue(state: &mut LuaState, get: bool) -> Result<usize, LuaError> {
600 let n = state.check_arg_integer(2)? as i32;
601 state.check_arg_type(1, LuaType::Function)?;
602
603 let name: Option<Vec<u8>> = if get {
604 state.get_upvalue(1, n)?
606 } else {
607 state.set_upvalue(1, n)?
609 };
610
611 let name_ref = match name {
612 Some(n) => n,
613 None => return Ok(0),
614 };
615
616 let ls = state.intern_str(&name_ref)?;
617 state.push(LuaValue::Str(ls));
618
619 if get {
622 state.insert(-2)?;
623 }
624
625 Ok(if get { 2 } else { 1 })
626}
627
628pub(crate) fn get_upvalue(state: &mut LuaState) -> Result<usize, LuaError> {
631 aux_upvalue(state, true)
632}
633
634pub(crate) fn set_upvalue(state: &mut LuaState) -> Result<usize, LuaError> {
638 state.check_arg_any(3)?;
639 aux_upvalue(state, false)
640}
641
642fn check_upval(
647 state: &mut LuaState,
648 argf: i32,
649 argnup: i32,
650 require_valid: bool,
651) -> Result<(Option<UpvalId>, i32), LuaError> {
652 let nup = state.check_arg_integer(argnup)? as i32;
653 state.check_arg_type(argf, LuaType::Function)?;
654 let id: Option<UpvalId> = match state.upvalue_id(argf, nup) {
659 Ok(p) if p.is_null() => None,
660 Ok(p) => Some(p as usize),
661 Err(_) => None,
662 };
663 if require_valid && id.is_none() {
664 return Err(lua_vm::debug::arg_error_impl(
665 state,
666 argnup,
667 b"invalid upvalue index",
668 ));
669 }
670 Ok((id, nup))
671}
672
673pub(crate) fn upvalue_id(state: &mut LuaState) -> Result<usize, LuaError> {
683 let require_valid = matches!(
684 state.global().lua_version,
685 LuaVersion::V51 | LuaVersion::V52 | LuaVersion::V53
686 );
687 let (id, _nup) = check_upval(state, 1, 2, require_valid)?;
688 match id {
689 Some(uid) => {
690 lua_vm::api::push_light_userdata(state, uid as *mut core::ffi::c_void);
691 }
692 None => {
693 state.push_fail()?;
694 }
695 }
696 Ok(1)
697}
698
699pub(crate) fn upvalue_join(state: &mut LuaState) -> Result<usize, LuaError> {
703 let (_id1, n1) = check_upval(state, 1, 2, true)?;
704 let (_id2, n2) = check_upval(state, 3, 4, true)?;
705 if state.is_c_function_at(1) {
706 return Err(lua_vm::debug::arg_error_impl(
707 state,
708 1,
709 b"Lua function expected",
710 ));
711 }
712 if state.is_c_function_at(3) {
713 return Err(lua_vm::debug::arg_error_impl(
714 state,
715 3,
716 b"Lua function expected",
717 ));
718 }
719 state.join_upvalues(1, n1, 3, n2)?;
720 Ok(0)
721}
722
723pub(crate) fn hookf(state: &mut LuaState, event: i32, currentline: i32) -> Result<(), LuaError> {
729 state.get_registry_field(HOOKKEY)?;
730 state.push_thread()?;
731 if state.raw_get(-2)? == LuaType::Function {
732 let event_idx = event.clamp(0, HOOKNAMES.len() as i32 - 1) as usize;
733 let event_str = state.intern_str(HOOKNAMES[event_idx])?;
734 state.push(LuaValue::Str(event_str));
735
736 if currentline >= 0 {
737 state.push(LuaValue::Int(currentline as i64));
738 } else {
739 state.push(LuaValue::Nil);
740 }
741
742 state.call(2, 0)?;
743 }
744 Ok(())
747}
748
749fn make_mask(smask: &[u8], count: i32) -> u32 {
753 let mut mask: u32 = 0;
754 if smask.contains(&b'c') {
755 mask |= MASK_CALL;
756 }
757 if smask.contains(&b'r') {
758 mask |= MASK_RET;
759 }
760 if smask.contains(&b'l') {
761 mask |= MASK_LINE;
762 }
763 if count > 0 {
764 mask |= MASK_COUNT;
765 }
766 mask
767}
768
769fn unmake_mask(mask: u32) -> Vec<u8> {
773 let mut smask = Vec::with_capacity(3);
774 if mask & MASK_CALL != 0 {
775 smask.push(b'c');
776 }
777 if mask & MASK_RET != 0 {
778 smask.push(b'r');
779 }
780 if mask & MASK_LINE != 0 {
781 smask.push(b'l');
782 }
783 smask
784}
785
786pub(crate) fn set_hook(state: &mut LuaState) -> Result<usize, LuaError> {
790 let (arg, other_thread) = getthread(state);
791 let target_is_self = other_thread.is_none();
792
793 let hook_active: bool;
794 let mask: u32;
795 let count: i32;
796
797 if matches!(state.type_at(arg + 1), LuaType::None | LuaType::Nil) {
798 lua_vm::api::set_top(state, arg + 1)?;
799 hook_active = false;
800 mask = 0;
801 count = 0;
802 } else {
803 let smask: Vec<u8> = state.check_arg_string(arg + 2)?.to_vec();
804 state.check_arg_type(arg + 1, LuaType::Function)?;
805 count = state.opt_arg_integer(arg + 3, 0)? as i32;
806 hook_active = true;
807 mask = make_mask(&smask, count);
808 }
809
810 if !state.get_or_create_registry_subtable(HOOKKEY)? {
811 let k = state.intern_str(b"k")?;
814 state.push(LuaValue::Str(k));
815 state.set_field(-2, b"__mode")?;
816 state.push_value_at(-1)?;
817 state.set_metatable(-2)?;
818 }
819
820 check_cross_thread_stack(state, target_is_self, 1)?;
821 let target_state = resolve_debug_thread_target(state, &other_thread);
822 match &target_state {
823 DebugThreadTarget::Other(st) => {
824 st.borrow_mut().ensure_stack(1, "stack overflow")?;
825 }
826 DebugThreadTarget::Current => {}
827 DebugThreadTarget::Unavailable => {}
828 }
829
830 if target_is_self {
831 state.push_thread()?;
832 } else {
833 let thr = other_thread
840 .clone()
841 .expect("other_thread is Some when target_is_self is false");
842 state.push(lua_types::value::LuaValue::Thread(thr));
843 }
844 state.push_value_at(arg + 1)?;
845 state.raw_set(-3)?;
846
847 let hook_box: Option<Box<dyn FnMut(&mut LuaState, &lua_vm::debug::LuaDebug)>> = if hook_active {
848 Some(Box::new(|st, ar| {
849 let _ = hookf(st, ar.event, ar.currentline);
850 }))
851 } else {
852 None
853 };
854 match target_state {
855 DebugThreadTarget::Current => {
856 lua_vm::debug::set_hook(state, hook_box, mask as i32, count);
857 }
858 DebugThreadTarget::Other(target_state) => {
859 lua_vm::debug::set_hook(&mut target_state.borrow_mut(), hook_box, mask as i32, count);
860 }
861 DebugThreadTarget::Unavailable => {
862 return Ok(0);
866 }
867 }
868
869 Ok(0)
870}
871
872pub(crate) fn get_hook(state: &mut LuaState) -> Result<usize, LuaError> {
876 let (_arg, other_thread) = getthread(state);
877 let target_is_self = other_thread.is_none();
878 let target_state = resolve_debug_thread_target(state, &other_thread);
879
880 let (mask, hook_is_set, hook_is_internal, hook_count) = match target_state {
881 DebugThreadTarget::Current => (
882 state.get_hook_mask(),
883 state.hook_is_set(),
884 state.hook_is_internal_lua_hook(),
885 state.get_hook_count(),
886 ),
887 DebugThreadTarget::Other(target_state) => {
888 let mut target_state = target_state.borrow_mut();
889 (
890 target_state.get_hook_mask(),
891 target_state.hook_is_set(),
892 target_state.hook_is_internal_lua_hook(),
893 target_state.get_hook_count(),
894 )
895 }
896 DebugThreadTarget::Unavailable => (0u32, false, false, 0i32),
897 };
898
899 if !hook_is_set {
900 state.push_fail()?;
901 return Ok(1);
902 }
903
904 if !hook_is_internal {
905 let s = state.intern_str(b"external hook")?;
906 state.push(LuaValue::Str(s));
907 } else {
908 state.get_registry_field(HOOKKEY)?;
909 check_cross_thread_stack(state, target_is_self, 1)?;
910 if target_is_self {
911 state.push_thread()?;
912 } else {
913 let key_thread = other_thread
914 .expect("other_thread is Some when target_is_self is false")
915 .clone();
916 state.push(lua_types::value::LuaValue::Thread(key_thread));
917 }
918 state.raw_get(-2)?;
919 state.remove(-2)?;
920 }
921
922 let smask = unmake_mask(mask);
923 let ls = state.intern_str(&smask)?;
924 state.push(LuaValue::Str(ls));
925
926 state.push(LuaValue::Int(hook_count as i64));
927
928 Ok(3)
929}
930
931pub(crate) fn debug_interactive(state: &mut LuaState) -> Result<usize, LuaError> {
938 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
939 {
940 let _ = state;
941 return Err(LuaError::runtime(format_args!(
942 "debug.debug interactive stdin not available in this host"
943 )));
944 }
945
946 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
947 {
948 let stdin = io::stdin();
949 loop {
950 eprint!("lua_debug> ");
951 let _ = io::stderr().flush();
952
953 let mut line = String::new();
956 let n = stdin
957 .lock()
958 .read_line(&mut line)
959 .map_err(|e| LuaError::runtime(format_args!("stdin read error: {}", e)))?;
960
961 if n == 0 || line == "cont\n" {
962 return Ok(0);
963 }
964
965 let bytes: &[u8] = line.as_bytes();
966
967 let result = state
968 .load_buffer(bytes, b"=(debug command)", None)
969 .and_then(|_| state.protected_call(0, 0, 0));
970
971 if result.is_err() {
972 eprintln!("(error in debug command)");
977 state.pop_n(1);
978 }
979
980 lua_vm::api::set_top(state, 0)?;
981 }
982 }
983}
984
985pub(crate) fn traceback(state: &mut LuaState) -> Result<usize, LuaError> {
992 let (arg, other_thread) = getthread(state);
993 let target_is_self = other_thread.is_none();
994
995 let msg_owned: Option<Vec<u8>> = state
997 .to_lua_string(arg + 1)
998 .map(|s: GcRef<LuaString>| s.as_bytes().to_vec());
999
1000 let arg1_ty = state.type_at(arg + 1);
1001 if msg_owned.is_none() && !matches!(arg1_ty, LuaType::None | LuaType::Nil) {
1002 state.push_value_at(arg + 1)?;
1003 } else {
1004 let default_level: i64 = if target_is_self { 1 } else { 0 };
1005 let level = state.opt_arg_integer(arg + 2, default_level)? as i32;
1006
1007 match resolve_debug_thread_target(state, &other_thread) {
1008 DebugThreadTarget::Current => {
1009 crate::auxlib::traceback(state, None, msg_owned.as_deref(), level)?;
1010 }
1011 DebugThreadTarget::Other(target_state) => {
1012 let mut target_state = crate::coro_lib::borrow_thread_rooted(state, &target_state);
1013 crate::auxlib::traceback(
1014 state,
1015 Some(&mut *target_state),
1016 msg_owned.as_deref(),
1017 level,
1018 )?;
1019 }
1020 DebugThreadTarget::Unavailable => {
1021 crate::auxlib::traceback(state, None, msg_owned.as_deref(), level)?;
1022 }
1023 }
1024 }
1025 Ok(1)
1026}
1027
1028pub(crate) fn set_c_stack_limit(state: &mut LuaState) -> Result<usize, LuaError> {
1032 let limit = state.check_arg_integer(1)? as i32;
1033 let res = state.set_c_stack_limit(limit)?;
1034 state.push(LuaValue::Int(res as i64));
1035 Ok(1)
1036}
1037
1038pub(crate) const DBLIB: &[(&[u8], LibFn)] = &[
1043 (b"debug", debug_interactive as LibFn),
1044 (b"getuservalue", get_uservalue as LibFn),
1045 (b"gethook", get_hook as LibFn),
1046 (b"getinfo", get_info as LibFn),
1047 (b"getlocal", get_local as LibFn),
1048 (b"getregistry", get_registry as LibFn),
1049 (b"getmetatable", get_metatable as LibFn),
1050 (b"getupvalue", get_upvalue as LibFn),
1051 (b"upvaluejoin", upvalue_join as LibFn),
1052 (b"upvalueid", upvalue_id as LibFn),
1053 (b"setuservalue", set_uservalue as LibFn),
1054 (b"sethook", set_hook as LibFn),
1055 (b"setlocal", set_local as LibFn),
1056 (b"setmetatable", set_metatable as LibFn),
1057 (b"setupvalue", set_upvalue as LibFn),
1058 (b"traceback", traceback as LibFn),
1059 (b"setcstacklimit", set_c_stack_limit as LibFn),
1060];
1061
1062const DBLIB_DROP_V51: &[&[u8]] = &[
1069 b"getuservalue",
1070 b"setuservalue",
1071 b"upvalueid",
1072 b"upvaluejoin",
1073 b"setcstacklimit",
1074];
1075
1076pub fn open_debug(state: &mut LuaState) -> Result<usize, LuaError> {
1084 use lua_types::LuaVersion;
1085 let version = state.global().lua_version;
1086 let is_v51 = matches!(version, LuaVersion::V51);
1087 let has_setcstacklimit = matches!(version, LuaVersion::V54);
1088
1089 let filtered: Vec<(&[u8], LibFn)> = DBLIB
1090 .iter()
1091 .filter(|(name, _)| {
1092 if !has_setcstacklimit && *name == b"setcstacklimit".as_slice() {
1093 return false;
1094 }
1095 if is_v51 && DBLIB_DROP_V51.contains(name) {
1096 return false;
1097 }
1098 true
1099 })
1100 .copied()
1101 .collect();
1102 state.new_lib(&filtered)?;
1103
1104 if is_v51 {
1105 state.push_c_function(crate::base::debug_getfenv_fn)?;
1110 state.set_field(-2, b"getfenv")?;
1111 state.push_c_function(crate::base::debug_setfenv_fn)?;
1112 state.set_field(-2, b"setfenv")?;
1113 }
1114
1115 Ok(1)
1116}
1117
1118