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"];
51
52const HOOKNAME_TAILRET_51: &[u8] = b"tail return";
54
55const MASK_CALL: u32 = 1 << 0;
57const MASK_RET: u32 = 1 << 1;
58const MASK_LINE: u32 = 1 << 2;
59const MASK_COUNT: u32 = 1 << 3;
60
61pub(crate) type LibFn = fn(&mut LuaState) -> Result<usize, LuaError>;
65
66#[expect(
72 dead_code,
73 reason = "ported stdlib helper; not yet wired into the runtime"
74)]
75pub(crate) type HookFn = fn(&mut LuaState, i32, i32) -> Result<(), LuaError>;
76
77type UpvalId = usize;
85
86#[derive(Clone)]
87enum DebugThreadTarget {
88 Current,
89 Other(Rc<RefCell<LuaState>>),
90 Unavailable,
91}
92
93fn resolve_debug_thread_target(
94 state: &LuaState,
95 target_thread: &Option<GcRef<lua_types::value::LuaThread>>,
96) -> DebugThreadTarget {
97 let Some(thread) = target_thread else {
98 return DebugThreadTarget::Current;
99 };
100
101 if thread.id == state.cached_thread_id {
102 return DebugThreadTarget::Current;
103 }
104
105 let g = state.global();
106 if thread.id == g.main_thread_id {
107 DebugThreadTarget::Unavailable
108 } else {
109 g.threads
110 .get(&thread.id)
111 .map(|entry| DebugThreadTarget::Other(entry.state.clone()))
112 .unwrap_or(DebugThreadTarget::Unavailable)
113 }
114}
115
116fn check_cross_thread_stack(
126 state: &mut LuaState,
127 target_is_self: bool,
128 n: i32,
129) -> Result<(), LuaError> {
130 if !target_is_self {
131 state.ensure_stack(n, "stack overflow")?;
132 }
133 Ok(())
134}
135
136fn getthread(state: &mut LuaState) -> (i32, Option<GcRef<lua_types::value::LuaThread>>) {
140 if state.type_at(1) == LuaType::Thread {
141 let thread = state.to_thread_at(1);
142 return (1, thread);
143 }
144 (0, None)
145}
146
147fn settabss(state: &mut LuaState, k: &[u8], v: Option<&[u8]>) -> Result<(), LuaError> {
150 match v {
151 Some(s) => {
152 let ls = state.intern_str(s)?;
153 state.push(LuaValue::Str(ls));
154 }
155 None => {
156 state.push(LuaValue::Nil);
157 }
158 }
159 state.set_field(-2, k)
160}
161
162fn settabsi(state: &mut LuaState, k: &[u8], v: i32) -> Result<(), LuaError> {
165 state.push(LuaValue::Int(v as i64));
166 state.set_field(-2, k)
167}
168
169fn settabsb(state: &mut LuaState, k: &[u8], v: bool) -> Result<(), LuaError> {
172 state.push(LuaValue::Bool(v));
173 state.set_field(-2, k)
174}
175
176fn treat_stack_option(
183 state: &mut LuaState,
184 target_is_self: bool,
185 fname: &[u8],
186) -> Result<(), LuaError> {
187 if target_is_self {
188 state.rotate(-2, 1)?;
189 } else {
190 state.push(LuaValue::Nil);
195 }
196 state.set_field(-2, fname)
197}
198
199fn move_stack_option_from_target(
200 state: &mut LuaState,
201 target: &mut LuaState,
202 fname: &[u8],
203) -> Result<(), LuaError> {
204 let val = target.get_at(target.top_idx() - 1);
205 target.pop_n(1);
206 state.push(val);
207 state.set_field(-2, fname)
208}
209
210pub(crate) fn get_registry(state: &mut LuaState) -> Result<usize, LuaError> {
215 state.push_registry()?;
216 Ok(1)
217}
218
219pub(crate) fn get_metatable(state: &mut LuaState) -> Result<usize, LuaError> {
222 state.check_arg_any(1)?;
223 if !state.get_metatable(1)? {
224 state.push(LuaValue::Nil);
225 }
226 Ok(1)
227}
228
229pub(crate) fn set_metatable(state: &mut LuaState) -> Result<usize, LuaError> {
233 let t = state.type_at(2);
234 if !(t == LuaType::Nil || t == LuaType::Table) {
235 let got = state.arg(2);
236 return Err(LuaError::type_arg_error(2, "nil or table", &got));
237 }
238 lua_vm::api::set_top(state, 2)?;
239 state.set_metatable(1)?;
240 Ok(1)
241}
242
243pub(crate) fn get_uservalue(state: &mut LuaState) -> Result<usize, LuaError> {
248 let n = state.opt_arg_integer(2, 1)? as i32;
249 if state.type_at(1) != LuaType::UserData {
250 state.push_fail()?;
251 return Ok(1);
252 }
253 let ty = state.get_iuservalue(1, n)?;
254 if ty != LuaType::None {
255 state.push(LuaValue::Bool(true));
256 return Ok(2);
257 }
258 Ok(1)
259}
260
261pub(crate) fn set_uservalue(state: &mut LuaState) -> Result<usize, LuaError> {
265 let n = state.opt_arg_integer(3, 1)? as i32;
266 state.check_arg_type(1, LuaType::UserData)?;
267 state.check_arg_any(2)?;
268 lua_vm::api::set_top(state, 2)?;
269 if !state.set_iuservalue(1, n)? {
270 state.push_fail()?;
271 }
272 Ok(1)
273}
274
275pub(crate) fn get_info(state: &mut LuaState) -> Result<usize, LuaError> {
280 let mut ar = DebugInfo::default();
281
282 let (arg, other_thread) = getthread(state);
283 let target_is_self = other_thread.is_none();
284 let target_state = resolve_debug_thread_target(state, &other_thread);
285
286 let raw_opts: Vec<u8> = state.opt_arg_string(arg + 2, b"flnSrtu")?.to_vec();
288
289 check_cross_thread_stack(state, target_is_self, 3)?;
290
291 if raw_opts.first() == Some(&b'>') {
292 return Err(lua_vm::debug::arg_error_impl(
293 state,
294 arg + 2,
295 b"invalid option '>'",
296 ));
297 }
298
299 let options: Vec<u8>;
301 let info_target_owner: Option<Rc<RefCell<LuaState>>>;
302 let mut info_target: Option<crate::coro_lib::RootedThreadBorrow<'_>> = None;
303 let mut info_target_is_self = target_is_self;
304
305 if state.type_at(arg + 1) == LuaType::Function {
306 let mut prefixed = Vec::with_capacity(raw_opts.len() + 1);
307 prefixed.push(b'>');
308 prefixed.extend_from_slice(&raw_opts);
309 options = prefixed;
310
311 if target_is_self {
312 state.push_value_at(arg + 1)?;
313 } else {
314 }
319
320 if state.get_debug_info(&options, &mut ar).is_err() {
322 return Err(lua_vm::debug::arg_error_impl(
323 state,
324 arg + 2,
325 b"invalid option",
326 ));
327 }
328 } else {
329 options = raw_opts;
330
331 let level = state.check_arg_integer(arg + 1)? as i32;
332 match target_state {
333 DebugThreadTarget::Current | DebugThreadTarget::Unavailable => {
334 info_target_is_self = true;
335 if !state.get_stack_level(level, &mut ar) {
336 state.push_fail()?;
337 return Ok(1);
338 }
339
340 if state.get_debug_info(&options, &mut ar).is_err() {
341 return Err(lua_vm::debug::arg_error_impl(
342 state,
343 arg + 2,
344 b"invalid option",
345 ));
346 }
347 }
348 DebugThreadTarget::Other(target_state) => {
349 info_target_owner = Some(target_state);
350 let mut target = crate::coro_lib::borrow_thread_rooted(
351 state,
352 info_target_owner
353 .as_ref()
354 .expect("target owner just stored"),
355 );
356 if !target.get_stack_level(level, &mut ar) {
357 state.push_fail()?;
358 return Ok(1);
359 }
360 if target.get_debug_info(&options, &mut ar).is_err() {
361 return Err(lua_vm::debug::arg_error_impl(
362 state,
363 arg + 2,
364 b"invalid option",
365 ));
366 }
367 target.resnapshot();
368 info_target = Some(target);
369 }
370 }
371 }
372
373 let result_tbl = state.new_table();
374 state.push(LuaValue::Table(result_tbl));
375
376 if options.contains(&b'S') {
377 let src = state.intern_str(ar.source_bytes())?;
378 state.push(LuaValue::Str(src));
379 state.set_field(-2, b"source")?;
380
381 settabss(state, b"short_src", Some(ar.short_src_bytes()))?;
382 settabsi(state, b"linedefined", ar.linedefined)?;
383 settabsi(state, b"lastlinedefined", ar.lastlinedefined)?;
384 settabss(state, b"what", Some(ar.what_bytes()))?;
385 }
386 if options.contains(&b'l') {
387 settabsi(state, b"currentline", ar.currentline)?;
388 }
389 if options.contains(&b'u') {
390 settabsi(state, b"nups", ar.nups as i32)?;
391 if !matches!(state.global().lua_version, LuaVersion::V51) {
392 settabsi(state, b"nparams", ar.nparams as i32)?;
393 settabsb(state, b"isvararg", ar.isvararg)?;
394 }
395 }
396 if options.contains(&b'n') {
397 let name_opt: Option<&[u8]> = ar.name.as_deref();
398 settabss(state, b"name", name_opt)?;
399 settabss(state, b"namewhat", Some(ar.namewhat_bytes()))?;
400 }
401 if options.contains(&b'r') {
402 settabsi(state, b"ftransfer", ar.ftransfer as i32)?;
403 settabsi(state, b"ntransfer", ar.ntransfer as i32)?;
404 }
405 if options.contains(&b't') {
406 settabsb(state, b"istailcall", ar.istailcall)?;
407 if matches!(state.global().lua_version, LuaVersion::V55) {
408 settabsi(state, b"extraargs", ar.extraargs as i32)?;
409 }
410 }
411 if options.contains(&b'L') {
416 if info_target_is_self {
417 treat_stack_option(state, true, b"activelines")?;
418 } else if let Some(target) = info_target.as_mut() {
419 move_stack_option_from_target(state, &mut **target, b"activelines")?;
420 } else {
421 state.push(LuaValue::Nil);
422 state.set_field(-2, b"activelines")?;
423 }
424 }
425 if options.contains(&b'f') {
426 if info_target_is_self {
427 treat_stack_option(state, true, b"func")?;
428 } else if let Some(target) = info_target.as_mut() {
429 move_stack_option_from_target(state, &mut **target, b"func")?;
430 } else {
431 state.push(LuaValue::Nil);
432 state.set_field(-2, b"func")?;
433 }
434 }
435
436 Ok(1)
437}
438
439fn function_arg_form_supported(state: &LuaState) -> bool {
450 !matches!(state.global().lua_version, LuaVersion::V51)
451}
452
453pub(crate) fn get_local(state: &mut LuaState) -> Result<usize, LuaError> {
461 let (arg, other_thread) = getthread(state);
462 let target_state = resolve_debug_thread_target(state, &other_thread);
463
464 let nvar = state.check_arg_integer(arg + 2)? as i32;
465
466 if function_arg_form_supported(state) && state.type_at(arg + 1) == LuaType::Function {
467 state.push_value_at(arg + 1)?;
468 let name = state.get_param_name(0, nvar)?;
469 match name {
470 Some(n) => {
471 let ls = state.intern_str(&n)?;
472 state.push(LuaValue::Str(ls));
473 }
474 None => {
475 state.push(LuaValue::Nil);
476 }
477 }
478 return Ok(1);
479 }
480
481 let level = state.check_arg_integer(arg + 1)? as i32;
483 let mut ar = DebugInfo::default();
484
485 let name = match target_state {
486 DebugThreadTarget::Current | DebugThreadTarget::Unavailable => {
487 if !state.get_stack_level(level, &mut ar) {
488 return Err(lua_vm::debug::arg_error_impl(
489 state,
490 arg + 1,
491 b"level out of range",
492 ));
493 }
494 check_cross_thread_stack(state, true, 1)?;
495 state.get_local_at(&ar, nvar)?
497 }
498 DebugThreadTarget::Other(target_state) => {
499 let mut target = crate::coro_lib::borrow_thread_rooted(state, &target_state);
500 if !target.get_stack_level(level, &mut ar) {
501 return Err(lua_vm::debug::arg_error_impl(
502 state,
503 arg + 1,
504 b"level out of range",
505 ));
506 }
507 check_cross_thread_stack(state, false, 1)?;
508 let name = target.get_local_at(&ar, nvar)?;
509 if name.is_some() {
510 let val = target.get_at(target.top_idx() - 1);
511 target.pop_n(1);
512 state.push(val);
513 }
514 name
515 }
516 };
517
518 if let Some(n) = name {
519 let ls = state.intern_str(&n)?;
520 state.push(LuaValue::Str(ls));
521 state.rotate(-2, 1)?;
522 Ok(2)
523 } else {
524 state.push_fail()?;
525 Ok(1)
526 }
527}
528
529pub(crate) fn set_local(state: &mut LuaState) -> Result<usize, LuaError> {
534 let (arg, other_thread) = getthread(state);
535 let target_state = resolve_debug_thread_target(state, &other_thread);
536
537 let level = state.check_arg_integer(arg + 1)? as i32;
538 let nvar = state.check_arg_integer(arg + 2)? as i32;
539
540 let mut ar = DebugInfo::default();
541
542 state.check_arg_any(arg + 3)?;
543 lua_vm::api::set_top(state, arg + 3)?;
544
545 let name = match target_state {
546 DebugThreadTarget::Current | DebugThreadTarget::Unavailable => {
547 if !state.get_stack_level(level, &mut ar) {
548 return Err(lua_vm::debug::arg_error_impl(
549 state,
550 arg + 1,
551 b"level out of range",
552 ));
553 }
554 check_cross_thread_stack(state, true, 1)?;
555 let name = state.set_local_at(&ar, nvar)?;
556 if name.is_none() {
557 state.pop_n(1);
558 }
559 name
560 }
561 DebugThreadTarget::Other(target_state) => {
562 let new_val = state.get_at(state.top_idx() - 1);
563 let mut target = crate::coro_lib::borrow_thread_rooted(state, &target_state);
564 if !target.get_stack_level(level, &mut ar) {
565 return Err(lua_vm::debug::arg_error_impl(
566 state,
567 arg + 1,
568 b"level out of range",
569 ));
570 }
571 check_cross_thread_stack(state, false, 1)?;
572 target.push(new_val);
573 let name = target.set_local_at(&ar, nvar)?;
574 if name.is_none() {
575 target.pop_n(1);
576 }
577 state.pop_n(1);
578 name
579 }
580 };
581
582 match name {
583 Some(n) => {
584 let ls = state.intern_str(&n)?;
585 state.push(LuaValue::Str(ls));
586 }
587 None => {
588 state.push(LuaValue::Nil);
589 }
590 }
591 Ok(1)
592}
593
594fn aux_upvalue(state: &mut LuaState, get: bool) -> Result<usize, LuaError> {
605 let n = state.check_arg_integer(2)? as i32;
606 state.check_arg_type(1, LuaType::Function)?;
607
608 let name: Option<Vec<u8>> = if get {
609 state.get_upvalue(1, n)?
611 } else {
612 state.set_upvalue(1, n)?
614 };
615
616 let name_ref = match name {
617 Some(n) => n,
618 None => return Ok(0),
619 };
620
621 let ls = state.intern_str(&name_ref)?;
622 state.push(LuaValue::Str(ls));
623
624 if get {
627 state.insert(-2)?;
628 }
629
630 Ok(if get { 2 } else { 1 })
631}
632
633pub(crate) fn get_upvalue(state: &mut LuaState) -> Result<usize, LuaError> {
636 aux_upvalue(state, true)
637}
638
639pub(crate) fn set_upvalue(state: &mut LuaState) -> Result<usize, LuaError> {
643 state.check_arg_any(3)?;
644 aux_upvalue(state, false)
645}
646
647fn check_upval(
652 state: &mut LuaState,
653 argf: i32,
654 argnup: i32,
655 require_valid: bool,
656) -> Result<(Option<UpvalId>, i32), LuaError> {
657 let nup = state.check_arg_integer(argnup)? as i32;
658 state.check_arg_type(argf, LuaType::Function)?;
659 let id: Option<UpvalId> = match state.upvalue_id(argf, nup) {
660 Ok(p) if p.is_null() => None,
661 Ok(p) => Some(p as usize),
662 Err(_) => None,
663 };
664 if require_valid && id.is_none() {
665 return Err(lua_vm::debug::arg_error_impl(
666 state,
667 argnup,
668 b"invalid upvalue index",
669 ));
670 }
671 Ok((id, nup))
672}
673
674pub(crate) fn upvalue_id(state: &mut LuaState) -> Result<usize, LuaError> {
684 let require_valid = matches!(
685 state.global().lua_version,
686 LuaVersion::V51 | LuaVersion::V52 | LuaVersion::V53
687 );
688 let (id, _nup) = check_upval(state, 1, 2, require_valid)?;
689 match id {
690 Some(uid) => {
691 lua_vm::api::push_light_userdata(state, uid as *mut core::ffi::c_void);
692 }
693 None => {
694 state.push_fail()?;
695 }
696 }
697 Ok(1)
698}
699
700pub(crate) fn upvalue_join(state: &mut LuaState) -> Result<usize, LuaError> {
704 let (_id1, n1) = check_upval(state, 1, 2, true)?;
705 let (_id2, n2) = check_upval(state, 3, 4, true)?;
706 if state.is_c_function_at(1) {
707 return Err(lua_vm::debug::arg_error_impl(
708 state,
709 1,
710 b"Lua function expected",
711 ));
712 }
713 if state.is_c_function_at(3) {
714 return Err(lua_vm::debug::arg_error_impl(
715 state,
716 3,
717 b"Lua function expected",
718 ));
719 }
720 state.join_upvalues(1, n1, 3, n2)?;
721 Ok(0)
722}
723
724pub(crate) fn hookf(state: &mut LuaState, event: i32, currentline: i32) -> Result<(), LuaError> {
730 state.get_registry_field(HOOKKEY)?;
731 state.push_thread()?;
732 if state.raw_get(-2)? == LuaType::Function {
733 let event_idx = event.clamp(0, HOOKNAMES.len() as i32 - 1) as usize;
734 let name = if event_idx == 4
735 && state.global().lua_version == lua_types::LuaVersion::V51
736 {
737 HOOKNAME_TAILRET_51
738 } else {
739 HOOKNAMES[event_idx]
740 };
741 let event_str = state.intern_str(name)?;
742 state.push(LuaValue::Str(event_str));
743
744 if currentline >= 0 {
745 state.push(LuaValue::Int(currentline as i64));
746 } else {
747 state.push(LuaValue::Nil);
748 }
749
750 state.call(2, 0)?;
751 }
752 Ok(())
755}
756
757fn make_mask(smask: &[u8], count: i32) -> u32 {
761 let mut mask: u32 = 0;
762 if smask.contains(&b'c') {
763 mask |= MASK_CALL;
764 }
765 if smask.contains(&b'r') {
766 mask |= MASK_RET;
767 }
768 if smask.contains(&b'l') {
769 mask |= MASK_LINE;
770 }
771 if count > 0 {
772 mask |= MASK_COUNT;
773 }
774 mask
775}
776
777fn unmake_mask(mask: u32) -> Vec<u8> {
781 let mut smask = Vec::with_capacity(3);
782 if mask & MASK_CALL != 0 {
783 smask.push(b'c');
784 }
785 if mask & MASK_RET != 0 {
786 smask.push(b'r');
787 }
788 if mask & MASK_LINE != 0 {
789 smask.push(b'l');
790 }
791 smask
792}
793
794pub(crate) fn set_hook(state: &mut LuaState) -> Result<usize, LuaError> {
798 let (arg, other_thread) = getthread(state);
799 let target_is_self = other_thread.is_none();
800
801 let hook_active: bool;
802 let mask: u32;
803 let count: i32;
804
805 if matches!(state.type_at(arg + 1), LuaType::None | LuaType::Nil) {
806 lua_vm::api::set_top(state, arg + 1)?;
807 hook_active = false;
808 mask = 0;
809 count = 0;
810 } else {
811 let smask: Vec<u8> = state.check_arg_string(arg + 2)?.to_vec();
812 state.check_arg_type(arg + 1, LuaType::Function)?;
813 count = state.opt_arg_integer(arg + 3, 0)? as i32;
814 hook_active = true;
815 mask = make_mask(&smask, count);
816 }
817
818 if !state.get_or_create_registry_subtable(HOOKKEY)? {
819 let k = state.intern_str(b"k")?;
822 state.push(LuaValue::Str(k));
823 state.set_field(-2, b"__mode")?;
824 state.push_value_at(-1)?;
825 state.set_metatable(-2)?;
826 }
827
828 check_cross_thread_stack(state, target_is_self, 1)?;
829 let target_state = resolve_debug_thread_target(state, &other_thread);
830 match &target_state {
831 DebugThreadTarget::Other(st) => {
832 st.borrow_mut().ensure_stack(1, "stack overflow")?;
833 }
834 DebugThreadTarget::Current => {}
835 DebugThreadTarget::Unavailable => {}
836 }
837
838 if target_is_self {
839 state.push_thread()?;
840 } else {
841 let thr = other_thread
848 .clone()
849 .expect("other_thread is Some when target_is_self is false");
850 state.push(lua_types::value::LuaValue::Thread(thr));
851 }
852 state.push_value_at(arg + 1)?;
853 state.raw_set(-3)?;
854
855 let hook_box: Option<Box<dyn FnMut(&mut LuaState, &lua_vm::debug::LuaDebug)>> = if hook_active {
856 Some(Box::new(|st, ar| {
857 let _ = hookf(st, ar.event, ar.currentline);
858 }))
859 } else {
860 None
861 };
862 match target_state {
863 DebugThreadTarget::Current => {
864 lua_vm::debug::set_hook(state, hook_box, mask as i32, count);
865 }
866 DebugThreadTarget::Other(target_state) => {
867 lua_vm::debug::set_hook(&mut target_state.borrow_mut(), hook_box, mask as i32, count);
868 }
869 DebugThreadTarget::Unavailable => {
870 return Ok(0);
874 }
875 }
876
877 Ok(0)
878}
879
880pub(crate) fn get_hook(state: &mut LuaState) -> Result<usize, LuaError> {
884 let (_arg, other_thread) = getthread(state);
885 let target_is_self = other_thread.is_none();
886 let target_state = resolve_debug_thread_target(state, &other_thread);
887
888 let (mask, hook_is_set, hook_is_internal, hook_count) = match target_state {
889 DebugThreadTarget::Current => (
890 state.get_hook_mask(),
891 state.hook_is_set(),
892 state.hook_is_internal_lua_hook(),
893 state.get_hook_count(),
894 ),
895 DebugThreadTarget::Other(target_state) => {
896 let mut target_state = target_state.borrow_mut();
897 (
898 target_state.get_hook_mask(),
899 target_state.hook_is_set(),
900 target_state.hook_is_internal_lua_hook(),
901 target_state.get_hook_count(),
902 )
903 }
904 DebugThreadTarget::Unavailable => (0u32, false, false, 0i32),
905 };
906
907 if !hook_is_set {
908 state.push_fail()?;
909 return Ok(1);
910 }
911
912 if !hook_is_internal {
913 let s = state.intern_str(b"external hook")?;
914 state.push(LuaValue::Str(s));
915 } else {
916 state.get_registry_field(HOOKKEY)?;
917 check_cross_thread_stack(state, target_is_self, 1)?;
918 if target_is_self {
919 state.push_thread()?;
920 } else {
921 let key_thread = other_thread
922 .expect("other_thread is Some when target_is_self is false")
923 .clone();
924 state.push(lua_types::value::LuaValue::Thread(key_thread));
925 }
926 state.raw_get(-2)?;
927 state.remove(-2)?;
928 }
929
930 let smask = unmake_mask(mask);
931 let ls = state.intern_str(&smask)?;
932 state.push(LuaValue::Str(ls));
933
934 state.push(LuaValue::Int(hook_count as i64));
935
936 Ok(3)
937}
938
939pub(crate) fn debug_interactive(state: &mut LuaState) -> Result<usize, LuaError> {
946 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
947 {
948 let _ = state;
949 return Err(LuaError::runtime(format_args!(
950 "debug.debug interactive stdin not available in this host"
951 )));
952 }
953
954 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
955 {
956 let stdin = io::stdin();
957 loop {
958 eprint!("lua_debug> ");
959 let _ = io::stderr().flush();
960
961 let mut line = String::new();
964 let n = stdin
965 .lock()
966 .read_line(&mut line)
967 .map_err(|e| LuaError::runtime(format_args!("stdin read error: {}", e)))?;
968
969 if n == 0 || line == "cont\n" {
970 return Ok(0);
971 }
972
973 let bytes: &[u8] = line.as_bytes();
974
975 let result = state
976 .load_buffer(bytes, b"=(debug command)", None)
977 .and_then(|_| state.protected_call(0, 0, 0));
978
979 if result.is_err() {
980 eprintln!("(error in debug command)");
984 state.pop_n(1);
985 }
986
987 lua_vm::api::set_top(state, 0)?;
988 }
989 }
990}
991
992pub(crate) fn traceback(state: &mut LuaState) -> Result<usize, LuaError> {
999 let (arg, other_thread) = getthread(state);
1000 let target_is_self = other_thread.is_none();
1001
1002 let msg_owned: Option<Vec<u8>> = state
1004 .to_lua_string(arg + 1)
1005 .map(|s: GcRef<LuaString>| s.as_bytes().to_vec());
1006
1007 let arg1_ty = state.type_at(arg + 1);
1008 if msg_owned.is_none() && !matches!(arg1_ty, LuaType::None | LuaType::Nil) {
1009 state.push_value_at(arg + 1)?;
1010 } else {
1011 let default_level: i64 = if target_is_self { 1 } else { 0 };
1012 let level = state.opt_arg_integer(arg + 2, default_level)? as i32;
1013
1014 match resolve_debug_thread_target(state, &other_thread) {
1015 DebugThreadTarget::Current => {
1016 crate::auxlib::traceback(state, None, msg_owned.as_deref(), level)?;
1017 }
1018 DebugThreadTarget::Other(target_state) => {
1019 let mut target_state = crate::coro_lib::borrow_thread_rooted(state, &target_state);
1020 crate::auxlib::traceback(
1021 state,
1022 Some(&mut *target_state),
1023 msg_owned.as_deref(),
1024 level,
1025 )?;
1026 }
1027 DebugThreadTarget::Unavailable => {
1028 crate::auxlib::traceback(state, None, msg_owned.as_deref(), level)?;
1029 }
1030 }
1031 }
1032 Ok(1)
1033}
1034
1035pub(crate) fn set_c_stack_limit(state: &mut LuaState) -> Result<usize, LuaError> {
1039 let limit = state.check_arg_integer(1)? as i32;
1040 let res = state.set_c_stack_limit(limit)?;
1041 state.push(LuaValue::Int(res as i64));
1042 Ok(1)
1043}
1044
1045pub(crate) const DBLIB: &[(&[u8], LibFn)] = &[
1050 (b"debug", debug_interactive as LibFn),
1051 (b"getuservalue", get_uservalue as LibFn),
1052 (b"gethook", get_hook as LibFn),
1053 (b"getinfo", get_info as LibFn),
1054 (b"getlocal", get_local as LibFn),
1055 (b"getregistry", get_registry as LibFn),
1056 (b"getmetatable", get_metatable as LibFn),
1057 (b"getupvalue", get_upvalue as LibFn),
1058 (b"upvaluejoin", upvalue_join as LibFn),
1059 (b"upvalueid", upvalue_id as LibFn),
1060 (b"setuservalue", set_uservalue as LibFn),
1061 (b"sethook", set_hook as LibFn),
1062 (b"setlocal", set_local as LibFn),
1063 (b"setmetatable", set_metatable as LibFn),
1064 (b"setupvalue", set_upvalue as LibFn),
1065 (b"traceback", traceback as LibFn),
1066 (b"setcstacklimit", set_c_stack_limit as LibFn),
1067];
1068
1069const DBLIB_DROP_V51: &[&[u8]] = &[
1076 b"getuservalue",
1077 b"setuservalue",
1078 b"upvalueid",
1079 b"upvaluejoin",
1080 b"setcstacklimit",
1081];
1082
1083pub fn open_debug(state: &mut LuaState) -> Result<usize, LuaError> {
1091 use lua_types::LuaVersion;
1092 let version = state.global().lua_version;
1093 let is_v51 = matches!(version, LuaVersion::V51);
1094 let has_setcstacklimit = matches!(version, LuaVersion::V54);
1095
1096 let filtered: Vec<(&[u8], LibFn)> = DBLIB
1097 .iter()
1098 .filter(|(name, _)| {
1099 if !has_setcstacklimit && *name == b"setcstacklimit".as_slice() {
1100 return false;
1101 }
1102 if is_v51 && DBLIB_DROP_V51.contains(name) {
1103 return false;
1104 }
1105 true
1106 })
1107 .copied()
1108 .collect();
1109 state.new_lib(&filtered)?;
1110
1111 if is_v51 {
1112 state.push_c_function(crate::base::debug_getfenv_fn)?;
1117 state.set_field(-2, b"getfenv")?;
1118 state.push_c_function(crate::base::debug_setfenv_fn)?;
1119 state.set_field(-2, b"setfenv")?;
1120 }
1121
1122 Ok(1)
1123}