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, LuaStatus, 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(
191 state: &mut LuaState,
192 target_is_self: bool,
193 fname: &[u8],
194) -> Result<(), LuaError> {
195 if target_is_self {
196 state.rotate(-2, 1)?;
197 } else {
198 state.push(LuaValue::Nil);
199 }
200 state.set_field(-2, fname)
201}
202
203fn move_stack_option_from_target(
204 state: &mut LuaState,
205 target: &mut LuaState,
206 fname: &[u8],
207) -> Result<(), LuaError> {
208 let val = target.get_at(target.top_idx() - 1);
209 target.pop_n(1);
210 state.push(val);
211 state.set_field(-2, fname)
212}
213
214pub(crate) fn get_registry(state: &mut LuaState) -> Result<usize, LuaError> {
219 state.push_registry()?;
220 Ok(1)
221}
222
223pub(crate) fn get_metatable(state: &mut LuaState) -> Result<usize, LuaError> {
226 state.check_arg_any(1)?;
227 if !state.get_metatable(1)? {
228 state.push(LuaValue::Nil);
229 }
230 Ok(1)
231}
232
233pub(crate) fn set_metatable(state: &mut LuaState) -> Result<usize, LuaError> {
237 let t = state.type_at(2);
238 if !(t == LuaType::Nil || t == LuaType::Table) {
239 let got = state.arg(2);
240 return Err(LuaError::type_arg_error(2, "nil or table", &got));
241 }
242 lua_vm::api::set_top(state, 2)?;
243 state.set_metatable(1)?;
244 Ok(1)
245}
246
247pub(crate) fn get_uservalue(state: &mut LuaState) -> Result<usize, LuaError> {
252 let n = state.opt_arg_integer(2, 1)? as i32;
253 if state.type_at(1) != LuaType::UserData {
254 state.push_fail()?;
255 return Ok(1);
256 }
257 let ty = state.get_iuservalue(1, n)?;
258 if ty != LuaType::None {
259 state.push(LuaValue::Bool(true));
260 return Ok(2);
261 }
262 Ok(1)
263}
264
265pub(crate) fn set_uservalue(state: &mut LuaState) -> Result<usize, LuaError> {
269 let n = state.opt_arg_integer(3, 1)? as i32;
270 state.check_arg_type(1, LuaType::UserData)?;
271 state.check_arg_any(2)?;
272 lua_vm::api::set_top(state, 2)?;
273 if !state.set_iuservalue(1, n)? {
274 state.push_fail()?;
275 }
276 Ok(1)
277}
278
279pub(crate) fn get_info(state: &mut LuaState) -> Result<usize, LuaError> {
284 let mut ar = DebugInfo::default();
285
286 let (arg, other_thread) = getthread(state);
287 let target_is_self = other_thread.is_none();
288 let target_state = resolve_debug_thread_target(state, &other_thread);
289
290 let default_opts: &[u8] = match state.global().lua_version {
296 LuaVersion::V51 => b"flnSu",
297 LuaVersion::V52 | LuaVersion::V53 => b"flnStu",
298 _ => b"flnSrtu",
299 };
300 let raw_opts: Vec<u8> = state.opt_arg_string(arg + 2, default_opts)?.to_vec();
302
303 check_cross_thread_stack(state, target_is_self, 3)?;
304
305 if raw_opts.first() == Some(&b'>') {
306 let msg: &[u8] = match state.global().lua_version {
313 LuaVersion::V54 | LuaVersion::V55 => b"invalid option '>'",
314 _ => b"invalid option",
315 };
316 return Err(lua_vm::debug::arg_error_impl(state, arg + 2, msg));
317 }
318
319 let options: Vec<u8>;
321 let info_target_owner: Option<Rc<RefCell<LuaState>>>;
322 let mut info_target: Option<crate::coro_lib::RootedThreadBorrow<'_>> = None;
323 let mut info_target_is_self = target_is_self;
324
325 if state.type_at(arg + 1) == LuaType::Function {
326 let mut prefixed = Vec::with_capacity(raw_opts.len() + 1);
327 prefixed.push(b'>');
328 prefixed.extend_from_slice(&raw_opts);
329 options = prefixed;
330
331 info_target_is_self = true;
344 state.push_value_at(arg + 1)?;
345 if state.get_debug_info(&options, &mut ar).is_err() {
346 return Err(lua_vm::debug::arg_error_impl(
347 state,
348 arg + 2,
349 b"invalid option",
350 ));
351 }
352 } else {
353 options = raw_opts;
354
355 let level = state.check_arg_integer(arg + 1)? as i32;
356 match target_state {
357 DebugThreadTarget::Current | DebugThreadTarget::Unavailable => {
358 info_target_is_self = true;
359 if !state.get_stack_level(level, &mut ar) {
360 state.push_fail()?;
361 return Ok(1);
362 }
363
364 if state.get_debug_info(&options, &mut ar).is_err() {
365 return Err(lua_vm::debug::arg_error_impl(
366 state,
367 arg + 2,
368 b"invalid option",
369 ));
370 }
371 }
372 DebugThreadTarget::Other(target_state) => {
373 info_target_owner = Some(target_state);
374 let mut target = crate::coro_lib::borrow_thread_rooted(
375 state,
376 info_target_owner
377 .as_ref()
378 .expect("target owner just stored"),
379 );
380 if !target.get_stack_level(level, &mut ar) {
381 state.push_fail()?;
382 return Ok(1);
383 }
384 if target.get_debug_info(&options, &mut ar).is_err() {
385 return Err(lua_vm::debug::arg_error_impl(
386 state,
387 arg + 2,
388 b"invalid option",
389 ));
390 }
391 target.resnapshot();
392 info_target = Some(target);
393 }
394 }
395 }
396
397 let result_tbl = state.new_table();
398 state.push(LuaValue::Table(result_tbl));
399
400 if options.contains(&b'S') {
401 let src = state.intern_str(ar.source_bytes())?;
402 state.push(LuaValue::Str(src));
403 state.set_field(-2, b"source")?;
404
405 settabss(state, b"short_src", Some(ar.short_src_bytes()))?;
406 settabsi(state, b"linedefined", ar.linedefined)?;
407 settabsi(state, b"lastlinedefined", ar.lastlinedefined)?;
408 settabss(state, b"what", Some(ar.what_bytes()))?;
409 }
410 if options.contains(&b'l') {
411 settabsi(state, b"currentline", ar.currentline)?;
412 }
413 if options.contains(&b'u') {
414 settabsi(state, b"nups", ar.nups as i32)?;
415 if !matches!(state.global().lua_version, LuaVersion::V51) {
416 settabsi(state, b"nparams", ar.nparams as i32)?;
417 settabsb(state, b"isvararg", ar.isvararg)?;
418 }
419 }
420 if options.contains(&b'n') {
421 let name_opt: Option<&[u8]> = ar.name.as_deref();
422 settabss(state, b"name", name_opt)?;
423 settabss(state, b"namewhat", Some(ar.namewhat_bytes()))?;
424 }
425 if options.contains(&b'r') {
426 settabsi(state, b"ftransfer", ar.ftransfer as i32)?;
427 settabsi(state, b"ntransfer", ar.ntransfer as i32)?;
428 }
429 if options.contains(&b't') {
430 settabsb(state, b"istailcall", ar.istailcall)?;
431 if matches!(state.global().lua_version, LuaVersion::V55) {
432 settabsi(state, b"extraargs", ar.extraargs as i32)?;
433 }
434 }
435 if options.contains(&b'L') {
440 if info_target_is_self {
441 treat_stack_option(state, true, b"activelines")?;
442 } else if let Some(target) = info_target.as_mut() {
443 move_stack_option_from_target(state, &mut **target, b"activelines")?;
444 } else {
445 state.push(LuaValue::Nil);
446 state.set_field(-2, b"activelines")?;
447 }
448 }
449 if options.contains(&b'f') {
450 if info_target_is_self {
451 treat_stack_option(state, true, b"func")?;
452 } else if let Some(target) = info_target.as_mut() {
453 move_stack_option_from_target(state, &mut **target, b"func")?;
454 } else {
455 state.push(LuaValue::Nil);
456 state.set_field(-2, b"func")?;
457 }
458 }
459
460 Ok(1)
461}
462
463fn function_arg_form_supported(state: &LuaState) -> bool {
474 !matches!(state.global().lua_version, LuaVersion::V51)
475}
476
477pub(crate) fn get_local(state: &mut LuaState) -> Result<usize, LuaError> {
485 let (arg, other_thread) = getthread(state);
486 let target_state = resolve_debug_thread_target(state, &other_thread);
487
488 let nvar = state.check_arg_integer(arg + 2)? as i32;
489
490 if function_arg_form_supported(state) && state.type_at(arg + 1) == LuaType::Function {
491 state.push_value_at(arg + 1)?;
492 let name = state.get_param_name(0, nvar)?;
493 match name {
494 Some(n) => {
495 let ls = state.intern_str(&n)?;
496 state.push(LuaValue::Str(ls));
497 }
498 None => {
499 state.push(LuaValue::Nil);
500 }
501 }
502 return Ok(1);
503 }
504
505 let level = state.check_arg_integer(arg + 1)? as i32;
507 let mut ar = DebugInfo::default();
508
509 let name = match target_state {
510 DebugThreadTarget::Current | DebugThreadTarget::Unavailable => {
511 if !state.get_stack_level(level, &mut ar) {
512 return Err(lua_vm::debug::arg_error_impl(
513 state,
514 arg + 1,
515 b"level out of range",
516 ));
517 }
518 check_cross_thread_stack(state, true, 1)?;
519 state.get_local_at(&ar, nvar)?
521 }
522 DebugThreadTarget::Other(target_state) => {
523 let mut target = crate::coro_lib::borrow_thread_rooted(state, &target_state);
524 if !target.get_stack_level(level, &mut ar) {
525 return Err(lua_vm::debug::arg_error_impl(
526 state,
527 arg + 1,
528 b"level out of range",
529 ));
530 }
531 check_cross_thread_stack(state, false, 1)?;
532 let name = target.get_local_at(&ar, nvar)?;
533 if name.is_some() {
534 let val = target.get_at(target.top_idx() - 1);
535 target.pop_n(1);
536 state.push(val);
537 }
538 name
539 }
540 };
541
542 if let Some(n) = name {
543 let ls = state.intern_str(&n)?;
544 state.push(LuaValue::Str(ls));
545 state.rotate(-2, 1)?;
546 Ok(2)
547 } else {
548 state.push_fail()?;
549 Ok(1)
550 }
551}
552
553pub(crate) fn set_local(state: &mut LuaState) -> Result<usize, LuaError> {
558 let (arg, other_thread) = getthread(state);
559 let target_state = resolve_debug_thread_target(state, &other_thread);
560
561 let level = state.check_arg_integer(arg + 1)? as i32;
562 let nvar = state.check_arg_integer(arg + 2)? as i32;
563
564 let mut ar = DebugInfo::default();
565
566 state.check_arg_any(arg + 3)?;
567 lua_vm::api::set_top(state, arg + 3)?;
568
569 let name = match target_state {
570 DebugThreadTarget::Current | DebugThreadTarget::Unavailable => {
571 if !state.get_stack_level(level, &mut ar) {
572 return Err(lua_vm::debug::arg_error_impl(
573 state,
574 arg + 1,
575 b"level out of range",
576 ));
577 }
578 check_cross_thread_stack(state, true, 1)?;
579 let name = state.set_local_at(&ar, nvar)?;
580 if name.is_none() {
581 state.pop_n(1);
582 }
583 name
584 }
585 DebugThreadTarget::Other(target_state) => {
586 let new_val = state.get_at(state.top_idx() - 1);
587 let mut target = crate::coro_lib::borrow_thread_rooted(state, &target_state);
588 if !target.get_stack_level(level, &mut ar) {
589 return Err(lua_vm::debug::arg_error_impl(
590 state,
591 arg + 1,
592 b"level out of range",
593 ));
594 }
595 check_cross_thread_stack(state, false, 1)?;
596 target.push(new_val);
597 let name = target.set_local_at(&ar, nvar)?;
598 if name.is_none() {
599 target.pop_n(1);
600 }
601 state.pop_n(1);
602 name
603 }
604 };
605
606 match name {
607 Some(n) => {
608 let ls = state.intern_str(&n)?;
609 state.push(LuaValue::Str(ls));
610 }
611 None => {
612 state.push(LuaValue::Nil);
613 }
614 }
615 Ok(1)
616}
617
618fn aux_upvalue(state: &mut LuaState, get: bool) -> Result<usize, LuaError> {
629 let n = state.check_arg_integer(2)? as i32;
630 state.check_arg_type(1, LuaType::Function)?;
631
632 let name: Option<Vec<u8>> = if get {
633 state.get_upvalue(1, n)?
635 } else {
636 state.set_upvalue(1, n)?
638 };
639
640 let name_ref = match name {
641 Some(n) => n,
642 None => return Ok(0),
643 };
644
645 let ls = state.intern_str(&name_ref)?;
646 state.push(LuaValue::Str(ls));
647
648 if get {
651 state.insert(-2)?;
652 }
653
654 Ok(if get { 2 } else { 1 })
655}
656
657pub(crate) fn get_upvalue(state: &mut LuaState) -> Result<usize, LuaError> {
660 aux_upvalue(state, true)
661}
662
663pub(crate) fn set_upvalue(state: &mut LuaState) -> Result<usize, LuaError> {
667 state.check_arg_any(3)?;
668 aux_upvalue(state, false)
669}
670
671fn check_upval(
676 state: &mut LuaState,
677 argf: i32,
678 argnup: i32,
679 require_valid: bool,
680) -> Result<(Option<UpvalId>, i32), LuaError> {
681 let nup = state.check_arg_integer(argnup)? as i32;
682 state.check_arg_type(argf, LuaType::Function)?;
683 let id: Option<UpvalId> = match state.upvalue_id(argf, nup) {
684 Ok(p) if p.is_null() => None,
685 Ok(p) => Some(p as usize),
686 Err(_) => None,
687 };
688 if require_valid && id.is_none() {
689 return Err(lua_vm::debug::arg_error_impl(
690 state,
691 argnup,
692 b"invalid upvalue index",
693 ));
694 }
695 Ok((id, nup))
696}
697
698pub(crate) fn upvalue_id(state: &mut LuaState) -> Result<usize, LuaError> {
708 let require_valid = matches!(
709 state.global().lua_version,
710 LuaVersion::V51 | LuaVersion::V52 | LuaVersion::V53
711 );
712 let (id, _nup) = check_upval(state, 1, 2, require_valid)?;
713 match id {
714 Some(uid) => {
715 lua_vm::api::push_light_userdata(state, uid as *mut core::ffi::c_void);
716 }
717 None => {
718 state.push_fail()?;
719 }
720 }
721 Ok(1)
722}
723
724pub(crate) fn upvalue_join(state: &mut LuaState) -> Result<usize, LuaError> {
728 let (_id1, n1) = check_upval(state, 1, 2, true)?;
729 let (_id2, n2) = check_upval(state, 3, 4, true)?;
730 if state.is_c_function_at(1) {
731 return Err(lua_vm::debug::arg_error_impl(
732 state,
733 1,
734 b"Lua function expected",
735 ));
736 }
737 if state.is_c_function_at(3) {
738 return Err(lua_vm::debug::arg_error_impl(
739 state,
740 3,
741 b"Lua function expected",
742 ));
743 }
744 state.join_upvalues(1, n1, 3, n2)?;
745 Ok(0)
746}
747
748pub(crate) fn hookf(state: &mut LuaState, event: i32, currentline: i32) -> Result<(), LuaError> {
754 state.get_registry_field(HOOKKEY)?;
755 state.push_thread()?;
756 if state.raw_get(-2)? == LuaType::Function {
757 let event_idx = event.clamp(0, HOOKNAMES.len() as i32 - 1) as usize;
758 let name = if event_idx == 4
759 && state.global().lua_version == lua_types::LuaVersion::V51
760 {
761 HOOKNAME_TAILRET_51
762 } else {
763 HOOKNAMES[event_idx]
764 };
765 let event_str = state.intern_str(name)?;
766 state.push(LuaValue::Str(event_str));
767
768 if currentline >= 0 {
769 state.push(LuaValue::Int(currentline as i64));
770 } else {
771 state.push(LuaValue::Nil);
772 }
773
774 state.call(2, 0)?;
775 }
776 Ok(())
779}
780
781fn make_mask(smask: &[u8], count: i32) -> u32 {
785 let mut mask: u32 = 0;
786 if smask.contains(&b'c') {
787 mask |= MASK_CALL;
788 }
789 if smask.contains(&b'r') {
790 mask |= MASK_RET;
791 }
792 if smask.contains(&b'l') {
793 mask |= MASK_LINE;
794 }
795 if count > 0 {
796 mask |= MASK_COUNT;
797 }
798 mask
799}
800
801fn unmake_mask(mask: u32) -> Vec<u8> {
805 let mut smask = Vec::with_capacity(3);
806 if mask & MASK_CALL != 0 {
807 smask.push(b'c');
808 }
809 if mask & MASK_RET != 0 {
810 smask.push(b'r');
811 }
812 if mask & MASK_LINE != 0 {
813 smask.push(b'l');
814 }
815 smask
816}
817
818pub(crate) fn set_hook(state: &mut LuaState) -> Result<usize, LuaError> {
822 let (arg, other_thread) = getthread(state);
823 let target_is_self = other_thread.is_none();
824
825 let hook_active: bool;
826 let mask: u32;
827 let count: i32;
828
829 if matches!(state.type_at(arg + 1), LuaType::None | LuaType::Nil) {
830 lua_vm::api::set_top(state, arg + 1)?;
831 hook_active = false;
832 mask = 0;
833 count = 0;
834 } else {
835 let smask: Vec<u8> = state.check_arg_string(arg + 2)?.to_vec();
836 state.check_arg_type(arg + 1, LuaType::Function)?;
837 count = state.opt_arg_integer(arg + 3, 0)? as i32;
838 hook_active = true;
839 mask = make_mask(&smask, count);
840 }
841
842 if !state.get_or_create_registry_subtable(HOOKKEY)? {
843 let k = state.intern_str(b"k")?;
846 state.push(LuaValue::Str(k));
847 state.set_field(-2, b"__mode")?;
848 state.push_value_at(-1)?;
849 state.set_metatable(-2)?;
850 }
851
852 check_cross_thread_stack(state, target_is_self, 1)?;
853 let target_state = resolve_debug_thread_target(state, &other_thread);
854 match &target_state {
855 DebugThreadTarget::Other(st) => {
856 st.borrow_mut().ensure_stack(1, "stack overflow")?;
857 }
858 DebugThreadTarget::Current => {}
859 DebugThreadTarget::Unavailable => {}
860 }
861
862 if target_is_self {
863 state.push_thread()?;
864 } else {
865 let thr = other_thread
872 .clone()
873 .expect("other_thread is Some when target_is_self is false");
874 state.push(lua_types::value::LuaValue::Thread(thr));
875 }
876 state.push_value_at(arg + 1)?;
877 state.raw_set(-3)?;
878
879 let hook_box: Option<Box<dyn FnMut(&mut LuaState, &lua_vm::debug::LuaDebug)>> = if hook_active {
880 Some(Box::new(|st, ar| {
881 let _ = hookf(st, ar.event, ar.currentline);
882 }))
883 } else {
884 None
885 };
886 match target_state {
887 DebugThreadTarget::Current => {
888 lua_vm::debug::set_hook(state, hook_box, mask as i32, count);
889 }
890 DebugThreadTarget::Other(target_state) => {
891 lua_vm::debug::set_hook(&mut target_state.borrow_mut(), hook_box, mask as i32, count);
892 }
893 DebugThreadTarget::Unavailable => {
894 return Ok(0);
898 }
899 }
900
901 Ok(0)
902}
903
904pub(crate) fn get_hook(state: &mut LuaState) -> Result<usize, LuaError> {
908 let (_arg, other_thread) = getthread(state);
909 let target_is_self = other_thread.is_none();
910 let target_state = resolve_debug_thread_target(state, &other_thread);
911
912 let (mask, hook_is_set, hook_is_internal, hook_count) = match target_state {
913 DebugThreadTarget::Current => (
914 state.get_hook_mask(),
915 state.hook_is_set(),
916 state.hook_is_internal_lua_hook(),
917 state.get_hook_count(),
918 ),
919 DebugThreadTarget::Other(target_state) => {
920 let mut target_state = target_state.borrow_mut();
921 (
922 target_state.get_hook_mask(),
923 target_state.hook_is_set(),
924 target_state.hook_is_internal_lua_hook(),
925 target_state.get_hook_count(),
926 )
927 }
928 DebugThreadTarget::Unavailable => (0u32, false, false, 0i32),
929 };
930
931 if !hook_is_set {
932 state.push_fail()?;
933 return Ok(1);
934 }
935
936 if !hook_is_internal {
937 let s = state.intern_str(b"external hook")?;
938 state.push(LuaValue::Str(s));
939 } else {
940 state.get_registry_field(HOOKKEY)?;
941 check_cross_thread_stack(state, target_is_self, 1)?;
942 if target_is_self {
943 state.push_thread()?;
944 } else {
945 let key_thread = other_thread
946 .expect("other_thread is Some when target_is_self is false")
947 .clone();
948 state.push(lua_types::value::LuaValue::Thread(key_thread));
949 }
950 state.raw_get(-2)?;
951 state.remove(-2)?;
952 }
953
954 let smask = unmake_mask(mask);
955 let ls = state.intern_str(&smask)?;
956 state.push(LuaValue::Str(ls));
957
958 state.push(LuaValue::Int(hook_count as i64));
959
960 Ok(3)
961}
962
963pub(crate) fn debug_interactive(state: &mut LuaState) -> Result<usize, LuaError> {
970 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
971 {
972 let _ = state;
973 return Err(LuaError::runtime(format_args!(
974 "debug.debug interactive stdin not available in this host"
975 )));
976 }
977
978 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
979 {
980 let stdin = io::stdin();
981 loop {
982 eprint!("lua_debug> ");
983 let _ = io::stderr().flush();
984
985 let mut line = String::new();
988 let n = stdin
989 .lock()
990 .read_line(&mut line)
991 .map_err(|e| LuaError::runtime(format_args!("stdin read error: {}", e)))?;
992
993 if n == 0 || line == "cont\n" {
994 return Ok(0);
995 }
996
997 let bytes: &[u8] = line.as_bytes();
998
999 let load_status = state.load_buffer(bytes, b"=(debug command)", None)?;
1000 let result: Result<(), LuaError> = if load_status == LuaStatus::Ok {
1001 state.protected_call(0, 0, 0)
1002 } else {
1003 let top = state.top_idx();
1013 let msg = state.get_at(top - 1);
1014 state.pop_n(1);
1015 Err(LuaError::Syntax(msg))
1016 };
1017
1018 if let Err(e) = result {
1019 let val = e.into_value();
1023 state.push(val);
1024 let msg: Vec<u8> = if matches!(
1031 state.global().lua_version,
1032 LuaVersion::V54 | LuaVersion::V55
1033 ) {
1034 crate::auxlib::to_lua_string(state, -1)?
1035 } else {
1036 match state.to_lua_string_bytes(-1) {
1040 Some(bytes) => bytes,
1041 None => b"(null)".to_vec(),
1042 }
1043 };
1044 let mut err = io::stderr();
1045 let _ = err.write_all(&msg);
1046 let _ = err.write_all(b"\n");
1047 let _ = err.flush();
1048 }
1049
1050 lua_vm::api::set_top(state, 0)?;
1051 }
1052 }
1053}
1054
1055pub(crate) fn traceback(state: &mut LuaState) -> Result<usize, LuaError> {
1062 let (arg, other_thread) = getthread(state);
1063 let target_is_self = other_thread.is_none();
1064
1065 let msg_owned: Option<Vec<u8>> = state
1067 .to_lua_string(arg + 1)
1068 .map(|s: GcRef<LuaString>| s.as_bytes().to_vec());
1069
1070 let arg1_ty = state.type_at(arg + 1);
1071 if msg_owned.is_none() && !matches!(arg1_ty, LuaType::None | LuaType::Nil) {
1072 state.push_value_at(arg + 1)?;
1073 } else {
1074 let default_level: i64 = if target_is_self { 1 } else { 0 };
1075 let level = state.opt_arg_integer(arg + 2, default_level)? as i32;
1076
1077 match resolve_debug_thread_target(state, &other_thread) {
1078 DebugThreadTarget::Current => {
1079 crate::auxlib::traceback(state, None, msg_owned.as_deref(), level)?;
1080 }
1081 DebugThreadTarget::Other(target_state) => {
1082 let mut target_state = crate::coro_lib::borrow_thread_rooted(state, &target_state);
1083 crate::auxlib::traceback(
1084 state,
1085 Some(&mut *target_state),
1086 msg_owned.as_deref(),
1087 level,
1088 )?;
1089 }
1090 DebugThreadTarget::Unavailable => {
1091 crate::auxlib::traceback(state, None, msg_owned.as_deref(), level)?;
1092 }
1093 }
1094 }
1095 Ok(1)
1096}
1097
1098pub(crate) fn set_c_stack_limit(state: &mut LuaState) -> Result<usize, LuaError> {
1102 let limit = state.check_arg_integer(1)? as i32;
1103 let res = state.set_c_stack_limit(limit)?;
1104 state.push(LuaValue::Int(res as i64));
1105 Ok(1)
1106}
1107
1108pub(crate) const DBLIB: &[(&[u8], LibFn)] = &[
1113 (b"debug", debug_interactive as LibFn),
1114 (b"getuservalue", get_uservalue as LibFn),
1115 (b"gethook", get_hook as LibFn),
1116 (b"getinfo", get_info as LibFn),
1117 (b"getlocal", get_local as LibFn),
1118 (b"getregistry", get_registry as LibFn),
1119 (b"getmetatable", get_metatable as LibFn),
1120 (b"getupvalue", get_upvalue as LibFn),
1121 (b"upvaluejoin", upvalue_join as LibFn),
1122 (b"upvalueid", upvalue_id as LibFn),
1123 (b"setuservalue", set_uservalue as LibFn),
1124 (b"sethook", set_hook as LibFn),
1125 (b"setlocal", set_local as LibFn),
1126 (b"setmetatable", set_metatable as LibFn),
1127 (b"setupvalue", set_upvalue as LibFn),
1128 (b"traceback", traceback as LibFn),
1129 (b"setcstacklimit", set_c_stack_limit as LibFn),
1130];
1131
1132const DBLIB_DROP_V51: &[&[u8]] = &[
1139 b"getuservalue",
1140 b"setuservalue",
1141 b"upvalueid",
1142 b"upvaluejoin",
1143 b"setcstacklimit",
1144];
1145
1146pub fn open_debug(state: &mut LuaState) -> Result<usize, LuaError> {
1154 use lua_types::LuaVersion;
1155 let version = state.global().lua_version;
1156 let is_v51 = matches!(version, LuaVersion::V51);
1157 let has_setcstacklimit = matches!(version, LuaVersion::V54);
1158
1159 let filtered: Vec<(&[u8], LibFn)> = DBLIB
1160 .iter()
1161 .filter(|(name, _)| {
1162 if !has_setcstacklimit && *name == b"setcstacklimit".as_slice() {
1163 return false;
1164 }
1165 if is_v51 && DBLIB_DROP_V51.contains(name) {
1166 return false;
1167 }
1168 true
1169 })
1170 .copied()
1171 .collect();
1172 state.new_lib(&filtered)?;
1173
1174 if is_v51 {
1175 state.push_c_function(crate::base::debug_getfenv_fn)?;
1180 state.set_field(-2, b"getfenv")?;
1181 state.push_c_function(crate::base::debug_setfenv_fn)?;
1182 state.set_field(-2, b"setfenv")?;
1183 }
1184
1185 Ok(1)
1186}