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;
86
87#[derive(Clone)]
88enum DebugThreadTarget {
89 Current,
90 Other(Rc<RefCell<LuaState>>),
91 Unavailable,
92}
93
94fn resolve_debug_thread_target(
95 state: &LuaState,
96 target_thread: &Option<GcRef<lua_types::value::LuaThread>>,
97) -> DebugThreadTarget {
98 let Some(thread) = target_thread else {
99 return DebugThreadTarget::Current;
100 };
101
102 if thread.id == state.cached_thread_id {
103 return DebugThreadTarget::Current;
104 }
105
106 let g = state.global();
107 if thread.id == g.main_thread_id {
108 DebugThreadTarget::Unavailable
109 } else {
110 g.threads
111 .get(&thread.id)
112 .map(|entry| DebugThreadTarget::Other(entry.state.clone()))
113 .unwrap_or(DebugThreadTarget::Unavailable)
114 }
115}
116
117fn check_cross_thread_stack(
127 state: &mut LuaState,
128 target_is_self: bool,
129 n: i32,
130) -> Result<(), LuaError> {
131 if !target_is_self {
132 state.ensure_stack(n, "stack overflow")?;
136 }
137 Ok(())
138}
139
140fn getthread(state: &mut LuaState) -> (i32, Option<GcRef<lua_types::value::LuaThread>>) {
144 if state.type_at(1) == LuaType::Thread {
145 let thread = state.to_thread_at(1);
146 return (1, thread);
147 }
148 (0, None)
149}
150
151fn settabss(state: &mut LuaState, k: &[u8], v: Option<&[u8]>) -> Result<(), LuaError> {
154 match v {
155 Some(s) => {
156 let ls = state.intern_str(s)?;
157 state.push(LuaValue::Str(ls));
158 }
159 None => {
160 state.push(LuaValue::Nil);
161 }
162 }
163 state.set_field(-2, k)
164}
165
166fn settabsi(state: &mut LuaState, k: &[u8], v: i32) -> Result<(), LuaError> {
169 state.push(LuaValue::Int(v as i64));
170 state.set_field(-2, k)
171}
172
173fn settabsb(state: &mut LuaState, k: &[u8], v: bool) -> Result<(), LuaError> {
176 state.push(LuaValue::Bool(v));
177 state.set_field(-2, k)
178}
179
180fn treat_stack_option(
187 state: &mut LuaState,
188 target_is_self: bool,
189 fname: &[u8],
190) -> Result<(), LuaError> {
191 if target_is_self {
192 state.rotate(-2, 1)?;
193 } else {
194 state.push(LuaValue::Nil);
198 }
199 state.set_field(-2, fname)
200}
201
202fn move_stack_option_from_target(
203 state: &mut LuaState,
204 target: &mut LuaState,
205 fname: &[u8],
206) -> Result<(), LuaError> {
207 let val = target.get_at(target.top_idx() - 1);
208 target.pop_n(1);
209 state.push(val);
210 state.set_field(-2, fname)
211}
212
213pub(crate) fn get_registry(state: &mut LuaState) -> Result<usize, LuaError> {
218 state.push_registry()?;
219 Ok(1)
220}
221
222pub(crate) fn get_metatable(state: &mut LuaState) -> Result<usize, LuaError> {
225 state.check_arg_any(1)?;
226 if !state.get_metatable(1)? {
227 state.push(LuaValue::Nil);
228 }
229 Ok(1)
230}
231
232pub(crate) fn set_metatable(state: &mut LuaState) -> Result<usize, LuaError> {
236 let t = state.type_at(2);
237 if !(t == LuaType::Nil || t == LuaType::Table) {
238 let got = state.arg(2);
239 return Err(LuaError::type_arg_error(2, "nil or table", &got));
240 }
241 lua_vm::api::set_top(state, 2)?;
242 state.set_metatable(1)?;
243 Ok(1)
244}
245
246pub(crate) fn get_uservalue(state: &mut LuaState) -> Result<usize, LuaError> {
251 let n = state.opt_arg_integer(2, 1)? as i32;
252 if state.type_at(1) != LuaType::UserData {
253 state.push_fail()?;
254 return Ok(1);
255 }
256 let ty = state.get_iuservalue(1, n)?;
257 if ty != LuaType::None {
258 state.push(LuaValue::Bool(true));
259 return Ok(2);
260 }
261 Ok(1)
262}
263
264pub(crate) fn set_uservalue(state: &mut LuaState) -> Result<usize, LuaError> {
268 let n = state.opt_arg_integer(3, 1)? as i32;
269 state.check_arg_type(1, LuaType::UserData)?;
270 state.check_arg_any(2)?;
271 lua_vm::api::set_top(state, 2)?;
272 if !state.set_iuservalue(1, n)? {
273 state.push_fail()?;
274 }
275 Ok(1)
276}
277
278pub(crate) fn get_info(state: &mut LuaState) -> Result<usize, LuaError> {
283 let mut ar = DebugInfo::default();
284
285 let (arg, other_thread) = getthread(state);
286 let target_is_self = other_thread.is_none();
287 let target_state = resolve_debug_thread_target(state, &other_thread);
288
289 let raw_opts: Vec<u8> = state.opt_arg_string(arg + 2, b"flnSrtu")?.to_vec();
291
292 check_cross_thread_stack(state, target_is_self, 3)?;
293
294 if raw_opts.first() == Some(&b'>') {
295 return Err(lua_vm::debug::arg_error_impl(
296 state,
297 arg + 2,
298 b"invalid option '>'",
299 ));
300 }
301
302 let options: Vec<u8>;
304 let info_target_owner: Option<Rc<RefCell<LuaState>>>;
305 let mut info_target: Option<crate::coro_lib::RootedThreadBorrow<'_>> = None;
306 let mut info_target_is_self = target_is_self;
307
308 if state.type_at(arg + 1) == LuaType::Function {
309 let mut prefixed = Vec::with_capacity(raw_opts.len() + 1);
310 prefixed.push(b'>');
311 prefixed.extend_from_slice(&raw_opts);
312 options = prefixed;
313
314 if target_is_self {
315 state.push_value_at(arg + 1)?;
316 } else {
317 }
321
322 if state.get_debug_info(&options, &mut ar).is_err() {
324 return Err(lua_vm::debug::arg_error_impl(
325 state,
326 arg + 2,
327 b"invalid option",
328 ));
329 }
330 } else {
331 options = raw_opts;
332
333 let level = state.check_arg_integer(arg + 1)? as i32;
334 match target_state {
335 DebugThreadTarget::Current | DebugThreadTarget::Unavailable => {
336 info_target_is_self = true;
337 if !state.get_stack_level(level, &mut ar) {
338 state.push_fail()?;
339 return Ok(1);
340 }
341
342 if state.get_debug_info(&options, &mut ar).is_err() {
343 return Err(lua_vm::debug::arg_error_impl(
344 state,
345 arg + 2,
346 b"invalid option",
347 ));
348 }
349 }
350 DebugThreadTarget::Other(target_state) => {
351 info_target_owner = Some(target_state);
352 let mut target = crate::coro_lib::borrow_thread_rooted(
353 state,
354 info_target_owner
355 .as_ref()
356 .expect("target owner just stored"),
357 );
358 if !target.get_stack_level(level, &mut ar) {
359 state.push_fail()?;
360 return Ok(1);
361 }
362 if target.get_debug_info(&options, &mut ar).is_err() {
363 return Err(lua_vm::debug::arg_error_impl(
364 state,
365 arg + 2,
366 b"invalid option",
367 ));
368 }
369 target.resnapshot();
370 info_target = Some(target);
371 }
372 }
373 }
374
375 let result_tbl = state.new_table();
376 state.push(LuaValue::Table(result_tbl));
377
378 if options.contains(&b'S') {
379 let src = state.intern_str(ar.source_bytes())?;
380 state.push(LuaValue::Str(src));
381 state.set_field(-2, b"source")?;
382
383 settabss(state, b"short_src", Some(ar.short_src_bytes()))?;
384 settabsi(state, b"linedefined", ar.linedefined)?;
385 settabsi(state, b"lastlinedefined", ar.lastlinedefined)?;
386 settabss(state, b"what", Some(ar.what_bytes()))?;
387 }
388 if options.contains(&b'l') {
389 settabsi(state, b"currentline", ar.currentline)?;
390 }
391 if options.contains(&b'u') {
392 settabsi(state, b"nups", ar.nups as i32)?;
393 if !matches!(state.global().lua_version, LuaVersion::V51) {
394 settabsi(state, b"nparams", ar.nparams as i32)?;
395 settabsb(state, b"isvararg", ar.isvararg)?;
396 }
397 }
398 if options.contains(&b'n') {
399 let name_opt: Option<&[u8]> = ar.name.as_deref();
400 settabss(state, b"name", name_opt)?;
401 settabss(state, b"namewhat", Some(ar.namewhat_bytes()))?;
402 }
403 if options.contains(&b'r') {
404 settabsi(state, b"ftransfer", ar.ftransfer as i32)?;
405 settabsi(state, b"ntransfer", ar.ntransfer as i32)?;
406 }
407 if options.contains(&b't') {
408 settabsb(state, b"istailcall", ar.istailcall)?;
409 if matches!(state.global().lua_version, LuaVersion::V55) {
410 settabsi(state, b"extraargs", ar.extraargs as i32)?;
411 }
412 }
413 if options.contains(&b'L') {
418 if info_target_is_self {
419 treat_stack_option(state, true, b"activelines")?;
420 } else if let Some(target) = info_target.as_mut() {
421 move_stack_option_from_target(state, &mut **target, b"activelines")?;
422 } else {
423 state.push(LuaValue::Nil);
424 state.set_field(-2, b"activelines")?;
425 }
426 }
427 if options.contains(&b'f') {
428 if info_target_is_self {
429 treat_stack_option(state, true, b"func")?;
430 } else if let Some(target) = info_target.as_mut() {
431 move_stack_option_from_target(state, &mut **target, b"func")?;
432 } else {
433 state.push(LuaValue::Nil);
434 state.set_field(-2, b"func")?;
435 }
436 }
437
438 Ok(1)
439}
440
441fn function_arg_form_supported(state: &LuaState) -> bool {
452 !matches!(state.global().lua_version, LuaVersion::V51)
453}
454
455pub(crate) fn get_local(state: &mut LuaState) -> Result<usize, LuaError> {
463 let (arg, other_thread) = getthread(state);
464 let target_state = resolve_debug_thread_target(state, &other_thread);
465
466 let nvar = state.check_arg_integer(arg + 2)? as i32;
467
468 if function_arg_form_supported(state) && state.type_at(arg + 1) == LuaType::Function {
469 state.push_value_at(arg + 1)?;
470 let name = state.get_param_name(0, nvar)?;
471 match name {
472 Some(n) => {
473 let ls = state.intern_str(&n)?;
474 state.push(LuaValue::Str(ls));
475 }
476 None => {
477 state.push(LuaValue::Nil);
478 }
479 }
480 return Ok(1);
481 }
482
483 let level = state.check_arg_integer(arg + 1)? as i32;
485 let mut ar = DebugInfo::default();
486
487 let name = match target_state {
488 DebugThreadTarget::Current | DebugThreadTarget::Unavailable => {
489 if !state.get_stack_level(level, &mut ar) {
490 return Err(lua_vm::debug::arg_error_impl(
491 state,
492 arg + 1,
493 b"level out of range",
494 ));
495 }
496 check_cross_thread_stack(state, true, 1)?;
497 state.get_local_at(&ar, nvar)?
499 }
500 DebugThreadTarget::Other(target_state) => {
501 let mut target = crate::coro_lib::borrow_thread_rooted(state, &target_state);
502 if !target.get_stack_level(level, &mut ar) {
503 return Err(lua_vm::debug::arg_error_impl(
504 state,
505 arg + 1,
506 b"level out of range",
507 ));
508 }
509 check_cross_thread_stack(state, false, 1)?;
510 let name = target.get_local_at(&ar, nvar)?;
511 if name.is_some() {
512 let val = target.get_at(target.top_idx() - 1);
513 target.pop_n(1);
514 state.push(val);
515 }
516 name
517 }
518 };
519
520 if let Some(n) = name {
521 let ls = state.intern_str(&n)?;
522 state.push(LuaValue::Str(ls));
523 state.rotate(-2, 1)?;
524 Ok(2)
525 } else {
526 state.push_fail()?;
527 Ok(1)
528 }
529}
530
531pub(crate) fn set_local(state: &mut LuaState) -> Result<usize, LuaError> {
536 let (arg, other_thread) = getthread(state);
537 let target_state = resolve_debug_thread_target(state, &other_thread);
538
539 let level = state.check_arg_integer(arg + 1)? as i32;
540 let nvar = state.check_arg_integer(arg + 2)? as i32;
541
542 let mut ar = DebugInfo::default();
543
544 state.check_arg_any(arg + 3)?;
545 lua_vm::api::set_top(state, arg + 3)?;
546
547 let name = match target_state {
548 DebugThreadTarget::Current | DebugThreadTarget::Unavailable => {
549 if !state.get_stack_level(level, &mut ar) {
550 return Err(lua_vm::debug::arg_error_impl(
551 state,
552 arg + 1,
553 b"level out of range",
554 ));
555 }
556 check_cross_thread_stack(state, true, 1)?;
557 let name = state.set_local_at(&ar, nvar)?;
558 if name.is_none() {
559 state.pop_n(1);
560 }
561 name
562 }
563 DebugThreadTarget::Other(target_state) => {
564 let new_val = state.get_at(state.top_idx() - 1);
565 let mut target = crate::coro_lib::borrow_thread_rooted(state, &target_state);
566 if !target.get_stack_level(level, &mut ar) {
567 return Err(lua_vm::debug::arg_error_impl(
568 state,
569 arg + 1,
570 b"level out of range",
571 ));
572 }
573 check_cross_thread_stack(state, false, 1)?;
574 target.push(new_val);
575 let name = target.set_local_at(&ar, nvar)?;
576 if name.is_none() {
577 target.pop_n(1);
578 }
579 state.pop_n(1);
580 name
581 }
582 };
583
584 match name {
585 Some(n) => {
586 let ls = state.intern_str(&n)?;
587 state.push(LuaValue::Str(ls));
588 }
589 None => {
590 state.push(LuaValue::Nil);
591 }
592 }
593 Ok(1)
594}
595
596fn aux_upvalue(state: &mut LuaState, get: bool) -> Result<usize, LuaError> {
607 let n = state.check_arg_integer(2)? as i32;
608 state.check_arg_type(1, LuaType::Function)?;
609
610 let name: Option<Vec<u8>> = if get {
611 state.get_upvalue(1, n)?
613 } else {
614 state.set_upvalue(1, n)?
616 };
617
618 let name_ref = match name {
619 Some(n) => n,
620 None => return Ok(0),
621 };
622
623 let ls = state.intern_str(&name_ref)?;
624 state.push(LuaValue::Str(ls));
625
626 if get {
629 state.insert(-2)?;
630 }
631
632 Ok(if get { 2 } else { 1 })
633}
634
635pub(crate) fn get_upvalue(state: &mut LuaState) -> Result<usize, LuaError> {
638 aux_upvalue(state, true)
639}
640
641pub(crate) fn set_upvalue(state: &mut LuaState) -> Result<usize, LuaError> {
645 state.check_arg_any(3)?;
646 aux_upvalue(state, false)
647}
648
649fn check_upval(
654 state: &mut LuaState,
655 argf: i32,
656 argnup: i32,
657 require_valid: bool,
658) -> Result<(Option<UpvalId>, i32), LuaError> {
659 let nup = state.check_arg_integer(argnup)? as i32;
660 state.check_arg_type(argf, LuaType::Function)?;
661 let id: Option<UpvalId> = match state.upvalue_id(argf, nup) {
666 Ok(p) if p.is_null() => None,
667 Ok(p) => Some(p as usize),
668 Err(_) => None,
669 };
670 if require_valid && id.is_none() {
671 return Err(lua_vm::debug::arg_error_impl(
672 state,
673 argnup,
674 b"invalid upvalue index",
675 ));
676 }
677 Ok((id, nup))
678}
679
680pub(crate) fn upvalue_id(state: &mut LuaState) -> Result<usize, LuaError> {
690 let require_valid = matches!(
691 state.global().lua_version,
692 LuaVersion::V51 | LuaVersion::V52 | LuaVersion::V53
693 );
694 let (id, _nup) = check_upval(state, 1, 2, require_valid)?;
695 match id {
696 Some(uid) => {
697 lua_vm::api::push_light_userdata(state, uid as *mut core::ffi::c_void);
698 }
699 None => {
700 state.push_fail()?;
701 }
702 }
703 Ok(1)
704}
705
706pub(crate) fn upvalue_join(state: &mut LuaState) -> Result<usize, LuaError> {
710 let (_id1, n1) = check_upval(state, 1, 2, true)?;
711 let (_id2, n2) = check_upval(state, 3, 4, true)?;
712 if state.is_c_function_at(1) {
713 return Err(lua_vm::debug::arg_error_impl(
714 state,
715 1,
716 b"Lua function expected",
717 ));
718 }
719 if state.is_c_function_at(3) {
720 return Err(lua_vm::debug::arg_error_impl(
721 state,
722 3,
723 b"Lua function expected",
724 ));
725 }
726 state.join_upvalues(1, n1, 3, n2)?;
727 Ok(0)
728}
729
730pub(crate) fn hookf(state: &mut LuaState, event: i32, currentline: i32) -> Result<(), LuaError> {
736 state.get_registry_field(HOOKKEY)?;
737 state.push_thread()?;
738 if state.raw_get(-2)? == LuaType::Function {
739 let event_idx = event.clamp(0, HOOKNAMES.len() as i32 - 1) as usize;
740 let name = if event_idx == 4
741 && state.global().lua_version == lua_types::LuaVersion::V51
742 {
743 HOOKNAME_TAILRET_51
744 } else {
745 HOOKNAMES[event_idx]
746 };
747 let event_str = state.intern_str(name)?;
748 state.push(LuaValue::Str(event_str));
749
750 if currentline >= 0 {
751 state.push(LuaValue::Int(currentline as i64));
752 } else {
753 state.push(LuaValue::Nil);
754 }
755
756 state.call(2, 0)?;
757 }
758 Ok(())
761}
762
763fn make_mask(smask: &[u8], count: i32) -> u32 {
767 let mut mask: u32 = 0;
768 if smask.contains(&b'c') {
769 mask |= MASK_CALL;
770 }
771 if smask.contains(&b'r') {
772 mask |= MASK_RET;
773 }
774 if smask.contains(&b'l') {
775 mask |= MASK_LINE;
776 }
777 if count > 0 {
778 mask |= MASK_COUNT;
779 }
780 mask
781}
782
783fn unmake_mask(mask: u32) -> Vec<u8> {
787 let mut smask = Vec::with_capacity(3);
788 if mask & MASK_CALL != 0 {
789 smask.push(b'c');
790 }
791 if mask & MASK_RET != 0 {
792 smask.push(b'r');
793 }
794 if mask & MASK_LINE != 0 {
795 smask.push(b'l');
796 }
797 smask
798}
799
800pub(crate) fn set_hook(state: &mut LuaState) -> Result<usize, LuaError> {
804 let (arg, other_thread) = getthread(state);
805 let target_is_self = other_thread.is_none();
806
807 let hook_active: bool;
808 let mask: u32;
809 let count: i32;
810
811 if matches!(state.type_at(arg + 1), LuaType::None | LuaType::Nil) {
812 lua_vm::api::set_top(state, arg + 1)?;
813 hook_active = false;
814 mask = 0;
815 count = 0;
816 } else {
817 let smask: Vec<u8> = state.check_arg_string(arg + 2)?.to_vec();
818 state.check_arg_type(arg + 1, LuaType::Function)?;
819 count = state.opt_arg_integer(arg + 3, 0)? as i32;
820 hook_active = true;
821 mask = make_mask(&smask, count);
822 }
823
824 if !state.get_or_create_registry_subtable(HOOKKEY)? {
825 let k = state.intern_str(b"k")?;
828 state.push(LuaValue::Str(k));
829 state.set_field(-2, b"__mode")?;
830 state.push_value_at(-1)?;
831 state.set_metatable(-2)?;
832 }
833
834 check_cross_thread_stack(state, target_is_self, 1)?;
835 let target_state = resolve_debug_thread_target(state, &other_thread);
836 match &target_state {
837 DebugThreadTarget::Other(st) => {
838 st.borrow_mut().ensure_stack(1, "stack overflow")?;
839 }
840 DebugThreadTarget::Current => {}
841 DebugThreadTarget::Unavailable => {}
842 }
843
844 if target_is_self {
845 state.push_thread()?;
846 } else {
847 let thr = other_thread
854 .clone()
855 .expect("other_thread is Some when target_is_self is false");
856 state.push(lua_types::value::LuaValue::Thread(thr));
857 }
858 state.push_value_at(arg + 1)?;
859 state.raw_set(-3)?;
860
861 let hook_box: Option<Box<dyn FnMut(&mut LuaState, &lua_vm::debug::LuaDebug)>> = if hook_active {
862 Some(Box::new(|st, ar| {
863 let _ = hookf(st, ar.event, ar.currentline);
864 }))
865 } else {
866 None
867 };
868 match target_state {
869 DebugThreadTarget::Current => {
870 lua_vm::debug::set_hook(state, hook_box, mask as i32, count);
871 }
872 DebugThreadTarget::Other(target_state) => {
873 lua_vm::debug::set_hook(&mut target_state.borrow_mut(), hook_box, mask as i32, count);
874 }
875 DebugThreadTarget::Unavailable => {
876 return Ok(0);
880 }
881 }
882
883 Ok(0)
884}
885
886pub(crate) fn get_hook(state: &mut LuaState) -> Result<usize, LuaError> {
890 let (_arg, other_thread) = getthread(state);
891 let target_is_self = other_thread.is_none();
892 let target_state = resolve_debug_thread_target(state, &other_thread);
893
894 let (mask, hook_is_set, hook_is_internal, hook_count) = match target_state {
895 DebugThreadTarget::Current => (
896 state.get_hook_mask(),
897 state.hook_is_set(),
898 state.hook_is_internal_lua_hook(),
899 state.get_hook_count(),
900 ),
901 DebugThreadTarget::Other(target_state) => {
902 let mut target_state = target_state.borrow_mut();
903 (
904 target_state.get_hook_mask(),
905 target_state.hook_is_set(),
906 target_state.hook_is_internal_lua_hook(),
907 target_state.get_hook_count(),
908 )
909 }
910 DebugThreadTarget::Unavailable => (0u32, false, false, 0i32),
911 };
912
913 if !hook_is_set {
914 state.push_fail()?;
915 return Ok(1);
916 }
917
918 if !hook_is_internal {
919 let s = state.intern_str(b"external hook")?;
920 state.push(LuaValue::Str(s));
921 } else {
922 state.get_registry_field(HOOKKEY)?;
923 check_cross_thread_stack(state, target_is_self, 1)?;
924 if target_is_self {
925 state.push_thread()?;
926 } else {
927 let key_thread = other_thread
928 .expect("other_thread is Some when target_is_self is false")
929 .clone();
930 state.push(lua_types::value::LuaValue::Thread(key_thread));
931 }
932 state.raw_get(-2)?;
933 state.remove(-2)?;
934 }
935
936 let smask = unmake_mask(mask);
937 let ls = state.intern_str(&smask)?;
938 state.push(LuaValue::Str(ls));
939
940 state.push(LuaValue::Int(hook_count as i64));
941
942 Ok(3)
943}
944
945pub(crate) fn debug_interactive(state: &mut LuaState) -> Result<usize, LuaError> {
952 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
953 {
954 let _ = state;
955 return Err(LuaError::runtime(format_args!(
956 "debug.debug interactive stdin not available in this host"
957 )));
958 }
959
960 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
961 {
962 let stdin = io::stdin();
963 loop {
964 eprint!("lua_debug> ");
965 let _ = io::stderr().flush();
966
967 let mut line = String::new();
970 let n = stdin
971 .lock()
972 .read_line(&mut line)
973 .map_err(|e| LuaError::runtime(format_args!("stdin read error: {}", e)))?;
974
975 if n == 0 || line == "cont\n" {
976 return Ok(0);
977 }
978
979 let bytes: &[u8] = line.as_bytes();
980
981 let result = state
982 .load_buffer(bytes, b"=(debug command)", None)
983 .and_then(|_| state.protected_call(0, 0, 0));
984
985 if result.is_err() {
986 eprintln!("(error in debug command)");
991 state.pop_n(1);
992 }
993
994 lua_vm::api::set_top(state, 0)?;
995 }
996 }
997}
998
999pub(crate) fn traceback(state: &mut LuaState) -> Result<usize, LuaError> {
1006 let (arg, other_thread) = getthread(state);
1007 let target_is_self = other_thread.is_none();
1008
1009 let msg_owned: Option<Vec<u8>> = state
1011 .to_lua_string(arg + 1)
1012 .map(|s: GcRef<LuaString>| s.as_bytes().to_vec());
1013
1014 let arg1_ty = state.type_at(arg + 1);
1015 if msg_owned.is_none() && !matches!(arg1_ty, LuaType::None | LuaType::Nil) {
1016 state.push_value_at(arg + 1)?;
1017 } else {
1018 let default_level: i64 = if target_is_self { 1 } else { 0 };
1019 let level = state.opt_arg_integer(arg + 2, default_level)? as i32;
1020
1021 match resolve_debug_thread_target(state, &other_thread) {
1022 DebugThreadTarget::Current => {
1023 crate::auxlib::traceback(state, None, msg_owned.as_deref(), level)?;
1024 }
1025 DebugThreadTarget::Other(target_state) => {
1026 let mut target_state = crate::coro_lib::borrow_thread_rooted(state, &target_state);
1027 crate::auxlib::traceback(
1028 state,
1029 Some(&mut *target_state),
1030 msg_owned.as_deref(),
1031 level,
1032 )?;
1033 }
1034 DebugThreadTarget::Unavailable => {
1035 crate::auxlib::traceback(state, None, msg_owned.as_deref(), level)?;
1036 }
1037 }
1038 }
1039 Ok(1)
1040}
1041
1042pub(crate) fn set_c_stack_limit(state: &mut LuaState) -> Result<usize, LuaError> {
1046 let limit = state.check_arg_integer(1)? as i32;
1047 let res = state.set_c_stack_limit(limit)?;
1048 state.push(LuaValue::Int(res as i64));
1049 Ok(1)
1050}
1051
1052pub(crate) const DBLIB: &[(&[u8], LibFn)] = &[
1057 (b"debug", debug_interactive as LibFn),
1058 (b"getuservalue", get_uservalue as LibFn),
1059 (b"gethook", get_hook as LibFn),
1060 (b"getinfo", get_info as LibFn),
1061 (b"getlocal", get_local as LibFn),
1062 (b"getregistry", get_registry as LibFn),
1063 (b"getmetatable", get_metatable as LibFn),
1064 (b"getupvalue", get_upvalue as LibFn),
1065 (b"upvaluejoin", upvalue_join as LibFn),
1066 (b"upvalueid", upvalue_id as LibFn),
1067 (b"setuservalue", set_uservalue as LibFn),
1068 (b"sethook", set_hook as LibFn),
1069 (b"setlocal", set_local as LibFn),
1070 (b"setmetatable", set_metatable as LibFn),
1071 (b"setupvalue", set_upvalue as LibFn),
1072 (b"traceback", traceback as LibFn),
1073 (b"setcstacklimit", set_c_stack_limit as LibFn),
1074];
1075
1076const DBLIB_DROP_V51: &[&[u8]] = &[
1083 b"getuservalue",
1084 b"setuservalue",
1085 b"upvalueid",
1086 b"upvaluejoin",
1087 b"setcstacklimit",
1088];
1089
1090pub fn open_debug(state: &mut LuaState) -> Result<usize, LuaError> {
1098 use lua_types::LuaVersion;
1099 let version = state.global().lua_version;
1100 let is_v51 = matches!(version, LuaVersion::V51);
1101 let has_setcstacklimit = matches!(version, LuaVersion::V54);
1102
1103 let filtered: Vec<(&[u8], LibFn)> = DBLIB
1104 .iter()
1105 .filter(|(name, _)| {
1106 if !has_setcstacklimit && *name == b"setcstacklimit".as_slice() {
1107 return false;
1108 }
1109 if is_v51 && DBLIB_DROP_V51.contains(name) {
1110 return false;
1111 }
1112 true
1113 })
1114 .copied()
1115 .collect();
1116 state.new_lib(&filtered)?;
1117
1118 if is_v51 {
1119 state.push_c_function(crate::base::debug_getfenv_fn)?;
1124 state.set_field(-2, b"getfenv")?;
1125 state.push_c_function(crate::base::debug_setfenv_fn)?;
1126 state.set_field(-2, b"setfenv")?;
1127 }
1128
1129 Ok(1)
1130}
1131
1132