luaur_rt/debug.rs
1//! Stack inspection. Mirrors the Luau-feasible subset of `mlua::Lua::inspect_stack`
2//! and `mlua::debug::Debug`.
3//!
4//! ## What Luau can back
5//!
6//! Luau's debug model is **not** the Lua 5.x line/count hook. It exposes
7//! `lua_getinfo(L, level, what, ar)` for activation records and `lua_singlestep`
8//! + the interrupt callback for stepping. We surface the *informational* part —
9//! resolving a stack level into a [`Debug`] record (current line, source, name,
10//! what kind of function) — which maps cleanly onto `lua_getinfo`.
11//!
12//! ## What is deferred (and why)
13//!
14//! The full `mlua::Lua::set_hook(HookTriggers, ...)` API (per-line / per-N-
15//! instruction / on-call / on-return hooks with a `Debug` event) is a Lua 5.x
16//! construct. Luau has no equivalent multiplexed hook: it has a *single* global
17//! interrupt callback (see [`Lua::set_interrupt`](crate::Lua::set_interrupt))
18//! and `lua_singlestep`. mlua itself gates `tests/hooks.rs` and `tests/debug.rs`
19//! behind `#![cfg(not(feature = "luau"))]` for exactly this reason. We therefore
20//! do **not** fake a 5.x hook surface; the interrupt API is the Luau-native
21//! analog and is implemented separately.
22
23use std::ffi::CStr;
24
25use crate::state::Lua;
26use crate::sys::*;
27
28/// What kind of function an activation record refers to. Mirrors the relevant
29/// part of `mlua::debug::DebugSource::what`.
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum DebugWhat {
32 /// A Lua function.
33 Lua,
34 /// The main chunk.
35 Main,
36 /// A C / Rust (native) function.
37 C,
38 /// Unknown / unavailable.
39 Unknown,
40}
41
42/// A snapshot of one activation record, resolved from a stack level via
43/// `lua_getinfo`. Mirrors the informational subset of `mlua::debug::Debug`.
44#[derive(Debug, Clone)]
45pub struct Debug {
46 name: Option<String>,
47 what: DebugWhat,
48 source: Option<String>,
49 short_src: Option<String>,
50 current_line: Option<i64>,
51 line_defined: Option<i64>,
52}
53
54impl Debug {
55 /// The function's name, if known (`(n)`).
56 pub fn name(&self) -> Option<&str> {
57 self.name.as_deref()
58 }
59
60 /// What kind of function this record refers to (`(s)`).
61 pub fn what(&self) -> DebugWhat {
62 self.what
63 }
64
65 /// The chunk source (`(s)`).
66 pub fn source(&self) -> Option<&str> {
67 self.source.as_deref()
68 }
69
70 /// A short, human-readable source description (`(s)`).
71 pub fn short_src(&self) -> Option<&str> {
72 self.short_src.as_deref()
73 }
74
75 /// The currently executing line (`(l)`), if available.
76 pub fn current_line(&self) -> Option<i64> {
77 self.current_line
78 }
79
80 /// The line where the function was defined (`(s)`).
81 pub fn line_defined(&self) -> Option<i64> {
82 self.line_defined
83 }
84}
85
86impl Lua {
87 /// Inspect the activation record `level` frames up the call stack (0 = the
88 /// currently running function). Returns `None` if there is no function at
89 /// that level. Mirrors the Luau-feasible part of `mlua::Lua::inspect_stack`.
90 pub fn inspect_stack(&self, level: usize) -> Option<Debug> {
91 let state = self.state();
92 unsafe {
93 let mut ar: LuaDebug = core::mem::zeroed();
94 // `lua_getinfo` with a non-negative level walks call-info depth.
95 let opt = c"nsl";
96 let ok = lua_getinfo(
97 state,
98 level as c_int,
99 opt.as_ptr() as *const c_char,
100 &mut ar,
101 );
102 if ok == 0 {
103 return None;
104 }
105 let cstr = |p: *const c_char| -> Option<String> {
106 if p.is_null() {
107 None
108 } else {
109 Some(CStr::from_ptr(p).to_string_lossy().into_owned())
110 }
111 };
112 let what_str = cstr(ar.what).unwrap_or_default();
113 let what = match what_str.as_str() {
114 "Lua" => DebugWhat::Lua,
115 "main" => DebugWhat::Main,
116 "C" => DebugWhat::C,
117 _ => DebugWhat::Unknown,
118 };
119 let current_line = if ar.currentline >= 0 {
120 Some(ar.currentline as i64)
121 } else {
122 None
123 };
124 let line_defined = if ar.linedefined > 0 {
125 Some(ar.linedefined as i64)
126 } else {
127 None
128 };
129 Some(Debug {
130 name: cstr(ar.name),
131 what,
132 source: cstr(ar.source),
133 short_src: cstr(ar.short_src),
134 current_line,
135 line_defined,
136 })
137 }
138 }
139}