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 lua_types::{GcRef, LuaError, LuaString, LuaType, LuaValue};
18use crate::state_stub::{LuaState, LuaStateStubExt as _, LuaDebug as DebugInfo};
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(dead_code, reason = "ported stdlib helper; not yet wired into the runtime")]
48pub(crate) type HookFn = fn(&mut LuaState, i32, i32) -> Result<(), LuaError>;
49
50type UpvalId = usize;
59
60#[derive(Clone)]
61enum DebugThreadTarget {
62 Current,
63 Other(Rc<RefCell<LuaState>>),
64 Unavailable,
65}
66
67fn resolve_debug_thread_target(
68 state: &LuaState,
69 target_thread: &Option<GcRef<lua_types::value::LuaThread>>,
70) -> DebugThreadTarget {
71 let Some(thread) = target_thread else {
72 return DebugThreadTarget::Current;
73 };
74
75 if thread.id == state.cached_thread_id {
76 return DebugThreadTarget::Current;
77 }
78
79 let g = state.global();
80 if thread.id == g.main_thread_id {
81 DebugThreadTarget::Unavailable
82 } else {
83 g.threads
84 .get(&thread.id)
85 .map(|entry| DebugThreadTarget::Other(entry.state.clone()))
86 .unwrap_or(DebugThreadTarget::Unavailable)
87 }
88}
89
90fn check_cross_thread_stack(
100 state: &mut LuaState,
101 target_is_self: bool,
102 n: i32,
103) -> Result<(), LuaError> {
104 if !target_is_self {
106 state.ensure_stack(n, "stack overflow")?;
110 }
111 Ok(())
112}
113
114fn getthread(state: &mut LuaState) -> (i32, Option<GcRef<lua_types::value::LuaThread>>) {
118 if state.type_at(1) == LuaType::Thread {
119 let thread = state.to_thread_at(1);
120 return (1, thread);
121 }
122 (0, None)
123}
124
125fn settabss(state: &mut LuaState, k: &[u8], v: Option<&[u8]>) -> Result<(), LuaError> {
131 match v {
132 Some(s) => {
133 let ls = state.intern_str(s)?;
134 state.push(LuaValue::Str(ls));
135 }
136 None => { state.push(LuaValue::Nil); }
137 }
138 state.set_field(-2, k)
139}
140
141fn settabsi(state: &mut LuaState, k: &[u8], v: i32) -> Result<(), LuaError> {
144 state.push(LuaValue::Int(v as i64));
145 state.set_field(-2, k)
146}
147
148fn settabsb(state: &mut LuaState, k: &[u8], v: bool) -> Result<(), LuaError> {
151 state.push(LuaValue::Bool(v));
152 state.set_field(-2, k)
153}
154
155fn treat_stack_option(
162 state: &mut LuaState,
163 target_is_self: bool,
164 fname: &[u8],
165) -> Result<(), LuaError> {
166 if target_is_self {
167 state.rotate(-2, 1)?;
168 } else {
169 state.push(LuaValue::Nil);
173 }
174 state.set_field(-2, fname)
175}
176
177fn move_stack_option_from_target(
178 state: &mut LuaState,
179 target: &mut LuaState,
180 fname: &[u8],
181) -> Result<(), LuaError> {
182 let val = target.get_at(target.top_idx() - 1);
183 target.pop_n(1);
184 state.push(val);
185 state.set_field(-2, fname)
186}
187
188pub(crate) fn get_registry(state: &mut LuaState) -> Result<usize, LuaError> {
193 state.push_registry()?;
194 Ok(1)
195}
196
197pub(crate) fn get_metatable(state: &mut LuaState) -> Result<usize, LuaError> {
200 state.check_arg_any(1)?;
201 if !state.get_metatable(1)? {
202 state.push(LuaValue::Nil);
203 }
204 Ok(1)
205}
206
207pub(crate) fn set_metatable(state: &mut LuaState) -> Result<usize, LuaError> {
211 let t = state.type_at(2);
212 if !(t == LuaType::Nil || t == LuaType::Table) {
213 let got = state.arg(2);
214 return Err(LuaError::type_arg_error(2, "nil or table", &got));
215 }
216 lua_vm::api::set_top(state, 2)?;
217 state.set_metatable(1)?;
218 Ok(1)
219}
220
221pub(crate) fn get_uservalue(state: &mut LuaState) -> Result<usize, LuaError> {
226 let n = state.opt_arg_integer(2, 1)? as i32;
227 if state.type_at(1) != LuaType::UserData {
228 state.push_fail()?;
229 return Ok(1);
230 }
231 let ty = state.get_iuservalue(1, n)?;
232 if ty != LuaType::None {
233 state.push(LuaValue::Bool(true));
234 return Ok(2);
235 }
236 Ok(1)
237}
238
239pub(crate) fn set_uservalue(state: &mut LuaState) -> Result<usize, LuaError> {
243 let n = state.opt_arg_integer(3, 1)? as i32;
244 state.check_arg_type(1, LuaType::UserData)?;
245 state.check_arg_any(2)?;
246 lua_vm::api::set_top(state, 2)?;
247 if !state.set_iuservalue(1, n)? {
248 state.push_fail()?;
249 }
250 Ok(1)
251}
252
253pub(crate) fn get_info(state: &mut LuaState) -> Result<usize, LuaError> {
258 let mut ar = DebugInfo::default();
259
260 let (arg, other_thread) = getthread(state);
261 let target_is_self = other_thread.is_none();
262 let target_state = resolve_debug_thread_target(state, &other_thread);
263
264 let raw_opts: Vec<u8> = state.opt_arg_string(arg + 2, b"flnSrtu")?.to_vec();
266
267 check_cross_thread_stack(state, target_is_self, 3)?;
268
269 if raw_opts.first() == Some(&b'>') {
270 return Err(LuaError::arg_error(arg + 2, "invalid option '>'"));
271 }
272
273 let options: Vec<u8>;
275 let info_target_owner: Option<Rc<RefCell<LuaState>>>;
276 let mut info_target: Option<std::cell::RefMut<'_, LuaState>> = None;
277 let mut info_target_is_self = target_is_self;
278
279 if state.type_at(arg + 1) == LuaType::Function {
280 let mut prefixed = Vec::with_capacity(raw_opts.len() + 1);
282 prefixed.push(b'>');
283 prefixed.extend_from_slice(&raw_opts);
284 options = prefixed;
285
286 if target_is_self {
287 state.push_value_at(arg + 1)?;
288 } else {
289 }
293
294 if state.get_debug_info(&options, &mut ar).is_err() {
296 return Err(LuaError::arg_error(arg + 2, "invalid option"));
297 }
298 } else {
299 options = raw_opts;
300
301 let level = state.check_arg_integer(arg + 1)? as i32;
302 match target_state {
303 DebugThreadTarget::Current | DebugThreadTarget::Unavailable => {
304 info_target_is_self = true;
305 if !state.get_stack_level(level, &mut ar) {
306 state.push_fail()?;
307 return Ok(1);
308 }
309
310 if state.get_debug_info(&options, &mut ar).is_err() {
311 return Err(LuaError::arg_error(arg + 2, "invalid option"));
312 }
313 }
314 DebugThreadTarget::Other(target_state) => {
315 info_target_owner = Some(target_state);
316 let mut target = info_target_owner
317 .as_ref()
318 .expect("target owner just stored")
319 .borrow_mut();
320 if !target.get_stack_level(level, &mut ar) {
321 state.push_fail()?;
322 return Ok(1);
323 }
324 if target.get_debug_info(&options, &mut ar).is_err() {
325 return Err(LuaError::arg_error(arg + 2, "invalid option"));
326 }
327 info_target = Some(target);
328 }
329 }
330 }
331
332 let result_tbl = state.new_table();
333 state.push(LuaValue::Table(result_tbl));
334
335 if options.contains(&b'S') {
336 let src = state.intern_str(ar.source_bytes())?;
337 state.push(LuaValue::Str(src));
338 state.set_field(-2, b"source")?;
339
340 settabss(state, b"short_src", Some(ar.short_src_bytes()))?;
341 settabsi(state, b"linedefined", ar.linedefined)?;
342 settabsi(state, b"lastlinedefined", ar.lastlinedefined)?;
343 settabss(state, b"what", Some(ar.what_bytes()))?;
344 }
345 if options.contains(&b'l') {
346 settabsi(state, b"currentline", ar.currentline)?;
347 }
348 if options.contains(&b'u') {
349 settabsi(state, b"nups", ar.nups as i32)?;
350 settabsi(state, b"nparams", ar.nparams as i32)?;
351 settabsb(state, b"isvararg", ar.isvararg)?;
352 }
353 if options.contains(&b'n') {
354 let name_opt: Option<&[u8]> = ar.name.as_deref();
355 settabss(state, b"name", name_opt)?;
356 settabss(state, b"namewhat", Some(ar.namewhat_bytes()))?;
357 }
358 if options.contains(&b'r') {
359 settabsi(state, b"ftransfer", ar.ftransfer as i32)?;
360 settabsi(state, b"ntransfer", ar.ntransfer as i32)?;
361 }
362 if options.contains(&b't') {
363 settabsb(state, b"istailcall", ar.istailcall)?;
364 }
365 if options.contains(&b'L') {
371 if info_target_is_self {
372 treat_stack_option(state, true, b"activelines")?;
373 } else if let Some(target) = info_target.as_mut() {
374 move_stack_option_from_target(state, &mut **target, b"activelines")?;
375 } else {
376 state.push(LuaValue::Nil);
377 state.set_field(-2, b"activelines")?;
378 }
379 }
380 if options.contains(&b'f') {
381 if info_target_is_self {
382 treat_stack_option(state, true, b"func")?;
383 } else if let Some(target) = info_target.as_mut() {
384 move_stack_option_from_target(state, &mut **target, b"func")?;
385 } else {
386 state.push(LuaValue::Nil);
387 state.set_field(-2, b"func")?;
388 }
389 }
390
391 Ok(1)
392}
393
394pub(crate) fn get_local(state: &mut LuaState) -> Result<usize, LuaError> {
401 let (arg, other_thread) = getthread(state);
402 let target_state = resolve_debug_thread_target(state, &other_thread);
403
404 let nvar = state.check_arg_integer(arg + 2)? as i32;
405
406 if state.type_at(arg + 1) == LuaType::Function {
407 state.push_value_at(arg + 1)?;
408 let name = state.get_param_name(0, nvar)?;
411 match name {
412 Some(n) => {
413 let ls = state.intern_str(&n)?;
414 state.push(LuaValue::Str(ls));
415 }
416 None => { state.push(LuaValue::Nil); }
417 }
418 return Ok(1);
421 }
422
423 let level = state.check_arg_integer(arg + 1)? as i32;
425 let mut ar = DebugInfo::default();
426
427 let name = match target_state {
428 DebugThreadTarget::Current | DebugThreadTarget::Unavailable => {
429 if !state.get_stack_level(level, &mut ar) {
430 return Err(LuaError::arg_error(arg + 1, "level out of range"));
431 }
432 check_cross_thread_stack(state, true, 1)?;
433 state.get_local_at(&ar, nvar)?
435 }
436 DebugThreadTarget::Other(target_state) => {
437 let mut target = target_state.borrow_mut();
438 if !target.get_stack_level(level, &mut ar) {
439 return Err(LuaError::arg_error(arg + 1, "level out of range"));
440 }
441 check_cross_thread_stack(state, false, 1)?;
442 let name = target.get_local_at(&ar, nvar)?;
443 if name.is_some() {
444 let val = target.get_at(target.top_idx() - 1);
445 target.pop_n(1);
446 state.push(val);
447 }
448 name
449 }
450 };
451
452 if let Some(n) = name {
453 let ls = state.intern_str(&n)?;
454 state.push(LuaValue::Str(ls));
455 state.rotate(-2, 1)?;
456 Ok(2)
457 } else {
458 state.push_fail()?;
459 Ok(1)
460 }
461}
462
463pub(crate) fn set_local(state: &mut LuaState) -> Result<usize, LuaError> {
468 let (arg, other_thread) = getthread(state);
469 let target_state = resolve_debug_thread_target(state, &other_thread);
470
471 let level = state.check_arg_integer(arg + 1)? as i32;
472 let nvar = state.check_arg_integer(arg + 2)? as i32;
473
474 let mut ar = DebugInfo::default();
475
476 state.check_arg_any(arg + 3)?;
477 lua_vm::api::set_top(state, arg + 3)?;
478
479 let name = match target_state {
480 DebugThreadTarget::Current | DebugThreadTarget::Unavailable => {
481 if !state.get_stack_level(level, &mut ar) {
482 return Err(LuaError::arg_error(arg + 1, "level out of range"));
483 }
484 check_cross_thread_stack(state, true, 1)?;
485 let name = state.set_local_at(&ar, nvar)?;
486 if name.is_none() {
487 state.pop_n(1);
488 }
489 name
490 }
491 DebugThreadTarget::Other(target_state) => {
492 let new_val = state.get_at(state.top_idx() - 1);
493 let mut target = target_state.borrow_mut();
494 if !target.get_stack_level(level, &mut ar) {
495 return Err(LuaError::arg_error(arg + 1, "level out of range"));
496 }
497 check_cross_thread_stack(state, false, 1)?;
498 target.push(new_val);
499 let name = target.set_local_at(&ar, nvar)?;
500 if name.is_none() {
501 target.pop_n(1);
502 }
503 state.pop_n(1);
504 name
505 }
506 };
507
508 match name {
509 Some(n) => {
510 let ls = state.intern_str(&n)?;
511 state.push(LuaValue::Str(ls));
512 }
513 None => { state.push(LuaValue::Nil); }
514 }
515 Ok(1)
516}
517
518fn aux_upvalue(state: &mut LuaState, get: bool) -> Result<usize, LuaError> {
529 let n = state.check_arg_integer(2)? as i32;
530 state.check_arg_type(1, LuaType::Function)?;
531
532 let name: Option<Vec<u8>> = if get {
533 state.get_upvalue(1, n)?
535 } else {
536 state.set_upvalue(1, n)?
538 };
539
540 let name_ref = match name {
541 Some(n) => n,
542 None => return Ok(0),
543 };
544
545 let ls = state.intern_str(&name_ref)?;
546 state.push(LuaValue::Str(ls));
547
548 if get {
551 state.insert(-2)?;
552 }
553
554 Ok(if get { 2 } else { 1 })
555}
556
557pub(crate) fn get_upvalue(state: &mut LuaState) -> Result<usize, LuaError> {
560 aux_upvalue(state, true)
561}
562
563pub(crate) fn set_upvalue(state: &mut LuaState) -> Result<usize, LuaError> {
567 state.check_arg_any(3)?;
568 aux_upvalue(state, false)
569}
570
571fn check_upval(
576 state: &mut LuaState,
577 argf: i32,
578 argnup: i32,
579 require_valid: bool,
580) -> Result<(Option<UpvalId>, i32), LuaError> {
581 let nup = state.check_arg_integer(argnup)? as i32;
582 state.check_arg_type(argf, LuaType::Function)?;
583 let id: Option<UpvalId> = match state.upvalue_id(argf, nup) {
588 Ok(p) if p.is_null() => None,
589 Ok(p) => Some(p as usize),
590 Err(_) => None,
591 };
592 if require_valid && id.is_none() {
593 return Err(LuaError::arg_error(argnup, "invalid upvalue index"));
594 }
595 Ok((id, nup))
596}
597
598pub(crate) fn upvalue_id(state: &mut LuaState) -> Result<usize, LuaError> {
602 let (id, _nup) = check_upval(state, 1, 2, false)?;
603 match id {
604 Some(uid) => {
605 lua_vm::api::push_light_userdata(state, uid as *mut core::ffi::c_void);
606 }
607 None => {
608 state.push_fail()?;
609 }
610 }
611 Ok(1)
612}
613
614pub(crate) fn upvalue_join(state: &mut LuaState) -> Result<usize, LuaError> {
618 let (_id1, n1) = check_upval(state, 1, 2, true)?;
619 let (_id2, n2) = check_upval(state, 3, 4, true)?;
620 if state.is_c_function_at(1) {
621 return Err(LuaError::arg_error(1, "Lua function expected"));
622 }
623 if state.is_c_function_at(3) {
624 return Err(LuaError::arg_error(3, "Lua function expected"));
625 }
626 state.join_upvalues(1, n1, 3, n2)?;
627 Ok(0)
628}
629
630pub(crate) fn hookf(state: &mut LuaState, event: i32, currentline: i32) -> Result<(), LuaError> {
636 state.get_registry_field(HOOKKEY)?;
637 state.push_thread()?;
638 if state.raw_get(-2)? == LuaType::Function {
639 let event_idx = event.clamp(0, HOOKNAMES.len() as i32 - 1) as usize;
640 let event_str = state.intern_str(HOOKNAMES[event_idx])?;
641 state.push(LuaValue::Str(event_str));
642
643 if currentline >= 0 {
644 state.push(LuaValue::Int(currentline as i64));
645 } else {
646 state.push(LuaValue::Nil);
647 }
648
649 state.call(2, 0)?;
650 }
651 Ok(())
654}
655
656fn make_mask(smask: &[u8], count: i32) -> u32 {
660 let mut mask: u32 = 0;
661 if smask.contains(&b'c') {
662 mask |= MASK_CALL;
663 }
664 if smask.contains(&b'r') {
665 mask |= MASK_RET;
666 }
667 if smask.contains(&b'l') {
668 mask |= MASK_LINE;
669 }
670 if count > 0 {
671 mask |= MASK_COUNT;
672 }
673 mask
674}
675
676fn unmake_mask(mask: u32) -> Vec<u8> {
680 let mut smask = Vec::with_capacity(3);
681 if mask & MASK_CALL != 0 {
682 smask.push(b'c');
683 }
684 if mask & MASK_RET != 0 {
685 smask.push(b'r');
686 }
687 if mask & MASK_LINE != 0 {
688 smask.push(b'l');
689 }
690 smask
691}
692
693pub(crate) fn set_hook(state: &mut LuaState) -> Result<usize, LuaError> {
697 let (arg, other_thread) = getthread(state);
698 let target_is_self = other_thread.is_none();
699
700 let hook_active: bool;
701 let mask: u32;
702 let count: i32;
703
704 if matches!(state.type_at(arg + 1), LuaType::None | LuaType::Nil) {
705 lua_vm::api::set_top(state, arg + 1)?;
706 hook_active = false;
707 mask = 0;
708 count = 0;
709 } else {
710 let smask: Vec<u8> = state.check_arg_string(arg + 2)?.to_vec();
711 state.check_arg_type(arg + 1, LuaType::Function)?;
712 count = state.opt_arg_integer(arg + 3, 0)? as i32;
713 hook_active = true;
714 mask = make_mask(&smask, count);
715 }
716
717 if !state.get_or_create_registry_subtable(HOOKKEY)? {
718 let k = state.intern_str(b"k")?;
721 state.push(LuaValue::Str(k));
722 state.set_field(-2, b"__mode")?;
723 state.push_value_at(-1)?;
724 state.set_metatable(-2)?;
725 }
726
727 check_cross_thread_stack(state, target_is_self, 1)?;
728 let target_state = resolve_debug_thread_target(state, &other_thread);
729 match &target_state {
730 DebugThreadTarget::Other(st) => {
731 st.borrow_mut().ensure_stack(1, "stack overflow")?;
732 }
733 DebugThreadTarget::Current => {}
734 DebugThreadTarget::Unavailable => {}
735 }
736
737 if target_is_self {
738 state.push_thread()?;
739 } else {
740 let thr = other_thread.clone().expect("other_thread is Some when target_is_self is false");
747 state.push(lua_types::value::LuaValue::Thread(thr));
748 }
749 state.push_value_at(arg + 1)?;
750 state.raw_set(-3)?;
751
752 let hook_box: Option<Box<dyn FnMut(&mut LuaState, &lua_vm::debug::LuaDebug)>> = if hook_active {
753 Some(Box::new(|st, ar| {
754 let _ = hookf(st, ar.event, ar.currentline);
755 }))
756 } else {
757 None
758 };
759 match target_state {
760 DebugThreadTarget::Current => {
761 lua_vm::debug::set_hook(state, hook_box, mask as i32, count);
762 }
763 DebugThreadTarget::Other(target_state) => {
764 lua_vm::debug::set_hook(&mut target_state.borrow_mut(), hook_box, mask as i32, count);
765 }
766 DebugThreadTarget::Unavailable => {
767 return Ok(0);
771 }
772 }
773
774 Ok(0)
775}
776
777pub(crate) fn get_hook(state: &mut LuaState) -> Result<usize, LuaError> {
781 let (_arg, other_thread) = getthread(state);
782 let target_is_self = other_thread.is_none();
783 let target_state = resolve_debug_thread_target(state, &other_thread);
784
785 let (mask, hook_is_set, hook_is_internal, hook_count) = match target_state {
786 DebugThreadTarget::Current => {
787 (
788 state.get_hook_mask(),
789 state.hook_is_set(),
790 state.hook_is_internal_lua_hook(),
791 state.get_hook_count(),
792 )
793 }
794 DebugThreadTarget::Other(target_state) => {
795 let mut target_state = target_state.borrow_mut();
796 (
797 target_state.get_hook_mask(),
798 target_state.hook_is_set(),
799 target_state.hook_is_internal_lua_hook(),
800 target_state.get_hook_count(),
801 )
802 }
803 DebugThreadTarget::Unavailable => (0u32, false, false, 0i32),
804 };
805
806 if !hook_is_set {
807 state.push_fail()?;
808 return Ok(1);
809 }
810
811 if !hook_is_internal {
813 let s = state.intern_str(b"external hook")?;
814 state.push(LuaValue::Str(s));
815 } else {
816 state.get_registry_field(HOOKKEY)?;
817 check_cross_thread_stack(state, target_is_self, 1)?;
818 if target_is_self {
819 state.push_thread()?;
820 } else {
821 let key_thread = other_thread
822 .expect("other_thread is Some when target_is_self is false")
823 .clone();
824 state.push(lua_types::value::LuaValue::Thread(key_thread));
825 }
826 state.raw_get(-2)?;
827 state.remove(-2)?;
828 }
829
830 let smask = unmake_mask(mask);
831 let ls = state.intern_str(&smask)?;
832 state.push(LuaValue::Str(ls));
833
834 state.push(LuaValue::Int(hook_count as i64));
835
836 Ok(3)
837}
838
839pub(crate) fn debug_interactive(state: &mut LuaState) -> Result<usize, LuaError> {
846 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
847 {
848 let _ = state;
849 return Err(LuaError::runtime(format_args!(
850 "debug.debug interactive stdin not available in this host"
851 )));
852 }
853
854 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
855 {
856 let stdin = io::stdin();
857 loop {
858 eprint!("lua_debug> ");
859 let _ = io::stderr().flush();
860
861 let mut line = String::new();
866 let n = stdin
867 .lock()
868 .read_line(&mut line)
869 .map_err(|e| LuaError::runtime(format_args!("stdin read error: {}", e)))?;
870
871 if n == 0 || line == "cont\n" {
872 return Ok(0);
873 }
874
875 let bytes: &[u8] = line.as_bytes();
876
877 let result = state
880 .load_buffer(bytes, b"=(debug command)", None)
881 .and_then(|_| state.protected_call(0, 0, 0));
882
883 if let Err(_) = result {
884 eprintln!("(error in debug command)");
889 state.pop_n(1);
890 }
891
892 lua_vm::api::set_top(state, 0)?;
893 }
894 }
895}
896
897pub(crate) fn traceback(state: &mut LuaState) -> Result<usize, LuaError> {
904 let (arg, other_thread) = getthread(state);
905 let target_is_self = other_thread.is_none();
906
907 let msg_owned: Option<Vec<u8>> = state
909 .to_lua_string(arg + 1)
910 .map(|s: GcRef<LuaString>| s.as_bytes().to_vec());
911
912 let arg1_ty = state.type_at(arg + 1);
913 if msg_owned.is_none() && !matches!(arg1_ty, LuaType::None | LuaType::Nil) {
914 state.push_value_at(arg + 1)?;
915 } else {
916 let default_level: i64 = if target_is_self { 1 } else { 0 };
917 let level = state.opt_arg_integer(arg + 2, default_level)? as i32;
918
919 match resolve_debug_thread_target(state, &other_thread) {
920 DebugThreadTarget::Current => {
921 crate::auxlib::traceback(state, None, msg_owned.as_deref(), level)?;
922 }
923 DebugThreadTarget::Other(target_state) => {
924 let mut target_state = target_state.borrow_mut();
925 crate::auxlib::traceback(
926 state,
927 Some(&mut *target_state),
928 msg_owned.as_deref(),
929 level,
930 )?;
931 }
932 DebugThreadTarget::Unavailable => {
933 crate::auxlib::traceback(state, None, msg_owned.as_deref(), level)?;
934 }
935 }
936 }
937 Ok(1)
938}
939
940pub(crate) fn set_c_stack_limit(state: &mut LuaState) -> Result<usize, LuaError> {
944 let limit = state.check_arg_integer(1)? as i32;
945 let res = state.set_c_stack_limit(limit)?;
946 state.push(LuaValue::Int(res as i64));
947 Ok(1)
948}
949
950pub(crate) const DBLIB: &[(&[u8], LibFn)] = &[
955 (b"debug", debug_interactive as LibFn),
956 (b"getuservalue", get_uservalue as LibFn),
957 (b"gethook", get_hook as LibFn),
958 (b"getinfo", get_info as LibFn),
959 (b"getlocal", get_local as LibFn),
960 (b"getregistry", get_registry as LibFn),
961 (b"getmetatable", get_metatable as LibFn),
962 (b"getupvalue", get_upvalue as LibFn),
963 (b"upvaluejoin", upvalue_join as LibFn),
964 (b"upvalueid", upvalue_id as LibFn),
965 (b"setuservalue", set_uservalue as LibFn),
966 (b"sethook", set_hook as LibFn),
967 (b"setlocal", set_local as LibFn),
968 (b"setmetatable", set_metatable as LibFn),
969 (b"setupvalue", set_upvalue as LibFn),
970 (b"traceback", traceback as LibFn),
971 (b"setcstacklimit", set_c_stack_limit as LibFn),
972];
973
974pub fn open_debug(state: &mut LuaState) -> Result<usize, LuaError> {
978 state.new_lib(DBLIB)?;
979 Ok(1)
980}
981
982