use std::cell::UnsafeCell; #[cfg(not(feature = "luau"))] use std::ops::{BitOr, BitOrAssign}; use std::os::raw::c_int; use crate::ffi::{self, lua_Debug}; use crate::lua::Lua; use crate::util::ptr_to_cstr_bytes; /// Contains information about currently executing Lua code. /// /// The `Debug` structure is provided as a parameter to the hook function set with /// [`Lua::set_hook`]. You may call the methods on this structure to retrieve information about the /// Lua code executing at the time that the hook function was called. Further information can be /// found in the Lua [documentation][lua_doc]. /// /// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#lua_Debug /// [`Lua::set_hook`]: crate::Lua::set_hook pub struct Debug<'lua> { lua: &'lua Lua, ar: ActivationRecord, #[cfg(feature = "luau")] level: c_int, } impl<'lua> Debug<'lua> { #[cfg(not(feature = "luau"))] pub(crate) fn new(lua: &'lua Lua, ar: *mut lua_Debug) -> Self { Debug { lua, ar: ActivationRecord::Borrowed(ar), } } pub(crate) fn new_owned(lua: &'lua Lua, _level: c_int, ar: lua_Debug) -> Self { Debug { lua, ar: ActivationRecord::Owned(UnsafeCell::new(ar)), #[cfg(feature = "luau")] level: _level, } } /// Returns the specific event that triggered the hook. /// /// For [Lua 5.1] `DebugEvent::TailCall` is used for return events to indicate a return /// from a function that did a tail call. /// /// [Lua 5.1]: https://www.lua.org/manual/5.1/manual.html#pdf-LUA_HOOKTAILRET #[cfg(not(feature = "luau"))] #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] pub fn event(&self) -> DebugEvent { unsafe { match (*self.ar.get()).event { ffi::LUA_HOOKCALL => DebugEvent::Call, ffi::LUA_HOOKRET => DebugEvent::Ret, ffi::LUA_HOOKTAILCALL => DebugEvent::TailCall, ffi::LUA_HOOKLINE => DebugEvent::Line, ffi::LUA_HOOKCOUNT => DebugEvent::Count, event => DebugEvent::Unknown(event), } } } /// Corresponds to the `n` what mask. pub fn names(&self) -> DebugNames { unsafe { #[cfg(not(feature = "luau"))] mlua_assert!( ffi::lua_getinfo(self.lua.state(), cstr!("n"), self.ar.get()) != 0, "lua_getinfo failed with `n`" ); #[cfg(feature = "luau")] mlua_assert!( ffi::lua_getinfo(self.lua.state(), self.level, cstr!("n"), self.ar.get()) != 0, "lua_getinfo failed with `n`" ); DebugNames { name: ptr_to_cstr_bytes((*self.ar.get()).name), #[cfg(not(feature = "luau"))] name_what: ptr_to_cstr_bytes((*self.ar.get()).namewhat), #[cfg(feature = "luau")] name_what: None, } } } /// Corresponds to the `S` what mask. pub fn source(&self) -> DebugSource { unsafe { #[cfg(not(feature = "luau"))] mlua_assert!( ffi::lua_getinfo(self.lua.state(), cstr!("S"), self.ar.get()) != 0, "lua_getinfo failed with `S`" ); #[cfg(feature = "luau")] mlua_assert!( ffi::lua_getinfo(self.lua.state(), self.level, cstr!("s"), self.ar.get()) != 0, "lua_getinfo failed with `s`" ); DebugSource { source: ptr_to_cstr_bytes((*self.ar.get()).source), #[cfg(not(feature = "luau"))] short_src: ptr_to_cstr_bytes((*self.ar.get()).short_src.as_ptr()), #[cfg(feature = "luau")] short_src: ptr_to_cstr_bytes((*self.ar.get()).short_src), line_defined: (*self.ar.get()).linedefined, #[cfg(not(feature = "luau"))] last_line_defined: (*self.ar.get()).lastlinedefined, what: ptr_to_cstr_bytes((*self.ar.get()).what), } } } /// Corresponds to the `l` what mask. Returns the current line. pub fn curr_line(&self) -> i32 { unsafe { #[cfg(not(feature = "luau"))] mlua_assert!( ffi::lua_getinfo(self.lua.state(), cstr!("l"), self.ar.get()) != 0, "lua_getinfo failed with `l`" ); #[cfg(feature = "luau")] mlua_assert!( ffi::lua_getinfo(self.lua.state(), self.level, cstr!("l"), self.ar.get()) != 0, "lua_getinfo failed with `l`" ); (*self.ar.get()).currentline } } /// Corresponds to the `t` what mask. Returns true if the hook is in a function tail call, false /// otherwise. #[cfg(not(feature = "luau"))] #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] pub fn is_tail_call(&self) -> bool { unsafe { mlua_assert!( ffi::lua_getinfo(self.lua.state(), cstr!("t"), self.ar.get()) != 0, "lua_getinfo failed with `t`" ); (*self.ar.get()).currentline != 0 } } /// Corresponds to the `u` what mask. pub fn stack(&self) -> DebugStack { unsafe { #[cfg(not(feature = "luau"))] mlua_assert!( ffi::lua_getinfo(self.lua.state(), cstr!("u"), self.ar.get()) != 0, "lua_getinfo failed with `u`" ); #[cfg(feature = "luau")] mlua_assert!( ffi::lua_getinfo(self.lua.state(), self.level, cstr!("a"), self.ar.get()) != 0, "lua_getinfo failed with `a`" ); #[cfg(not(feature = "luau"))] let stack = DebugStack { num_ups: (*self.ar.get()).nups as i32, #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] num_params: (*self.ar.get()).nparams as i32, #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] is_vararg: (*self.ar.get()).isvararg != 0, }; #[cfg(feature = "luau")] let stack = DebugStack { num_ups: (*self.ar.get()).nupvals as i32, num_params: (*self.ar.get()).nparams as i32, is_vararg: (*self.ar.get()).isvararg != 0, }; stack } } } enum ActivationRecord { #[cfg(not(feature = "luau"))] Borrowed(*mut lua_Debug), Owned(UnsafeCell), } impl ActivationRecord { #[inline] fn get(&self) -> *mut lua_Debug { match self { #[cfg(not(feature = "luau"))] ActivationRecord::Borrowed(x) => *x, ActivationRecord::Owned(x) => x.get(), } } } /// Represents a specific event that triggered the hook. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum DebugEvent { Call, Ret, TailCall, Line, Count, Unknown(c_int), } #[derive(Clone, Debug)] pub struct DebugNames<'a> { pub name: Option<&'a [u8]>, pub name_what: Option<&'a [u8]>, } #[derive(Clone, Debug)] pub struct DebugSource<'a> { pub source: Option<&'a [u8]>, pub short_src: Option<&'a [u8]>, pub line_defined: i32, #[cfg(not(feature = "luau"))] pub last_line_defined: i32, pub what: Option<&'a [u8]>, } #[derive(Copy, Clone, Debug)] pub struct DebugStack { pub num_ups: i32, /// Requires `feature = "lua54/lua53/lua52/luau"` #[cfg(any( feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau" ))] pub num_params: i32, /// Requires `feature = "lua54/lua53/lua52/luau"` #[cfg(any( feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau" ))] pub is_vararg: bool, } /// Determines when a hook function will be called by Lua. #[cfg(not(feature = "luau"))] #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] #[derive(Clone, Copy, Debug, Default)] pub struct HookTriggers { /// Before a function call. pub on_calls: bool, /// When Lua returns from a function. pub on_returns: bool, /// Before executing a new line, or returning from a function call. pub every_line: bool, /// After a certain number of VM instructions have been executed. When set to `Some(count)`, /// `count` is the number of VM instructions to execute before calling the hook. /// /// # Performance /// /// Setting this option to a low value can incur a very high overhead. pub every_nth_instruction: Option, } #[cfg(not(feature = "luau"))] impl HookTriggers { /// Returns a new instance of `HookTriggers` with [`on_calls`] trigger set. /// /// [`on_calls`]: #structfield.on_calls pub fn on_calls() -> Self { HookTriggers { on_calls: true, ..Default::default() } } /// Returns a new instance of `HookTriggers` with [`on_returns`] trigger set. /// /// [`on_returns`]: #structfield.on_returns pub fn on_returns() -> Self { HookTriggers { on_returns: true, ..Default::default() } } /// Returns a new instance of `HookTriggers` with [`every_line`] trigger set. /// /// [`every_line`]: #structfield.every_line pub fn every_line() -> Self { HookTriggers { every_line: true, ..Default::default() } } /// Returns a new instance of `HookTriggers` with [`every_nth_instruction`] trigger set. /// /// [`every_nth_instruction`]: #structfield.every_nth_instruction pub fn every_nth_instruction(n: u32) -> Self { HookTriggers { every_nth_instruction: Some(n), ..Default::default() } } // Compute the mask to pass to `lua_sethook`. pub(crate) fn mask(&self) -> c_int { let mut mask: c_int = 0; if self.on_calls { mask |= ffi::LUA_MASKCALL } if self.on_returns { mask |= ffi::LUA_MASKRET } if self.every_line { mask |= ffi::LUA_MASKLINE } if self.every_nth_instruction.is_some() { mask |= ffi::LUA_MASKCOUNT } mask } // Returns the `count` parameter to pass to `lua_sethook`, if applicable. Otherwise, zero is // returned. pub(crate) fn count(&self) -> c_int { self.every_nth_instruction.unwrap_or(0) as c_int } } #[cfg(not(feature = "luau"))] impl BitOr for HookTriggers { type Output = Self; fn bitor(mut self, rhs: Self) -> Self::Output { self.on_calls |= rhs.on_calls; self.on_returns |= rhs.on_returns; self.every_line |= rhs.every_line; if self.every_nth_instruction.is_none() && rhs.every_nth_instruction.is_some() { self.every_nth_instruction = rhs.every_nth_instruction; } self } } #[cfg(not(feature = "luau"))] impl BitOrAssign for HookTriggers { fn bitor_assign(&mut self, rhs: Self) { *self = *self | rhs; } }