Skip to main content

mlua/
debug.rs

1//! Lua debugging interface.
2//!
3//! This module provides access to the Lua debug interface, allowing inspection of the call stack,
4//! and function information. The main type is [`struct@Debug`] for accessing debug information.
5#![cfg_attr(
6    not(feature = "luau"),
7    doc = "\nDebug hooks are configured with [`HookTriggers`]."
8)]
9
10use std::borrow::Cow;
11use std::os::raw::c_int;
12
13use ffi::{lua_Debug, lua_State};
14
15use crate::function::Function;
16use crate::state::RawLua;
17use crate::util::{StackGuard, assert_stack, linenumber_to_usize, ptr_to_lossy_str, ptr_to_str};
18
19/// Contains information about currently executing Lua code.
20///
21/// You may call the methods on this structure to retrieve information about the Lua code executing
22/// at the specific level. Further information can be found in the Lua [documentation].
23///
24/// [documentation]: https://www.lua.org/manual/5.4/manual.html#lua_Debug
25pub struct Debug<'a> {
26    state: *mut lua_State,
27    lua: &'a RawLua,
28    #[cfg_attr(not(feature = "luau"), allow(unused))]
29    level: c_int,
30    ar: *mut lua_Debug,
31}
32
33impl<'a> Debug<'a> {
34    pub(crate) fn new(lua: &'a RawLua, level: c_int, ar: *mut lua_Debug) -> Self {
35        Debug {
36            state: lua.state(),
37            lua,
38            ar,
39            level,
40        }
41    }
42
43    /// Returns the specific event that triggered the hook.
44    ///
45    /// For [Lua 5.1] [`DebugEvent::TailCall`] is used for return events to indicate a return
46    /// from a function that did a tail call.
47    ///
48    /// [Lua 5.1]: https://www.lua.org/manual/5.1/manual.html#pdf-LUA_HOOKTAILRET
49    #[cfg(not(feature = "luau"))]
50    #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
51    pub fn event(&self) -> DebugEvent {
52        unsafe {
53            match (*self.ar).event {
54                ffi::LUA_HOOKCALL => DebugEvent::Call,
55                ffi::LUA_HOOKRET => DebugEvent::Ret,
56                ffi::LUA_HOOKTAILCALL => DebugEvent::TailCall,
57                ffi::LUA_HOOKLINE => DebugEvent::Line,
58                ffi::LUA_HOOKCOUNT => DebugEvent::Count,
59                event => DebugEvent::Unknown(event),
60            }
61        }
62    }
63
64    /// Returns the function that is running at the given level.
65    ///
66    /// Corresponds to the `f` "what" mask.
67    pub fn function(&self) -> Function {
68        unsafe {
69            let _sg = StackGuard::new(self.state);
70            assert_stack(self.state, 1);
71
72            #[cfg(not(feature = "luau"))]
73            mlua_assert!(
74                ffi::lua_getinfo(self.state, cstr!("f"), self.ar) != 0,
75                "lua_getinfo failed with `f`"
76            );
77            #[cfg(feature = "luau")]
78            mlua_assert!(
79                ffi::lua_getinfo(self.state, self.level, cstr!("f"), self.ar) != 0,
80                "lua_getinfo failed with `f`"
81            );
82
83            ffi::lua_xmove(self.state, self.lua.ref_thread(), 1);
84            Function(self.lua.pop_ref_thread())
85        }
86    }
87
88    /// Corresponds to the `n` "what" mask.
89    pub fn names(&self) -> DebugNames<'_> {
90        unsafe {
91            #[cfg(not(feature = "luau"))]
92            mlua_assert!(
93                ffi::lua_getinfo(self.state, cstr!("n"), self.ar) != 0,
94                "lua_getinfo failed with `n`"
95            );
96            #[cfg(feature = "luau")]
97            mlua_assert!(
98                ffi::lua_getinfo(self.state, self.level, cstr!("n"), self.ar) != 0,
99                "lua_getinfo failed with `n`"
100            );
101
102            DebugNames {
103                name: ptr_to_lossy_str((*self.ar).name),
104                #[cfg(not(feature = "luau"))]
105                name_what: ptr_to_str((*self.ar).namewhat).filter(|s| !s.is_empty()),
106                #[cfg(feature = "luau")]
107                name_what: None,
108            }
109        }
110    }
111
112    /// Corresponds to the `S` "what" mask.
113    pub fn source(&self) -> DebugSource<'_> {
114        unsafe {
115            #[cfg(not(feature = "luau"))]
116            mlua_assert!(
117                ffi::lua_getinfo(self.state, cstr!("S"), self.ar) != 0,
118                "lua_getinfo failed with `S`"
119            );
120            #[cfg(feature = "luau")]
121            mlua_assert!(
122                ffi::lua_getinfo(self.state, self.level, cstr!("s"), self.ar) != 0,
123                "lua_getinfo failed with `s`"
124            );
125
126            DebugSource {
127                source: ptr_to_lossy_str((*self.ar).source),
128                #[cfg(not(feature = "luau"))]
129                short_src: ptr_to_lossy_str((*self.ar).short_src.as_ptr()),
130                #[cfg(feature = "luau")]
131                short_src: ptr_to_lossy_str((*self.ar).short_src),
132                line_defined: linenumber_to_usize((*self.ar).linedefined),
133                #[cfg(not(feature = "luau"))]
134                last_line_defined: linenumber_to_usize((*self.ar).lastlinedefined),
135                #[cfg(feature = "luau")]
136                last_line_defined: None,
137                what: ptr_to_str((*self.ar).what).unwrap_or("main"),
138            }
139        }
140    }
141
142    /// Corresponds to the `l` "what" mask. Returns the current line.
143    pub fn current_line(&self) -> Option<usize> {
144        unsafe {
145            #[cfg(not(feature = "luau"))]
146            mlua_assert!(
147                ffi::lua_getinfo(self.state, cstr!("l"), self.ar) != 0,
148                "lua_getinfo failed with `l`"
149            );
150            #[cfg(feature = "luau")]
151            mlua_assert!(
152                ffi::lua_getinfo(self.state, self.level, cstr!("l"), self.ar) != 0,
153                "lua_getinfo failed with `l`"
154            );
155
156            linenumber_to_usize((*self.ar).currentline)
157        }
158    }
159
160    /// Corresponds to the `t` "what" mask. Returns true if the hook is in a function tail call,
161    /// false otherwise.
162    #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
163    #[cfg_attr(
164        docsrs,
165        doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52")))
166    )]
167    pub fn is_tail_call(&self) -> bool {
168        unsafe {
169            mlua_assert!(
170                ffi::lua_getinfo(self.state, cstr!("t"), self.ar) != 0,
171                "lua_getinfo failed with `t`"
172            );
173            (*self.ar).istailcall != 0
174        }
175    }
176
177    /// Corresponds to the `u` "what" mask.
178    pub fn stack(&self) -> DebugStack {
179        unsafe {
180            #[cfg(not(feature = "luau"))]
181            mlua_assert!(
182                ffi::lua_getinfo(self.state, cstr!("u"), self.ar) != 0,
183                "lua_getinfo failed with `u`"
184            );
185            #[cfg(feature = "luau")]
186            mlua_assert!(
187                ffi::lua_getinfo(self.state, self.level, cstr!("au"), self.ar) != 0,
188                "lua_getinfo failed with `au`"
189            );
190
191            #[cfg(not(feature = "luau"))]
192            let stack = DebugStack {
193                num_upvalues: (*self.ar).nups as _,
194                #[cfg(not(any(feature = "lua51", feature = "luajit")))]
195                num_params: (*self.ar).nparams as _,
196                #[cfg(not(any(feature = "lua51", feature = "luajit")))]
197                is_vararg: (*self.ar).isvararg != 0,
198            };
199            #[cfg(feature = "luau")]
200            let stack = DebugStack {
201                num_upvalues: (*self.ar).nupvals,
202                num_params: (*self.ar).nparams,
203                is_vararg: (*self.ar).isvararg != 0,
204            };
205            stack
206        }
207    }
208}
209
210/// Represents a specific event that triggered the hook.
211#[cfg(not(feature = "luau"))]
212#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
213#[derive(Clone, Copy, Debug, PartialEq, Eq)]
214pub enum DebugEvent {
215    Call,
216    Ret,
217    TailCall,
218    Line,
219    Count,
220    Unknown(c_int),
221}
222
223/// Contains the name information of a function in the call stack.
224///
225/// Returned by the [`Debug::names`] method.
226#[derive(Clone, Debug)]
227pub struct DebugNames<'a> {
228    /// A (reasonable) name of the function (`None` if the name cannot be found).
229    pub name: Option<Cow<'a, str>>,
230    /// Explains the `name` field (can be `global`/`local`/`method`/`field`/`upvalue`/etc).
231    ///
232    /// Always `None` for Luau.
233    pub name_what: Option<&'static str>,
234}
235
236/// Contains the source information of a function in the call stack.
237///
238/// Returned by the [`Debug::source`] method.
239#[derive(Clone, Debug)]
240pub struct DebugSource<'a> {
241    /// Source of the chunk that created the function.
242    pub source: Option<Cow<'a, str>>,
243    /// A "printable" version of `source`, to be used in error messages.
244    pub short_src: Option<Cow<'a, str>>,
245    /// The line number where the definition of the function starts.
246    pub line_defined: Option<usize>,
247    /// The line number where the definition of the function ends (not set by Luau).
248    pub last_line_defined: Option<usize>,
249    /// A string `Lua` if the function is a Lua function, `C` if it is a C function, `main` if it is
250    /// the main part of a chunk.
251    pub what: &'static str,
252}
253
254/// Contains stack information about a function in the call stack.
255///
256/// Returned by the [`Debug::stack`] method.
257#[derive(Copy, Clone, Debug)]
258pub struct DebugStack {
259    /// The number of upvalues of the function.
260    pub num_upvalues: u8,
261    /// The number of parameters of the function (always 0 for C).
262    #[cfg(any(not(any(feature = "lua51", feature = "luajit")), doc))]
263    #[cfg_attr(docsrs, doc(cfg(not(any(feature = "lua51", feature = "luajit")))))]
264    pub num_params: u8,
265    /// Whether the function is a variadic function (always true for C).
266    #[cfg(any(not(any(feature = "lua51", feature = "luajit")), doc))]
267    #[cfg_attr(docsrs, doc(cfg(not(any(feature = "lua51", feature = "luajit")))))]
268    pub is_vararg: bool,
269}
270
271/// Determines when a hook function will be called by Lua.
272#[cfg(not(feature = "luau"))]
273#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
274#[derive(Clone, Copy, Debug, Default)]
275pub struct HookTriggers {
276    /// Before a function call.
277    pub on_calls: bool,
278    /// When Lua returns from a function.
279    pub on_returns: bool,
280    /// Before executing a new line, or returning from a function call.
281    pub every_line: bool,
282    /// After a certain number of VM instructions have been executed. When set to `Some(count)`,
283    /// `count` is the number of VM instructions to execute before calling the hook.
284    ///
285    /// # Performance
286    ///
287    /// Setting this option to a low value can incur a very high overhead.
288    pub every_nth_instruction: Option<u32>,
289}
290
291#[cfg(not(feature = "luau"))]
292impl HookTriggers {
293    /// An instance of `HookTriggers` with `on_calls` trigger set.
294    pub const ON_CALLS: Self = HookTriggers::new().on_calls();
295
296    /// An instance of `HookTriggers` with `on_returns` trigger set.
297    pub const ON_RETURNS: Self = HookTriggers::new().on_returns();
298
299    /// An instance of `HookTriggers` with `every_line` trigger set.
300    pub const EVERY_LINE: Self = HookTriggers::new().every_line();
301
302    /// Returns a new instance of `HookTriggers` with all triggers disabled.
303    pub const fn new() -> Self {
304        HookTriggers {
305            on_calls: false,
306            on_returns: false,
307            every_line: false,
308            every_nth_instruction: None,
309        }
310    }
311
312    /// Returns an instance of `HookTriggers` with [`on_calls`] trigger set.
313    ///
314    /// [`on_calls`]: #structfield.on_calls
315    #[must_use]
316    pub const fn on_calls(mut self) -> Self {
317        self.on_calls = true;
318        self
319    }
320
321    /// Returns an instance of `HookTriggers` with [`on_returns`] trigger set.
322    ///
323    /// [`on_returns`]: #structfield.on_returns
324    #[must_use]
325    pub const fn on_returns(mut self) -> Self {
326        self.on_returns = true;
327        self
328    }
329
330    /// Returns an instance of `HookTriggers` with [`every_line`] trigger set.
331    ///
332    /// [`every_line`]: #structfield.every_line
333    #[must_use]
334    pub const fn every_line(mut self) -> Self {
335        self.every_line = true;
336        self
337    }
338
339    /// Returns an instance of `HookTriggers` with [`every_nth_instruction`] trigger set.
340    ///
341    /// [`every_nth_instruction`]: #structfield.every_nth_instruction
342    #[must_use]
343    pub const fn every_nth_instruction(mut self, n: u32) -> Self {
344        self.every_nth_instruction = Some(n);
345        self
346    }
347
348    // Compute the mask to pass to `lua_sethook`.
349    #[cfg(not(feature = "luau"))]
350    pub(crate) const fn mask(&self) -> c_int {
351        let mut mask: c_int = 0;
352        if self.on_calls {
353            mask |= ffi::LUA_MASKCALL
354        }
355        if self.on_returns {
356            mask |= ffi::LUA_MASKRET
357        }
358        if self.every_line {
359            mask |= ffi::LUA_MASKLINE
360        }
361        if self.every_nth_instruction.is_some() {
362            mask |= ffi::LUA_MASKCOUNT
363        }
364        mask
365    }
366
367    // Returns the `count` parameter to pass to `lua_sethook`, if applicable. Otherwise, zero is
368    // returned.
369    #[cfg(not(feature = "luau"))]
370    pub(crate) const fn count(&self) -> c_int {
371        match self.every_nth_instruction {
372            Some(n) => n as c_int,
373            None => 0,
374        }
375    }
376}
377
378#[cfg(not(feature = "luau"))]
379impl std::ops::BitOr for HookTriggers {
380    type Output = Self;
381
382    fn bitor(mut self, rhs: Self) -> Self::Output {
383        self.on_calls |= rhs.on_calls;
384        self.on_returns |= rhs.on_returns;
385        self.every_line |= rhs.every_line;
386        self.every_nth_instruction = self.every_nth_instruction.or(rhs.every_nth_instruction);
387        self
388    }
389}
390
391#[cfg(not(feature = "luau"))]
392impl std::ops::BitOrAssign for HookTriggers {
393    fn bitor_assign(&mut self, rhs: Self) {
394        *self = *self | rhs;
395    }
396}