Add `Lua::inspect_stack` to get information about the interpreter runtime stack.
This functionality is provided by `lua_getstack`.
This commit is contained in:
parent
153502ec73
commit
8af1304fd0
81
src/hook.rs
81
src/hook.rs
|
@ -1,3 +1,4 @@
|
|||
use std::cell::UnsafeCell;
|
||||
use std::ffi::CStr;
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::{BitOr, BitOrAssign};
|
||||
|
@ -16,14 +17,29 @@ use crate::util::callback_error;
|
|||
///
|
||||
/// [lua_doc]: https://www.lua.org/manual/5.3/manual.html#lua_Debug
|
||||
/// [`Lua::set_hook`]: crate::Lua::set_hook
|
||||
#[derive(Clone)]
|
||||
pub struct Debug<'a> {
|
||||
ar: *mut lua_Debug,
|
||||
ar: ActivationRecord,
|
||||
state: *mut lua_State,
|
||||
_phantom: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<'a> Debug<'a> {
|
||||
pub(crate) fn new(state: *mut lua_State, ar: *mut lua_Debug) -> Self {
|
||||
Debug {
|
||||
ar: ActivationRecord::Borrowed(ar),
|
||||
state,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_owned(state: *mut lua_State, ar: lua_Debug) -> Self {
|
||||
Debug {
|
||||
ar: ActivationRecord::Owned(UnsafeCell::new(ar)),
|
||||
state,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the specific event that triggered the hook.
|
||||
///
|
||||
/// For [Lua 5.1] `DebugEvent::TailCall` is used for return events to indicate a return
|
||||
|
@ -32,13 +48,13 @@ impl<'a> Debug<'a> {
|
|||
/// [Lua 5.1]: https://www.lua.org/manual/5.1/manual.html#pdf-LUA_HOOKTAILRET
|
||||
pub fn event(&self) -> DebugEvent {
|
||||
unsafe {
|
||||
match (*self.ar).event {
|
||||
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 => mlua_panic!("Unknown Lua event code: {}", event),
|
||||
code => DebugEvent::Unknown(code),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -47,12 +63,12 @@ impl<'a> Debug<'a> {
|
|||
pub fn names(&self) -> DebugNames<'a> {
|
||||
unsafe {
|
||||
mlua_assert!(
|
||||
ffi::lua_getinfo(self.state, cstr!("n"), self.ar) != 0,
|
||||
ffi::lua_getinfo(self.state, cstr!("n"), self.ar.get()) != 0,
|
||||
"lua_getinfo failed with `n`"
|
||||
);
|
||||
DebugNames {
|
||||
name: ptr_to_str((*self.ar).name),
|
||||
name_what: ptr_to_str((*self.ar).namewhat),
|
||||
name: ptr_to_str((*self.ar.get()).name),
|
||||
name_what: ptr_to_str((*self.ar.get()).namewhat),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -61,15 +77,15 @@ impl<'a> Debug<'a> {
|
|||
pub fn source(&self) -> DebugSource<'a> {
|
||||
unsafe {
|
||||
mlua_assert!(
|
||||
ffi::lua_getinfo(self.state, cstr!("S"), self.ar) != 0,
|
||||
ffi::lua_getinfo(self.state, cstr!("S"), self.ar.get()) != 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),
|
||||
source: ptr_to_str((*self.ar.get()).source),
|
||||
short_src: ptr_to_str((*self.ar.get()).short_src.as_ptr()),
|
||||
line_defined: (*self.ar.get()).linedefined as i32,
|
||||
last_line_defined: (*self.ar.get()).lastlinedefined as i32,
|
||||
what: ptr_to_str((*self.ar.get()).what),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -78,10 +94,10 @@ impl<'a> Debug<'a> {
|
|||
pub fn curr_line(&self) -> i32 {
|
||||
unsafe {
|
||||
mlua_assert!(
|
||||
ffi::lua_getinfo(self.state, cstr!("l"), self.ar) != 0,
|
||||
ffi::lua_getinfo(self.state, cstr!("l"), self.ar.get()) != 0,
|
||||
"lua_getinfo failed with `l`"
|
||||
);
|
||||
(*self.ar).currentline as i32
|
||||
(*self.ar.get()).currentline as i32
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,10 +106,10 @@ impl<'a> Debug<'a> {
|
|||
pub fn is_tail_call(&self) -> bool {
|
||||
unsafe {
|
||||
mlua_assert!(
|
||||
ffi::lua_getinfo(self.state, cstr!("t"), self.ar) != 0,
|
||||
ffi::lua_getinfo(self.state, cstr!("t"), self.ar.get()) != 0,
|
||||
"lua_getinfo failed with `t`"
|
||||
);
|
||||
(*self.ar).currentline != 0
|
||||
(*self.ar.get()).currentline != 0
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,20 +117,35 @@ impl<'a> Debug<'a> {
|
|||
pub fn stack(&self) -> DebugStack {
|
||||
unsafe {
|
||||
mlua_assert!(
|
||||
ffi::lua_getinfo(self.state, cstr!("u"), self.ar) != 0,
|
||||
ffi::lua_getinfo(self.state, cstr!("u"), self.ar.get()) != 0,
|
||||
"lua_getinfo failed with `u`"
|
||||
);
|
||||
DebugStack {
|
||||
num_ups: (*self.ar).nups as i32,
|
||||
num_ups: (*self.ar.get()).nups as i32,
|
||||
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
|
||||
num_params: (*self.ar).nparams as i32,
|
||||
num_params: (*self.ar.get()).nparams as i32,
|
||||
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
|
||||
is_vararg: (*self.ar).isvararg != 0,
|
||||
is_vararg: (*self.ar.get()).isvararg != 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ActivationRecord {
|
||||
Borrowed(*mut lua_Debug),
|
||||
Owned(UnsafeCell<lua_Debug>),
|
||||
}
|
||||
|
||||
impl ActivationRecord {
|
||||
#[inline]
|
||||
fn get(&self) -> *mut lua_Debug {
|
||||
match self {
|
||||
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 {
|
||||
|
@ -123,6 +154,7 @@ pub enum DebugEvent {
|
|||
TailCall,
|
||||
Line,
|
||||
Count,
|
||||
Unknown(c_int),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -257,12 +289,7 @@ impl BitOrAssign for HookTriggers {
|
|||
|
||||
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 debug = Debug::new(state, ar);
|
||||
let lua = mlua_expect!(Lua::make_from_ptr(state), "cannot make Lua instance");
|
||||
let hook_cb = mlua_expect!(lua.hook_callback(), "no hook callback set in hook_proc");
|
||||
|
||||
|
|
17
src/lua.rs
17
src/lua.rs
|
@ -741,6 +741,23 @@ impl Lua {
|
|||
}
|
||||
}
|
||||
|
||||
/// Gets information about the interpreter runtime stack.
|
||||
///
|
||||
/// This function returns [`Debug`] structure that can be used to get information about the function
|
||||
/// executing at a given level. Level `0` is the current running function, whereas level `n+1` is the
|
||||
/// function that has called level `n` (except for tail calls, which do not count in the stack).
|
||||
///
|
||||
/// [`Debug`]: crate::hook::Debug
|
||||
pub fn inspect_stack(&self, level: usize) -> Option<Debug> {
|
||||
unsafe {
|
||||
let mut ar: ffi::lua_Debug = mem::zeroed();
|
||||
if ffi::lua_getstack(self.state, level as c_int, &mut ar) == 0 {
|
||||
return None;
|
||||
}
|
||||
Some(Debug::new_owned(self.state, ar))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the amount of memory (in bytes) currently used inside this Lua state.
|
||||
pub fn used_memory(&self) -> usize {
|
||||
unsafe {
|
||||
|
|
|
@ -1150,3 +1150,40 @@ fn test_load_from_function() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inspect_stack() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
// Not inside any function
|
||||
assert!(lua.inspect_stack(0).is_none());
|
||||
|
||||
let logline = lua.create_function(|lua, msg: StdString| {
|
||||
let debug = lua.inspect_stack(1).unwrap(); // caller
|
||||
let source = debug.source().short_src.map(core::str::from_utf8);
|
||||
let source = source.transpose().unwrap().unwrap_or("?");
|
||||
let line = debug.curr_line();
|
||||
Ok(format!("{}:{} {}", source, line, msg))
|
||||
})?;
|
||||
lua.globals().set("logline", logline)?;
|
||||
|
||||
lua.load(
|
||||
r#"
|
||||
local function foo()
|
||||
local line = logline("hello")
|
||||
return line
|
||||
end
|
||||
local function bar()
|
||||
return foo()
|
||||
end
|
||||
|
||||
assert(foo() == '[string "chunk"]:3 hello')
|
||||
assert(bar() == '[string "chunk"]:3 hello')
|
||||
assert(logline("world") == '[string "chunk"]:12 world')
|
||||
"#,
|
||||
)
|
||||
.set_name("chunk")?
|
||||
.exec()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue