1use core::str;
2
3use luau_common::ByteSlice;
4
5use crate::debug::LuaDebug;
6use crate::native::{NativeCallContext, NativeCallResult, NativeFunction};
7use crate::thread::Thread;
8use crate::types::LUA_TFUNCTION;
9
10static DEBUG_LIB: [NativeFunction; 2] = [
11 NativeFunction {
12 name: "info",
13 function: db_info,
14 },
15 NativeFunction {
16 name: "traceback",
17 function: db_traceback,
18 },
19];
20
21fn get_thread(thread: &Thread) -> (Option<Thread>, i32) {
23 if let Some(other) = unsafe { thread.to_thread(1) } {
24 (Some(other), 1)
25 } else {
26 (None, 0)
27 }
28}
29
30fn db_info(ctx: NativeCallContext) -> NativeCallResult {
32 let thread = ctx.raw_thread();
33 unsafe {
34 let (source, arg) = get_thread(thread);
35 let source = source.as_ref().unwrap_or(thread);
36 let mut source_top = 0;
37
38 if source != thread {
39 source.raw_check_stack(1)?;
40 source_top = source.get_top();
41 }
42
43 let level = if thread.is_number(arg + 1) != 0 {
44 let level = thread.check_integer(arg + 1)?;
45 if level < 0 {
46 return thread
47 .lua_arg_error(arg + 1, "level can't be negative")
48 .map_err(Into::into);
49 }
50 level
51 } else if arg == 0 && thread.type_of(1) == LUA_TFUNCTION {
52 -thread.get_top()
53 } else {
54 return thread
55 .lua_arg_error(arg + 1, "function or level expected")
56 .map_err(Into::into);
57 };
58
59 let options = thread.check_string(arg + 2)?;
60 let options = match str::from_utf8(options) {
61 Ok(options) => options,
62 Err(_) => {
63 return thread
64 .lua_arg_error(arg + 2, "invalid option")
65 .map_err(Into::into);
66 }
67 };
68 let mut ar = LuaDebug::default();
69 if source.get_info(level, options, &mut ar)? == 0 {
70 return Ok(0);
71 }
72
73 let mut results = 0;
74 let mut occurs = [false; 26];
75 for &byte in options.as_bytes() {
76 if byte.is_ascii_lowercase() {
77 let slot = (byte - b'a') as usize;
78 if occurs[slot] {
79 if source != thread {
80 source.restore_top(source_top);
81 }
82 return thread
83 .lua_arg_error(arg + 2, "duplicate option")
84 .map_err(Into::into);
85 }
86 occurs[slot] = true;
87 }
88
89 match byte {
90 b's' => {
91 thread.push_string(ar.short_src())?;
92 results += 1;
93 }
94 b'l' => {
95 thread.push_integer(ar.currentline)?;
96 results += 1;
97 }
98 b'n' => {
99 if let Some(name) = &ar.name {
100 thread.push_string(name)?;
101 } else {
102 thread.push_string("")?;
103 }
104 results += 1;
105 }
106 b'f' => {
107 if source == thread {
108 thread.push_value(-1 - results)?;
109 } else {
110 source.x_move(thread, 1)?;
111 }
112 results += 1;
113 }
114 b'a' => {
115 thread.push_integer(ar.nparams as i32)?;
116 thread.push_boolean(i32::from(ar.is_vararg))?;
117 results += 2;
118 }
119 _ => {
120 if source != thread {
121 source.restore_top(source_top);
122 }
123 return thread
124 .lua_arg_error(arg + 2, "invalid option")
125 .map_err(Into::into);
126 }
127 }
128 }
129
130 Ok(results as usize)
131 }
132}
133
134fn db_traceback(ctx: NativeCallContext) -> NativeCallResult {
136 let thread = ctx.raw_thread();
137 unsafe {
138 let (source, arg) = get_thread(thread);
139 let source = source.as_ref().unwrap_or(thread);
140 let message = thread.opt_string(arg + 1)?.map(|value| value.as_bstr());
141 let level = thread.opt_integer(arg + 2, if source == thread { 1 } else { 0 })?;
142 if level < 0 {
143 return thread
144 .lua_arg_error(arg + 2, "level can't be negative")
145 .map_err(Into::into);
146 }
147
148 thread.traceback(Some(source), message, level)?;
149 Ok(1)
150 }
151}
152
153impl Thread {
154 pub unsafe fn open_debug(&self) -> NativeCallResult {
156 unsafe { self.register(Some(super::LUA_DBLIB_NAME), &DEBUG_LIB[..])? };
157 Ok(1)
158 }
159}