Skip to main content

luau_vm/libs/
debug.rs

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
21/// `getthread`
22fn 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
30/// `db_info`
31fn 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
134/// `db_traceback`
135fn 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    /// `luaopen_debug`
155    pub unsafe fn open_debug(&self) -> NativeCallResult {
156        unsafe { self.register(Some(super::LUA_DBLIB_NAME), &DEBUG_LIB[..])? };
157        Ok(1)
158    }
159}