factorio_mlua/
hook.rs

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