From 8af1304fd07e8fae73817fba9f36096602c178cf Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 9 Nov 2021 13:41:10 +0000 Subject: [PATCH] Add `Lua::inspect_stack` to get information about the interpreter runtime stack. This functionality is provided by `lua_getstack`. --- src/hook.rs | 81 +++++++++++++++++++++++++++++++++----------------- src/lua.rs | 17 +++++++++++ tests/tests.rs | 37 +++++++++++++++++++++++ 3 files changed, 108 insertions(+), 27 deletions(-) diff --git a/src/hook.rs b/src/hook.rs index ed1fcf3..4d7113a 100644 --- a/src/hook.rs +++ b/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), +} + +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"); diff --git a/src/lua.rs b/src/lua.rs index b1567a9..4c42341 100644 --- a/src/lua.rs +++ b/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 { + 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 { diff --git a/tests/tests.rs b/tests/tests.rs index 4d76adb..ee72b81 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -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(()) +}