1use std::cell::RefCell;
13#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
14use std::io::{self, BufRead, Write};
15use std::rc::Rc;
16
17use crate::state_stub::{LuaDebug as DebugInfo, LuaState, LuaStateStubExt as _};
18use lua_types::{GcRef, LuaError, LuaString, LuaType, LuaValue, LuaVersion};
19
20const HOOKKEY: &[u8] = b"_HOOKKEY";
25
26const HOOKNAMES: &[&[u8]; 5] = &[b"call", b"return", b"line", b"count", b"tail call"];
30
31const MASK_CALL: u32 = 1 << 0;
33const MASK_RET: u32 = 1 << 1;
34const MASK_LINE: u32 = 1 << 2;
35const MASK_COUNT: u32 = 1 << 3;
36
37pub(crate) type LibFn = fn(&mut LuaState) -> Result<usize, LuaError>;
41
42#[expect(
48 dead_code,
49 reason = "ported stdlib helper; not yet wired into the runtime"
50)]
51pub(crate) type HookFn = fn(&mut LuaState, i32, i32) -> Result<(), LuaError>;
52
53type UpvalId = usize;
62
63#[derive(Clone)]
64enum DebugThreadTarget {
65 Current,
66 Other(Rc<RefCell<LuaState>>),
67 Unavailable,
68}
69
70fn resolve_debug_thread_target(
71 state: &LuaState,
72 target_thread: &Option<GcRef<lua_types::value::LuaThread>>,
73) -> DebugThreadTarget {
74 let Some(thread) = target_thread else {
75 return DebugThreadTarget::Current;
76 };
77
78 if thread.id == state.cached_thread_id {
79 return DebugThreadTarget::Current;
80 }
81
82 let g = state.global();
83 if thread.id == g.main_thread_id {
84 DebugThreadTarget::Unavailable
85 } else {
86 g.threads
87 .get(&thread.id)
88 .map(|entry| DebugThreadTarget::Other(entry.state.clone()))
89 .unwrap_or(DebugThreadTarget::Unavailable)
90 }
91}
92
93fn check_cross_thread_stack(
103 state: &mut LuaState,
104 target_is_self: bool,
105 n: i32,
106) -> Result<(), LuaError> {
107 if !target_is_self {
109 state.ensure_stack(n, "stack overflow")?;
113 }
114 Ok(())
115}
116
117fn getthread(state: &mut LuaState) -> (i32, Option<GcRef<lua_types::value::LuaThread>>) {
121 if state.type_at(1) == LuaType::Thread {
122 let thread = state.to_thread_at(1);
123 return (1, thread);
124 }
125 (0, None)
126}
127
128fn settabss(state: &mut LuaState, k: &[u8], v: Option<&[u8]>) -> Result<(), LuaError> {
134 match v {
135 Some(s) => {
136 let ls = state.intern_str(s)?;
137 state.push(LuaValue::Str(ls));
138 }
139 None => {
140 state.push(LuaValue::Nil);
141 }
142 }
143 state.set_field(-2, k)
144}
145
146fn settabsi(state: &mut LuaState, k: &[u8], v: i32) -> Result<(), LuaError> {
149 state.push(LuaValue::Int(v as i64));
150 state.set_field(-2, k)
151}
152
153fn settabsb(state: &mut LuaState, k: &[u8], v: bool) -> Result<(), LuaError> {
156 state.push(LuaValue::Bool(v));
157 state.set_field(-2, k)
158}
159
160fn treat_stack_option(
167 state: &mut LuaState,
168 target_is_self: bool,
169 fname: &[u8],
170) -> Result<(), LuaError> {
171 if target_is_self {
172 state.rotate(-2, 1)?;
173 } else {
174 state.push(LuaValue::Nil);
178 }
179 state.set_field(-2, fname)
180}
181
182fn move_stack_option_from_target(
183 state: &mut LuaState,
184 target: &mut LuaState,
185 fname: &[u8],
186) -> Result<(), LuaError> {
187 let val = target.get_at(target.top_idx() - 1);
188 target.pop_n(1);
189 state.push(val);
190 state.set_field(-2, fname)
191}
192
193pub(crate) fn get_registry(state: &mut LuaState) -> Result<usize, LuaError> {
198 state.push_registry()?;
199 Ok(1)
200}
201
202pub(crate) fn get_metatable(state: &mut LuaState) -> Result<usize, LuaError> {
205 state.check_arg_any(1)?;
206 if !state.get_metatable(1)? {
207 state.push(LuaValue::Nil);
208 }
209 Ok(1)
210}
211
212pub(crate) fn set_metatable(state: &mut LuaState) -> Result<usize, LuaError> {
216 let t = state.type_at(2);
217 if !(t == LuaType::Nil || t == LuaType::Table) {
218 let got = state.arg(2);
219 return Err(LuaError::type_arg_error(2, "nil or table", &got));
220 }
221 lua_vm::api::set_top(state, 2)?;
222 state.set_metatable(1)?;
223 Ok(1)
224}
225
226pub(crate) fn get_uservalue(state: &mut LuaState) -> Result<usize, LuaError> {
231 let n = state.opt_arg_integer(2, 1)? as i32;
232 if state.type_at(1) != LuaType::UserData {
233 state.push_fail()?;
234 return Ok(1);
235 }
236 let ty = state.get_iuservalue(1, n)?;
237 if ty != LuaType::None {
238 state.push(LuaValue::Bool(true));
239 return Ok(2);
240 }
241 Ok(1)
242}
243
244pub(crate) fn set_uservalue(state: &mut LuaState) -> Result<usize, LuaError> {
248 let n = state.opt_arg_integer(3, 1)? as i32;
249 state.check_arg_type(1, LuaType::UserData)?;
250 state.check_arg_any(2)?;
251 lua_vm::api::set_top(state, 2)?;
252 if !state.set_iuservalue(1, n)? {
253 state.push_fail()?;
254 }
255 Ok(1)
256}
257
258pub(crate) fn get_info(state: &mut LuaState) -> Result<usize, LuaError> {
263 let mut ar = DebugInfo::default();
264
265 let (arg, other_thread) = getthread(state);
266 let target_is_self = other_thread.is_none();
267 let target_state = resolve_debug_thread_target(state, &other_thread);
268
269 let raw_opts: Vec<u8> = state.opt_arg_string(arg + 2, b"flnSrtu")?.to_vec();
271
272 check_cross_thread_stack(state, target_is_self, 3)?;
273
274 if raw_opts.first() == Some(&b'>') {
275 return Err(lua_vm::debug::arg_error_impl(
276 state,
277 arg + 2,
278 b"invalid option '>'",
279 ));
280 }
281
282 let options: Vec<u8>;
284 let info_target_owner: Option<Rc<RefCell<LuaState>>>;
285 let mut info_target: Option<std::cell::RefMut<'_, LuaState>> = None;
286 let mut info_target_is_self = target_is_self;
287
288 if state.type_at(arg + 1) == LuaType::Function {
289 let mut prefixed = Vec::with_capacity(raw_opts.len() + 1);
291 prefixed.push(b'>');
292 prefixed.extend_from_slice(&raw_opts);
293 options = prefixed;
294
295 if target_is_self {
296 state.push_value_at(arg + 1)?;
297 } else {
298 }
302
303 if state.get_debug_info(&options, &mut ar).is_err() {
305 return Err(lua_vm::debug::arg_error_impl(
306 state,
307 arg + 2,
308 b"invalid option",
309 ));
310 }
311 } else {
312 options = raw_opts;
313
314 let level = state.check_arg_integer(arg + 1)? as i32;
315 match target_state {
316 DebugThreadTarget::Current | DebugThreadTarget::Unavailable => {
317 info_target_is_self = true;
318 if !state.get_stack_level(level, &mut ar) {
319 state.push_fail()?;
320 return Ok(1);
321 }
322
323 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 }
331 DebugThreadTarget::Other(target_state) => {
332 info_target_owner = Some(target_state);
333 let mut target = info_target_owner
334 .as_ref()
335 .expect("target owner just stored")
336 .borrow_mut();
337 if !target.get_stack_level(level, &mut ar) {
338 state.push_fail()?;
339 return Ok(1);
340 }
341 if target.get_debug_info(&options, &mut ar).is_err() {
342 return Err(lua_vm::debug::arg_error_impl(
343 state,
344 arg + 2,
345 b"invalid option",
346 ));
347 }
348 info_target = Some(target);
349 }
350 }
351 }
352
353 let result_tbl = state.new_table();
354 state.push(LuaValue::Table(result_tbl));
355
356 if options.contains(&b'S') {
357 let src = state.intern_str(ar.source_bytes())?;
358 state.push(LuaValue::Str(src));
359 state.set_field(-2, b"source")?;
360
361 settabss(state, b"short_src", Some(ar.short_src_bytes()))?;
362 settabsi(state, b"linedefined", ar.linedefined)?;
363 settabsi(state, b"lastlinedefined", ar.lastlinedefined)?;
364 settabss(state, b"what", Some(ar.what_bytes()))?;
365 }
366 if options.contains(&b'l') {
367 settabsi(state, b"currentline", ar.currentline)?;
368 }
369 if options.contains(&b'u') {
370 settabsi(state, b"nups", ar.nups as i32)?;
371 settabsi(state, b"nparams", ar.nparams as i32)?;
372 settabsb(state, b"isvararg", ar.isvararg)?;
373 }
374 if options.contains(&b'n') {
375 let name_opt: Option<&[u8]> = ar.name.as_deref();
376 settabss(state, b"name", name_opt)?;
377 settabss(state, b"namewhat", Some(ar.namewhat_bytes()))?;
378 }
379 if options.contains(&b'r') {
380 settabsi(state, b"ftransfer", ar.ftransfer as i32)?;
381 settabsi(state, b"ntransfer", ar.ntransfer as i32)?;
382 }
383 if options.contains(&b't') {
384 settabsb(state, b"istailcall", ar.istailcall)?;
385 if matches!(state.global().lua_version, LuaVersion::V55) {
386 settabsi(state, b"extraargs", ar.extraargs as i32)?;
387 }
388 }
389 if options.contains(&b'L') {
395 if info_target_is_self {
396 treat_stack_option(state, true, b"activelines")?;
397 } else if let Some(target) = info_target.as_mut() {
398 move_stack_option_from_target(state, &mut **target, b"activelines")?;
399 } else {
400 state.push(LuaValue::Nil);
401 state.set_field(-2, b"activelines")?;
402 }
403 }
404 if options.contains(&b'f') {
405 if info_target_is_self {
406 treat_stack_option(state, true, b"func")?;
407 } else if let Some(target) = info_target.as_mut() {
408 move_stack_option_from_target(state, &mut **target, b"func")?;
409 } else {
410 state.push(LuaValue::Nil);
411 state.set_field(-2, b"func")?;
412 }
413 }
414
415 Ok(1)
416}
417
418pub(crate) fn get_local(state: &mut LuaState) -> Result<usize, LuaError> {
425 let (arg, other_thread) = getthread(state);
426 let target_state = resolve_debug_thread_target(state, &other_thread);
427
428 let nvar = state.check_arg_integer(arg + 2)? as i32;
429
430 if state.type_at(arg + 1) == LuaType::Function {
431 state.push_value_at(arg + 1)?;
432 let name = state.get_param_name(0, nvar)?;
435 match name {
436 Some(n) => {
437 let ls = state.intern_str(&n)?;
438 state.push(LuaValue::Str(ls));
439 }
440 None => {
441 state.push(LuaValue::Nil);
442 }
443 }
444 return Ok(1);
447 }
448
449 let level = state.check_arg_integer(arg + 1)? as i32;
451 let mut ar = DebugInfo::default();
452
453 let name = match target_state {
454 DebugThreadTarget::Current | DebugThreadTarget::Unavailable => {
455 if !state.get_stack_level(level, &mut ar) {
456 return Err(lua_vm::debug::arg_error_impl(
457 state,
458 arg + 1,
459 b"level out of range",
460 ));
461 }
462 check_cross_thread_stack(state, true, 1)?;
463 state.get_local_at(&ar, nvar)?
465 }
466 DebugThreadTarget::Other(target_state) => {
467 let mut target = target_state.borrow_mut();
468 if !target.get_stack_level(level, &mut ar) {
469 return Err(lua_vm::debug::arg_error_impl(
470 state,
471 arg + 1,
472 b"level out of range",
473 ));
474 }
475 check_cross_thread_stack(state, false, 1)?;
476 let name = target.get_local_at(&ar, nvar)?;
477 if name.is_some() {
478 let val = target.get_at(target.top_idx() - 1);
479 target.pop_n(1);
480 state.push(val);
481 }
482 name
483 }
484 };
485
486 if let Some(n) = name {
487 let ls = state.intern_str(&n)?;
488 state.push(LuaValue::Str(ls));
489 state.rotate(-2, 1)?;
490 Ok(2)
491 } else {
492 state.push_fail()?;
493 Ok(1)
494 }
495}
496
497pub(crate) fn set_local(state: &mut LuaState) -> Result<usize, LuaError> {
502 let (arg, other_thread) = getthread(state);
503 let target_state = resolve_debug_thread_target(state, &other_thread);
504
505 let level = state.check_arg_integer(arg + 1)? as i32;
506 let nvar = state.check_arg_integer(arg + 2)? as i32;
507
508 let mut ar = DebugInfo::default();
509
510 state.check_arg_any(arg + 3)?;
511 lua_vm::api::set_top(state, arg + 3)?;
512
513 let name = match target_state {
514 DebugThreadTarget::Current | DebugThreadTarget::Unavailable => {
515 if !state.get_stack_level(level, &mut ar) {
516 return Err(lua_vm::debug::arg_error_impl(
517 state,
518 arg + 1,
519 b"level out of range",
520 ));
521 }
522 check_cross_thread_stack(state, true, 1)?;
523 let name = state.set_local_at(&ar, nvar)?;
524 if name.is_none() {
525 state.pop_n(1);
526 }
527 name
528 }
529 DebugThreadTarget::Other(target_state) => {
530 let new_val = state.get_at(state.top_idx() - 1);
531 let mut target = target_state.borrow_mut();
532 if !target.get_stack_level(level, &mut ar) {
533 return Err(lua_vm::debug::arg_error_impl(
534 state,
535 arg + 1,
536 b"level out of range",
537 ));
538 }
539 check_cross_thread_stack(state, false, 1)?;
540 target.push(new_val);
541 let name = target.set_local_at(&ar, nvar)?;
542 if name.is_none() {
543 target.pop_n(1);
544 }
545 state.pop_n(1);
546 name
547 }
548 };
549
550 match name {
551 Some(n) => {
552 let ls = state.intern_str(&n)?;
553 state.push(LuaValue::Str(ls));
554 }
555 None => {
556 state.push(LuaValue::Nil);
557 }
558 }
559 Ok(1)
560}
561
562fn aux_upvalue(state: &mut LuaState, get: bool) -> Result<usize, LuaError> {
573 let n = state.check_arg_integer(2)? as i32;
574 state.check_arg_type(1, LuaType::Function)?;
575
576 let name: Option<Vec<u8>> = if get {
577 state.get_upvalue(1, n)?
579 } else {
580 state.set_upvalue(1, n)?
582 };
583
584 let name_ref = match name {
585 Some(n) => n,
586 None => return Ok(0),
587 };
588
589 let ls = state.intern_str(&name_ref)?;
590 state.push(LuaValue::Str(ls));
591
592 if get {
595 state.insert(-2)?;
596 }
597
598 Ok(if get { 2 } else { 1 })
599}
600
601pub(crate) fn get_upvalue(state: &mut LuaState) -> Result<usize, LuaError> {
604 aux_upvalue(state, true)
605}
606
607pub(crate) fn set_upvalue(state: &mut LuaState) -> Result<usize, LuaError> {
611 state.check_arg_any(3)?;
612 aux_upvalue(state, false)
613}
614
615fn check_upval(
620 state: &mut LuaState,
621 argf: i32,
622 argnup: i32,
623 require_valid: bool,
624) -> Result<(Option<UpvalId>, i32), LuaError> {
625 let nup = state.check_arg_integer(argnup)? as i32;
626 state.check_arg_type(argf, LuaType::Function)?;
627 let id: Option<UpvalId> = match state.upvalue_id(argf, nup) {
632 Ok(p) if p.is_null() => None,
633 Ok(p) => Some(p as usize),
634 Err(_) => None,
635 };
636 if require_valid && id.is_none() {
637 return Err(lua_vm::debug::arg_error_impl(
638 state,
639 argnup,
640 b"invalid upvalue index",
641 ));
642 }
643 Ok((id, nup))
644}
645
646pub(crate) fn upvalue_id(state: &mut LuaState) -> Result<usize, LuaError> {
650 let (id, _nup) = check_upval(state, 1, 2, false)?;
651 match id {
652 Some(uid) => {
653 lua_vm::api::push_light_userdata(state, uid as *mut core::ffi::c_void);
654 }
655 None => {
656 state.push_fail()?;
657 }
658 }
659 Ok(1)
660}
661
662pub(crate) fn upvalue_join(state: &mut LuaState) -> Result<usize, LuaError> {
666 let (_id1, n1) = check_upval(state, 1, 2, true)?;
667 let (_id2, n2) = check_upval(state, 3, 4, true)?;
668 if state.is_c_function_at(1) {
669 return Err(lua_vm::debug::arg_error_impl(
670 state,
671 1,
672 b"Lua function expected",
673 ));
674 }
675 if state.is_c_function_at(3) {
676 return Err(lua_vm::debug::arg_error_impl(
677 state,
678 3,
679 b"Lua function expected",
680 ));
681 }
682 state.join_upvalues(1, n1, 3, n2)?;
683 Ok(0)
684}
685
686pub(crate) fn hookf(state: &mut LuaState, event: i32, currentline: i32) -> Result<(), LuaError> {
692 state.get_registry_field(HOOKKEY)?;
693 state.push_thread()?;
694 if state.raw_get(-2)? == LuaType::Function {
695 let event_idx = event.clamp(0, HOOKNAMES.len() as i32 - 1) as usize;
696 let event_str = state.intern_str(HOOKNAMES[event_idx])?;
697 state.push(LuaValue::Str(event_str));
698
699 if currentline >= 0 {
700 state.push(LuaValue::Int(currentline as i64));
701 } else {
702 state.push(LuaValue::Nil);
703 }
704
705 state.call(2, 0)?;
706 }
707 Ok(())
710}
711
712fn make_mask(smask: &[u8], count: i32) -> u32 {
716 let mut mask: u32 = 0;
717 if smask.contains(&b'c') {
718 mask |= MASK_CALL;
719 }
720 if smask.contains(&b'r') {
721 mask |= MASK_RET;
722 }
723 if smask.contains(&b'l') {
724 mask |= MASK_LINE;
725 }
726 if count > 0 {
727 mask |= MASK_COUNT;
728 }
729 mask
730}
731
732fn unmake_mask(mask: u32) -> Vec<u8> {
736 let mut smask = Vec::with_capacity(3);
737 if mask & MASK_CALL != 0 {
738 smask.push(b'c');
739 }
740 if mask & MASK_RET != 0 {
741 smask.push(b'r');
742 }
743 if mask & MASK_LINE != 0 {
744 smask.push(b'l');
745 }
746 smask
747}
748
749pub(crate) fn set_hook(state: &mut LuaState) -> Result<usize, LuaError> {
753 let (arg, other_thread) = getthread(state);
754 let target_is_self = other_thread.is_none();
755
756 let hook_active: bool;
757 let mask: u32;
758 let count: i32;
759
760 if matches!(state.type_at(arg + 1), LuaType::None | LuaType::Nil) {
761 lua_vm::api::set_top(state, arg + 1)?;
762 hook_active = false;
763 mask = 0;
764 count = 0;
765 } else {
766 let smask: Vec<u8> = state.check_arg_string(arg + 2)?.to_vec();
767 state.check_arg_type(arg + 1, LuaType::Function)?;
768 count = state.opt_arg_integer(arg + 3, 0)? as i32;
769 hook_active = true;
770 mask = make_mask(&smask, count);
771 }
772
773 if !state.get_or_create_registry_subtable(HOOKKEY)? {
774 let k = state.intern_str(b"k")?;
777 state.push(LuaValue::Str(k));
778 state.set_field(-2, b"__mode")?;
779 state.push_value_at(-1)?;
780 state.set_metatable(-2)?;
781 }
782
783 check_cross_thread_stack(state, target_is_self, 1)?;
784 let target_state = resolve_debug_thread_target(state, &other_thread);
785 match &target_state {
786 DebugThreadTarget::Other(st) => {
787 st.borrow_mut().ensure_stack(1, "stack overflow")?;
788 }
789 DebugThreadTarget::Current => {}
790 DebugThreadTarget::Unavailable => {}
791 }
792
793 if target_is_self {
794 state.push_thread()?;
795 } else {
796 let thr = other_thread
803 .clone()
804 .expect("other_thread is Some when target_is_self is false");
805 state.push(lua_types::value::LuaValue::Thread(thr));
806 }
807 state.push_value_at(arg + 1)?;
808 state.raw_set(-3)?;
809
810 let hook_box: Option<Box<dyn FnMut(&mut LuaState, &lua_vm::debug::LuaDebug)>> = if hook_active {
811 Some(Box::new(|st, ar| {
812 let _ = hookf(st, ar.event, ar.currentline);
813 }))
814 } else {
815 None
816 };
817 match target_state {
818 DebugThreadTarget::Current => {
819 lua_vm::debug::set_hook(state, hook_box, mask as i32, count);
820 }
821 DebugThreadTarget::Other(target_state) => {
822 lua_vm::debug::set_hook(&mut target_state.borrow_mut(), hook_box, mask as i32, count);
823 }
824 DebugThreadTarget::Unavailable => {
825 return Ok(0);
829 }
830 }
831
832 Ok(0)
833}
834
835pub(crate) fn get_hook(state: &mut LuaState) -> Result<usize, LuaError> {
839 let (_arg, other_thread) = getthread(state);
840 let target_is_self = other_thread.is_none();
841 let target_state = resolve_debug_thread_target(state, &other_thread);
842
843 let (mask, hook_is_set, hook_is_internal, hook_count) = match target_state {
844 DebugThreadTarget::Current => (
845 state.get_hook_mask(),
846 state.hook_is_set(),
847 state.hook_is_internal_lua_hook(),
848 state.get_hook_count(),
849 ),
850 DebugThreadTarget::Other(target_state) => {
851 let mut target_state = target_state.borrow_mut();
852 (
853 target_state.get_hook_mask(),
854 target_state.hook_is_set(),
855 target_state.hook_is_internal_lua_hook(),
856 target_state.get_hook_count(),
857 )
858 }
859 DebugThreadTarget::Unavailable => (0u32, false, false, 0i32),
860 };
861
862 if !hook_is_set {
863 state.push_fail()?;
864 return Ok(1);
865 }
866
867 if !hook_is_internal {
869 let s = state.intern_str(b"external hook")?;
870 state.push(LuaValue::Str(s));
871 } else {
872 state.get_registry_field(HOOKKEY)?;
873 check_cross_thread_stack(state, target_is_self, 1)?;
874 if target_is_self {
875 state.push_thread()?;
876 } else {
877 let key_thread = other_thread
878 .expect("other_thread is Some when target_is_self is false")
879 .clone();
880 state.push(lua_types::value::LuaValue::Thread(key_thread));
881 }
882 state.raw_get(-2)?;
883 state.remove(-2)?;
884 }
885
886 let smask = unmake_mask(mask);
887 let ls = state.intern_str(&smask)?;
888 state.push(LuaValue::Str(ls));
889
890 state.push(LuaValue::Int(hook_count as i64));
891
892 Ok(3)
893}
894
895pub(crate) fn debug_interactive(state: &mut LuaState) -> Result<usize, LuaError> {
902 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
903 {
904 let _ = state;
905 return Err(LuaError::runtime(format_args!(
906 "debug.debug interactive stdin not available in this host"
907 )));
908 }
909
910 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
911 {
912 let stdin = io::stdin();
913 loop {
914 eprint!("lua_debug> ");
915 let _ = io::stderr().flush();
916
917 let mut line = String::new();
922 let n = stdin
923 .lock()
924 .read_line(&mut line)
925 .map_err(|e| LuaError::runtime(format_args!("stdin read error: {}", e)))?;
926
927 if n == 0 || line == "cont\n" {
928 return Ok(0);
929 }
930
931 let bytes: &[u8] = line.as_bytes();
932
933 let result = state
936 .load_buffer(bytes, b"=(debug command)", None)
937 .and_then(|_| state.protected_call(0, 0, 0));
938
939 if let Err(_) = result {
940 eprintln!("(error in debug command)");
945 state.pop_n(1);
946 }
947
948 lua_vm::api::set_top(state, 0)?;
949 }
950 }
951}
952
953pub(crate) fn traceback(state: &mut LuaState) -> Result<usize, LuaError> {
960 let (arg, other_thread) = getthread(state);
961 let target_is_self = other_thread.is_none();
962
963 let msg_owned: Option<Vec<u8>> = state
965 .to_lua_string(arg + 1)
966 .map(|s: GcRef<LuaString>| s.as_bytes().to_vec());
967
968 let arg1_ty = state.type_at(arg + 1);
969 if msg_owned.is_none() && !matches!(arg1_ty, LuaType::None | LuaType::Nil) {
970 state.push_value_at(arg + 1)?;
971 } else {
972 let default_level: i64 = if target_is_self { 1 } else { 0 };
973 let level = state.opt_arg_integer(arg + 2, default_level)? as i32;
974
975 match resolve_debug_thread_target(state, &other_thread) {
976 DebugThreadTarget::Current => {
977 crate::auxlib::traceback(state, None, msg_owned.as_deref(), level)?;
978 }
979 DebugThreadTarget::Other(target_state) => {
980 let mut target_state = target_state.borrow_mut();
981 crate::auxlib::traceback(
982 state,
983 Some(&mut *target_state),
984 msg_owned.as_deref(),
985 level,
986 )?;
987 }
988 DebugThreadTarget::Unavailable => {
989 crate::auxlib::traceback(state, None, msg_owned.as_deref(), level)?;
990 }
991 }
992 }
993 Ok(1)
994}
995
996pub(crate) fn set_c_stack_limit(state: &mut LuaState) -> Result<usize, LuaError> {
1000 let limit = state.check_arg_integer(1)? as i32;
1001 let res = state.set_c_stack_limit(limit)?;
1002 state.push(LuaValue::Int(res as i64));
1003 Ok(1)
1004}
1005
1006pub(crate) const DBLIB: &[(&[u8], LibFn)] = &[
1011 (b"debug", debug_interactive as LibFn),
1012 (b"getuservalue", get_uservalue as LibFn),
1013 (b"gethook", get_hook as LibFn),
1014 (b"getinfo", get_info as LibFn),
1015 (b"getlocal", get_local as LibFn),
1016 (b"getregistry", get_registry as LibFn),
1017 (b"getmetatable", get_metatable as LibFn),
1018 (b"getupvalue", get_upvalue as LibFn),
1019 (b"upvaluejoin", upvalue_join as LibFn),
1020 (b"upvalueid", upvalue_id as LibFn),
1021 (b"setuservalue", set_uservalue as LibFn),
1022 (b"sethook", set_hook as LibFn),
1023 (b"setlocal", set_local as LibFn),
1024 (b"setmetatable", set_metatable as LibFn),
1025 (b"setupvalue", set_upvalue as LibFn),
1026 (b"traceback", traceback as LibFn),
1027 (b"setcstacklimit", set_c_stack_limit as LibFn),
1028];
1029
1030pub fn open_debug(state: &mut LuaState) -> Result<usize, LuaError> {
1034 state.new_lib(DBLIB)?;
1035 Ok(1)
1036}
1037
1038