Add hooks support (based on rlua v0.17 implementation)
This feature works on lua54, lua53, lua52 and lua51 only. LuaJIT is unstable.
This commit is contained in:
parent
f6da437d8b
commit
c3822219e0
|
@ -739,7 +739,7 @@ pub const LUA_MASKLINE: c_int = 1 << (LUA_HOOKLINE as usize);
|
||||||
pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
|
pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
|
||||||
|
|
||||||
/// Type for functions to be called on debug events.
|
/// Type for functions to be called on debug events.
|
||||||
pub type lua_Hook = extern "C" fn(L: *mut lua_State, ar: *mut lua_Debug);
|
pub type lua_Hook = unsafe extern "C" fn(L: *mut lua_State, ar: *mut lua_Debug);
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn lua_getstack(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int;
|
pub fn lua_getstack(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int;
|
||||||
|
@ -754,7 +754,7 @@ extern "C" {
|
||||||
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
|
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
|
||||||
pub fn lua_upvaluejoin(L: *mut lua_State, fidx1: c_int, n1: c_int, fidx2: c_int, n2: c_int);
|
pub fn lua_upvaluejoin(L: *mut lua_State, fidx1: c_int, n1: c_int, fidx2: c_int, n2: c_int);
|
||||||
|
|
||||||
pub fn lua_sethook(L: *mut lua_State, func: lua_Hook, mask: c_int, count: c_int);
|
pub fn lua_sethook(L: *mut lua_State, func: Option<lua_Hook>, mask: c_int, count: c_int);
|
||||||
pub fn lua_gethook(L: *mut lua_State) -> Option<lua_Hook>;
|
pub fn lua_gethook(L: *mut lua_State) -> Option<lua_Hook>;
|
||||||
pub fn lua_gethookmask(L: *mut lua_State) -> c_int;
|
pub fn lua_gethookmask(L: *mut lua_State) -> c_int;
|
||||||
pub fn lua_gethookcount(L: *mut lua_State) -> c_int;
|
pub fn lua_gethookcount(L: *mut lua_State) -> c_int;
|
||||||
|
|
|
@ -0,0 +1,204 @@
|
||||||
|
#![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())
|
||||||
|
}
|
||||||
|
}
|
10
src/lib.rs
10
src/lib.rs
|
@ -63,6 +63,7 @@ mod conversion;
|
||||||
mod error;
|
mod error;
|
||||||
mod ffi;
|
mod ffi;
|
||||||
mod function;
|
mod function;
|
||||||
|
mod hook;
|
||||||
mod lua;
|
mod lua;
|
||||||
mod multi;
|
mod multi;
|
||||||
mod scope;
|
mod scope;
|
||||||
|
@ -90,6 +91,15 @@ pub use crate::types::{Integer, LightUserData, Number, RegistryKey};
|
||||||
pub use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods};
|
pub use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods};
|
||||||
pub use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti, Value};
|
pub use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti, Value};
|
||||||
|
|
||||||
|
#[cfg(any(
|
||||||
|
feature = "lua54",
|
||||||
|
feature = "lua53",
|
||||||
|
feature = "lua52",
|
||||||
|
feature = "lua51",
|
||||||
|
doc
|
||||||
|
))]
|
||||||
|
pub use crate::hook::{Debug, DebugNames, DebugSource, DebugStack, HookTriggers};
|
||||||
|
|
||||||
#[cfg(feature = "async")]
|
#[cfg(feature = "async")]
|
||||||
pub use crate::thread::AsyncThread;
|
pub use crate::thread::AsyncThread;
|
||||||
|
|
||||||
|
|
154
src/lua.rs
154
src/lua.rs
|
@ -4,7 +4,7 @@ use std::collections::HashMap;
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::os::raw::{c_char, c_int, c_void};
|
use std::os::raw::{c_char, c_int, c_void};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex, Weak};
|
||||||
use std::{mem, ptr, str};
|
use std::{mem, ptr, str};
|
||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
|
@ -15,7 +15,9 @@ use crate::stdlib::StdLib;
|
||||||
use crate::string::String;
|
use crate::string::String;
|
||||||
use crate::table::Table;
|
use crate::table::Table;
|
||||||
use crate::thread::Thread;
|
use crate::thread::Thread;
|
||||||
use crate::types::{Callback, Integer, LightUserData, LuaRef, MaybeSend, Number, RegistryKey};
|
use crate::types::{
|
||||||
|
Callback, HookCallback, Integer, LightUserData, LuaRef, MaybeSend, Number, RegistryKey,
|
||||||
|
};
|
||||||
use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods};
|
use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods};
|
||||||
use crate::util::{
|
use crate::util::{
|
||||||
assert_stack, callback_error, check_stack, get_gc_userdata, get_main_state,
|
assert_stack, callback_error, check_stack, get_gc_userdata, get_main_state,
|
||||||
|
@ -25,6 +27,15 @@ use crate::util::{
|
||||||
};
|
};
|
||||||
use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti, Value};
|
use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti, Value};
|
||||||
|
|
||||||
|
#[cfg(any(
|
||||||
|
feature = "lua54",
|
||||||
|
feature = "lua53",
|
||||||
|
feature = "lua52",
|
||||||
|
feature = "lua51",
|
||||||
|
doc
|
||||||
|
))]
|
||||||
|
use crate::hook::{hook_proc, Debug, HookTriggers};
|
||||||
|
|
||||||
#[cfg(feature = "async")]
|
#[cfg(feature = "async")]
|
||||||
use {
|
use {
|
||||||
crate::types::AsyncCallback,
|
crate::types::AsyncCallback,
|
||||||
|
@ -58,6 +69,8 @@ struct ExtraData {
|
||||||
ref_stack_size: c_int,
|
ref_stack_size: c_int,
|
||||||
ref_stack_max: c_int,
|
ref_stack_max: c_int,
|
||||||
ref_free: Vec<c_int>,
|
ref_free: Vec<c_int>,
|
||||||
|
|
||||||
|
hook_callback: Option<HookCallback>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(any(feature = "lua51", feature = "luajit"), allow(dead_code))]
|
#[cfg_attr(any(feature = "lua51", feature = "luajit"), allow(dead_code))]
|
||||||
|
@ -85,6 +98,7 @@ pub enum GCMode {
|
||||||
pub(crate) struct AsyncPollPending;
|
pub(crate) struct AsyncPollPending;
|
||||||
#[cfg(feature = "async")]
|
#[cfg(feature = "async")]
|
||||||
pub(crate) static WAKER_REGISTRY_KEY: u8 = 0;
|
pub(crate) static WAKER_REGISTRY_KEY: u8 = 0;
|
||||||
|
pub(crate) static EXTRA_REGISTRY_KEY: u8 = 0;
|
||||||
|
|
||||||
/// Requires `feature = "send"`
|
/// Requires `feature = "send"`
|
||||||
#[cfg(feature = "send")]
|
#[cfg(feature = "send")]
|
||||||
|
@ -276,6 +290,7 @@ impl Lua {
|
||||||
|
|
||||||
init_gc_metatable_for::<Callback>(state, None);
|
init_gc_metatable_for::<Callback>(state, None);
|
||||||
init_gc_metatable_for::<Lua>(state, None);
|
init_gc_metatable_for::<Lua>(state, None);
|
||||||
|
init_gc_metatable_for::<Weak<Mutex<ExtraData>>>(state, None);
|
||||||
#[cfg(feature = "async")]
|
#[cfg(feature = "async")]
|
||||||
{
|
{
|
||||||
init_gc_metatable_for::<AsyncCallback>(state, None);
|
init_gc_metatable_for::<AsyncCallback>(state, None);
|
||||||
|
@ -305,8 +320,24 @@ impl Lua {
|
||||||
ref_stack_size: ffi::LUA_MINSTACK - 1,
|
ref_stack_size: ffi::LUA_MINSTACK - 1,
|
||||||
ref_stack_max: 0,
|
ref_stack_max: 0,
|
||||||
ref_free: Vec::new(),
|
ref_free: Vec::new(),
|
||||||
|
hook_callback: None,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
mlua_expect!(
|
||||||
|
push_gc_userdata(state, Arc::downgrade(&extra)),
|
||||||
|
"Error while storing extra data",
|
||||||
|
);
|
||||||
|
mlua_expect!(
|
||||||
|
protect_lua_closure(main_state, 1, 0, |state| {
|
||||||
|
ffi::lua_rawsetp(
|
||||||
|
state,
|
||||||
|
ffi::LUA_REGISTRYINDEX,
|
||||||
|
&EXTRA_REGISTRY_KEY as *const u8 as *mut c_void,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
"Error while storing extra data"
|
||||||
|
);
|
||||||
|
|
||||||
mlua_debug_assert!(
|
mlua_debug_assert!(
|
||||||
ffi::lua_gettop(main_state) == main_state_top,
|
ffi::lua_gettop(main_state) == main_state_top,
|
||||||
"stack leak during creation"
|
"stack leak during creation"
|
||||||
|
@ -387,6 +418,90 @@ impl Lua {
|
||||||
unsafe { self.push_value(cb.call(())?).map(|_| 1) }
|
unsafe { self.push_value(cb.call(())?).map(|_| 1) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets a 'hook' function that will periodically be called as Lua code executes.
|
||||||
|
///
|
||||||
|
/// When exactly the hook function is called depends on the contents of the `triggers`
|
||||||
|
/// parameter, see [`HookTriggers`] for more details.
|
||||||
|
///
|
||||||
|
/// The provided hook function can error, and this error will be propagated through the Lua code
|
||||||
|
/// that was executing at the time the hook was triggered. This can be used to implement a
|
||||||
|
/// limited form of execution limits by setting [`HookTriggers.every_nth_instruction`] and
|
||||||
|
/// erroring once an instruction limit has been reached.
|
||||||
|
///
|
||||||
|
/// Requires `feature = "lua54/lua53/lua52/lua51"`
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// Shows each line number of code being executed by the Lua interpreter.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "lua51"))]
|
||||||
|
/// # use mlua::{Lua, HookTriggers, Result};
|
||||||
|
/// # #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "lua51"))]
|
||||||
|
/// # fn main() -> Result<()> {
|
||||||
|
/// let lua = Lua::new();
|
||||||
|
/// lua.set_hook(HookTriggers {
|
||||||
|
/// every_line: true, ..Default::default()
|
||||||
|
/// }, |_lua, debug| {
|
||||||
|
/// println!("line {}", debug.curr_line());
|
||||||
|
/// Ok(())
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// lua.load(r#"
|
||||||
|
/// local x = 2 + 3
|
||||||
|
/// local y = x * 63
|
||||||
|
/// local z = string.len(x..", "..y)
|
||||||
|
/// "#).exec()
|
||||||
|
/// # }
|
||||||
|
///
|
||||||
|
/// # #[cfg(not(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "lua51")))]
|
||||||
|
/// # fn main() {}
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`HookTriggers`]: struct.HookTriggers.html
|
||||||
|
/// [`HookTriggers.every_nth_instruction`]: struct.HookTriggers.html#field.every_nth_instruction
|
||||||
|
#[cfg(any(
|
||||||
|
feature = "lua54",
|
||||||
|
feature = "lua53",
|
||||||
|
feature = "lua52",
|
||||||
|
feature = "lua51",
|
||||||
|
doc
|
||||||
|
))]
|
||||||
|
pub fn set_hook<F>(&self, triggers: HookTriggers, callback: F)
|
||||||
|
where
|
||||||
|
F: 'static + MaybeSend + FnMut(&Lua, Debug) -> Result<()>,
|
||||||
|
{
|
||||||
|
unsafe {
|
||||||
|
let mut extra = mlua_expect!(self.extra.lock(), "extra is poisoned");
|
||||||
|
extra.hook_callback = Some(Arc::new(RefCell::new(callback)));
|
||||||
|
ffi::lua_sethook(
|
||||||
|
self.main_state,
|
||||||
|
Some(hook_proc),
|
||||||
|
triggers.mask(),
|
||||||
|
triggers.count(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove any hook previously set by `set_hook`. This function has no effect if a hook was not
|
||||||
|
/// previously set.
|
||||||
|
///
|
||||||
|
/// Requires `feature = "lua54/lua53/lua52/lua51"`
|
||||||
|
#[cfg(any(
|
||||||
|
feature = "lua54",
|
||||||
|
feature = "lua53",
|
||||||
|
feature = "lua52",
|
||||||
|
feature = "lua51",
|
||||||
|
doc
|
||||||
|
))]
|
||||||
|
pub fn remove_hook(&self) {
|
||||||
|
let mut extra = mlua_expect!(self.extra.lock(), "extra is poisoned");
|
||||||
|
unsafe {
|
||||||
|
extra.hook_callback = None;
|
||||||
|
ffi::lua_sethook(self.main_state, None, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the amount of memory (in bytes) currently used inside this Lua state.
|
/// Returns the amount of memory (in bytes) currently used inside this Lua state.
|
||||||
pub fn used_memory(&self) -> usize {
|
pub fn used_memory(&self) -> usize {
|
||||||
let extra = mlua_expect!(self.extra.lock(), "extra is poisoned");
|
let extra = mlua_expect!(self.extra.lock(), "extra is poisoned");
|
||||||
|
@ -1544,10 +1659,7 @@ impl Lua {
|
||||||
let mut waker = noop_waker();
|
let mut waker = noop_waker();
|
||||||
|
|
||||||
// Try to get an outer poll waker
|
// Try to get an outer poll waker
|
||||||
ffi::lua_pushlightuserdata(
|
ffi::lua_pushlightuserdata(state, &WAKER_REGISTRY_KEY as *const u8 as *mut c_void);
|
||||||
state,
|
|
||||||
&WAKER_REGISTRY_KEY as *const u8 as *mut c_void,
|
|
||||||
);
|
|
||||||
ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX);
|
ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX);
|
||||||
if let Some(w) = get_gc_userdata::<Waker>(state, -1).as_ref() {
|
if let Some(w) = get_gc_userdata::<Waker>(state, -1).as_ref() {
|
||||||
waker = (*w).clone();
|
waker = (*w).clone();
|
||||||
|
@ -1675,6 +1787,36 @@ impl Lua {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) unsafe fn make_from_ptr(state: *mut ffi::lua_State) -> Self {
|
||||||
|
let _sg = StackGuard::new(state);
|
||||||
|
assert_stack(state, 3);
|
||||||
|
|
||||||
|
ffi::lua_rawgetp(
|
||||||
|
state,
|
||||||
|
ffi::LUA_REGISTRYINDEX,
|
||||||
|
&EXTRA_REGISTRY_KEY as *const u8 as *mut c_void,
|
||||||
|
);
|
||||||
|
let extra = mlua_expect!(
|
||||||
|
(*get_gc_userdata::<Weak<Mutex<ExtraData>>>(state, -1)).upgrade(),
|
||||||
|
"extra is destroyed"
|
||||||
|
);
|
||||||
|
ffi::lua_pop(state, 1);
|
||||||
|
|
||||||
|
Lua {
|
||||||
|
state,
|
||||||
|
main_state: get_main_state(state),
|
||||||
|
extra,
|
||||||
|
ephemeral: true,
|
||||||
|
safe: true, // TODO: Inherit the attribute
|
||||||
|
_no_ref_unwind_safe: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) unsafe fn hook_callback(&self) -> Option<HookCallback> {
|
||||||
|
let extra = mlua_expect!(self.extra.lock(), "extra is poisoned");
|
||||||
|
extra.hook_callback.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returned from [`Lua::load`] and is used to finalize loading and executing Lua main chunks.
|
/// Returned from [`Lua::load`] and is used to finalize loading and executing Lua main chunks.
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::os::raw::{c_int, c_void};
|
use std::os::raw::{c_int, c_void};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::{fmt, mem, ptr};
|
use std::{fmt, mem, ptr};
|
||||||
|
@ -7,6 +8,7 @@ use futures_core::future::LocalBoxFuture;
|
||||||
|
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::ffi;
|
use crate::ffi;
|
||||||
|
use crate::hook::Debug;
|
||||||
use crate::lua::Lua;
|
use crate::lua::Lua;
|
||||||
use crate::util::{assert_stack, StackGuard};
|
use crate::util::{assert_stack, StackGuard};
|
||||||
use crate::value::MultiValue;
|
use crate::value::MultiValue;
|
||||||
|
@ -27,6 +29,8 @@ pub(crate) type Callback<'lua, 'a> =
|
||||||
pub(crate) type AsyncCallback<'lua, 'a> =
|
pub(crate) type AsyncCallback<'lua, 'a> =
|
||||||
Box<dyn Fn(&'lua Lua, MultiValue<'lua>) -> LocalBoxFuture<'lua, Result<MultiValue<'lua>>> + 'a>;
|
Box<dyn Fn(&'lua Lua, MultiValue<'lua>) -> LocalBoxFuture<'lua, Result<MultiValue<'lua>>> + 'a>;
|
||||||
|
|
||||||
|
pub(crate) type HookCallback = Arc<RefCell<dyn FnMut(&Lua, Debug) -> Result<()>>>;
|
||||||
|
|
||||||
#[cfg(feature = "send")]
|
#[cfg(feature = "send")]
|
||||||
pub trait MaybeSend: Send {}
|
pub trait MaybeSend: Send {}
|
||||||
#[cfg(feature = "send")]
|
#[cfg(feature = "send")]
|
||||||
|
|
|
@ -0,0 +1,230 @@
|
||||||
|
#![cfg(any(
|
||||||
|
feature = "lua54",
|
||||||
|
feature = "lua53",
|
||||||
|
feature = "lua52",
|
||||||
|
feature = "lua51"
|
||||||
|
))]
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::str;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use mlua::{Error, HookTriggers, Lua, Result, Value};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn line_counts() -> Result<()> {
|
||||||
|
let output = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
let hook_output = output.clone();
|
||||||
|
|
||||||
|
let lua = Lua::new();
|
||||||
|
lua.set_hook(
|
||||||
|
HookTriggers {
|
||||||
|
every_line: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
move |_lua, debug| {
|
||||||
|
hook_output.lock().unwrap().push(debug.curr_line());
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
);
|
||||||
|
lua.load(
|
||||||
|
r#"
|
||||||
|
local x = 2 + 3
|
||||||
|
local y = x * 63
|
||||||
|
local z = string.len(x..", "..y)
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.exec()?;
|
||||||
|
|
||||||
|
let output = output.lock().unwrap();
|
||||||
|
assert_eq!(*output, vec![2, 3, 4]);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn function_calls() -> Result<()> {
|
||||||
|
let output = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
let hook_output = output.clone();
|
||||||
|
|
||||||
|
let lua = Lua::new();
|
||||||
|
lua.set_hook(
|
||||||
|
HookTriggers {
|
||||||
|
on_calls: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
move |_lua, debug| {
|
||||||
|
let names = debug.names();
|
||||||
|
let source = debug.source();
|
||||||
|
let name = names.name.map(|s| str::from_utf8(s).unwrap().to_owned());
|
||||||
|
let what = source.what.map(|s| str::from_utf8(s).unwrap().to_owned());
|
||||||
|
hook_output.lock().unwrap().push((name, what));
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
lua.load(
|
||||||
|
r#"
|
||||||
|
local v = string.len("Hello World")
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.exec()?;
|
||||||
|
|
||||||
|
let output = output.lock().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
*output,
|
||||||
|
vec![
|
||||||
|
(None, Some("main".to_string())),
|
||||||
|
(Some("len".to_string()), Some("C".to_string()))
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_within_hook() {
|
||||||
|
let lua = Lua::new();
|
||||||
|
lua.set_hook(
|
||||||
|
HookTriggers {
|
||||||
|
every_line: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
|_lua, _debug| {
|
||||||
|
Err(Error::RuntimeError(
|
||||||
|
"Something happened in there!".to_string(),
|
||||||
|
))
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let err = lua
|
||||||
|
.load("x = 1")
|
||||||
|
.exec()
|
||||||
|
.expect_err("panic didn't propagate");
|
||||||
|
|
||||||
|
match err {
|
||||||
|
Error::CallbackError { cause, .. } => match cause.deref() {
|
||||||
|
Error::RuntimeError(s) => assert_eq!(s, "Something happened in there!"),
|
||||||
|
_ => panic!("wrong callback error kind caught"),
|
||||||
|
},
|
||||||
|
_ => panic!("wrong error kind caught"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn limit_execution_instructions() {
|
||||||
|
let lua = Lua::new();
|
||||||
|
let mut max_instructions = 10000;
|
||||||
|
|
||||||
|
lua.set_hook(
|
||||||
|
HookTriggers {
|
||||||
|
every_nth_instruction: Some(30),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
move |_lua, _debug| {
|
||||||
|
max_instructions -= 30;
|
||||||
|
if max_instructions < 0 {
|
||||||
|
Err(Error::RuntimeError("time's up".to_string()))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
lua.globals().set("x", Value::Integer(0)).unwrap();
|
||||||
|
let _ = lua
|
||||||
|
.load(
|
||||||
|
r#"
|
||||||
|
for i = 1, 10000 do
|
||||||
|
x = x + 1
|
||||||
|
end
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.exec()
|
||||||
|
.expect_err("instruction limit didn't occur");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hook_removal() {
|
||||||
|
let lua = Lua::new();
|
||||||
|
|
||||||
|
lua.set_hook(
|
||||||
|
HookTriggers {
|
||||||
|
every_nth_instruction: Some(1),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
|_lua, _debug| {
|
||||||
|
Err(Error::RuntimeError(
|
||||||
|
"this hook should've been removed by this time".to_string(),
|
||||||
|
))
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(lua.load("local x = 1").exec().is_err());
|
||||||
|
lua.remove_hook();
|
||||||
|
assert!(lua.load("local x = 1").exec().is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hook_swap_within_hook() {
|
||||||
|
thread_local! {
|
||||||
|
static TL_LUA: RefCell<Option<Lua>> = RefCell::new(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
TL_LUA.with(|tl| {
|
||||||
|
*tl.borrow_mut() = Some(Lua::new());
|
||||||
|
});
|
||||||
|
|
||||||
|
TL_LUA.with(|tl| {
|
||||||
|
tl.borrow().as_ref().unwrap().set_hook(
|
||||||
|
HookTriggers {
|
||||||
|
every_line: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
move |lua, _debug| {
|
||||||
|
lua.globals().set("ok", 1i64).unwrap();
|
||||||
|
TL_LUA.with(|tl| {
|
||||||
|
tl.borrow().as_ref().unwrap().set_hook(
|
||||||
|
HookTriggers {
|
||||||
|
every_line: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
move |lua, _debug| {
|
||||||
|
lua.load(
|
||||||
|
r#"
|
||||||
|
if ok ~= nil then
|
||||||
|
ok = ok + 1
|
||||||
|
end
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.exec()
|
||||||
|
.expect("exec failure within hook");
|
||||||
|
TL_LUA.with(|tl| {
|
||||||
|
tl.borrow().as_ref().unwrap().remove_hook();
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
TL_LUA.with(|tl| {
|
||||||
|
let tl = tl.borrow();
|
||||||
|
let lua = tl.as_ref().unwrap();
|
||||||
|
assert!(lua
|
||||||
|
.load(
|
||||||
|
r#"
|
||||||
|
local x = 1
|
||||||
|
x = 2
|
||||||
|
local y = 3
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.exec()
|
||||||
|
.is_ok());
|
||||||
|
assert_eq!(lua.globals().get::<_, i64>("ok").unwrap_or(-1), 2);
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in New Issue