Files
mlua-rs-mlua/src/hook.rs
T
Alex Orlenko c3822219e0 Add hooks support (based on rlua v0.17 implementation)
This feature works on lua54, lua53, lua52 and lua51 only.
LuaJIT is unstable.
2020-06-06 16:07:16 +01:00

205 lines
6.3 KiB
Rust

#![cfg_attr(
not(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "lua51")),
allow(dead_code)
)]
use std::ffi::CStr;
use std::marker::PhantomData;
use std::os::raw::{c_char, c_int};
use crate::ffi::{self, lua_Debug, lua_State};
use crate::lua::Lua;
use crate::util::callback_error;
/// 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 5.3 documentaton][lua_doc].
///
/// Requires `feature = "lua54/lua53/lua52/lua51"`
///
/// [lua_doc]: https://www.lua.org/manual/5.3/manual.html#lua_Debug
/// [`Lua::set_hook`]: struct.Lua.html#method.set_hook
#[derive(Clone)]
pub struct Debug<'a> {
ar: *mut lua_Debug,
state: *mut lua_State,
_phantom: PhantomData<&'a ()>,
}
impl<'a> Debug<'a> {
/// Corresponds to the `n` what mask.
pub fn names(&self) -> DebugNames<'a> {
unsafe {
mlua_assert!(
ffi::lua_getinfo(self.state, cstr!("n"), self.ar) != 0,
"lua_getinfo failed with `n`"
);
DebugNames {
name: ptr_to_str((*self.ar).name),
name_what: ptr_to_str((*self.ar).namewhat),
}
}
}
/// Corresponds to the `n` what mask.
pub fn source(&self) -> DebugSource<'a> {
unsafe {
mlua_assert!(
ffi::lua_getinfo(self.state, cstr!("S"), self.ar) != 0,
"lua_getinfo failed with `S`"
);
DebugSource {
source: ptr_to_str((*self.ar).source),
short_src: ptr_to_str((*self.ar).short_src.as_ptr()),
line_defined: (*self.ar).linedefined as i32,
last_line_defined: (*self.ar).lastlinedefined as i32,
what: ptr_to_str((*self.ar).what),
}
}
}
/// Corresponds to the `l` what mask. Returns the current line.
pub fn curr_line(&self) -> i32 {
unsafe {
mlua_assert!(
ffi::lua_getinfo(self.state, cstr!("l"), self.ar) != 0,
"lua_getinfo failed with `l`"
);
(*self.ar).currentline as i32
}
}
/// Corresponds to the `t` what mask. Returns true if the hook is in a function tail call, false
/// otherwise.
pub fn is_tail_call(&self) -> bool {
unsafe {
mlua_assert!(
ffi::lua_getinfo(self.state, cstr!("t"), self.ar) != 0,
"lua_getinfo failed with `t`"
);
(*self.ar).currentline != 0
}
}
/// Corresponds to the `u` what mask.
pub fn stack(&self) -> DebugStack {
unsafe {
mlua_assert!(
ffi::lua_getinfo(self.state, cstr!("u"), self.ar) != 0,
"lua_getinfo failed with `u`"
);
DebugStack {
num_ups: (*self.ar).nups as i32,
#[cfg(any(feature = "lua52", feature = "lua53", feature = "lua54"))]
num_params: (*self.ar).nparams as i32,
#[cfg(any(feature = "lua52", feature = "lua53", feature = "lua54"))]
is_vararg: (*self.ar).isvararg != 0,
}
}
}
}
#[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,
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"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", doc))]
pub num_params: i32,
/// Requires `feature = "lua54/lua53/lua52"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", doc))]
pub is_vararg: bool,
}
/// Determines when a hook function will be called by Lua.
///
/// Requires `feature = "lua54/lua53/lua52/lua51"`
#[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<u32>,
}
impl HookTriggers {
// 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
}
}
pub(crate) unsafe extern "C" fn hook_proc(state: *mut lua_State, ar: *mut lua_Debug) {
callback_error(state, |_| {
let debug = Debug {
ar,
state,
_phantom: PhantomData,
};
let lua = Lua::make_from_ptr(state);
let hook_cb = mlua_expect!(lua.hook_callback(), "no hook callback set in hook_proc");
#[allow(clippy::match_wild_err_arm)]
match hook_cb.try_borrow_mut() {
Ok(mut b) => (&mut *b)(&lua, debug),
Err(_) => mlua_panic!("Lua should not allow hooks to be called within another hook"),
}?;
Ok(())
});
}
unsafe fn ptr_to_str<'a>(input: *const c_char) -> Option<&'a [u8]> {
if input.is_null() {
None
} else {
Some(CStr::from_ptr(input).to_bytes())
}
}