mlua/
hook.rs

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