Add `Lua::inspect_stack` to get information about the interpreter runtime stack.

This functionality is provided by `lua_getstack`.
This commit is contained in:
Alex Orlenko 2021-11-09 13:41:10 +00:00
parent 153502ec73
commit 8af1304fd0
No known key found for this signature in database
GPG Key ID: 4C150C250863B96D
3 changed files with 108 additions and 27 deletions

View File

@ -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");

View File

@ -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 {

View File

@ -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(())
}