1use std::borrow::Cow;
2use std::cell::UnsafeCell;
3use std::ops::Deref;
4#[cfg(not(feature = "luau"))]
5use std::ops::{BitOr, BitOrAssign};
6use std::os::raw::c_int;
7
8use ffi::lua_Debug;
9
10use crate::state::RawLua;
11use crate::types::ReentrantMutexGuard;
12use crate::util::{linenumber_to_usize, ptr_to_lossy_str, ptr_to_str};
13
14pub struct Debug<'a> {
24 lua: EitherLua<'a>,
25 ar: ActivationRecord,
26 #[cfg(feature = "luau")]
27 level: c_int,
28}
29
30enum EitherLua<'a> {
31 Owned(ReentrantMutexGuard<'a, RawLua>),
32 #[cfg(not(feature = "luau"))]
33 Borrowed(&'a RawLua),
34}
35
36impl Deref for EitherLua<'_> {
37 type Target = RawLua;
38
39 fn deref(&self) -> &Self::Target {
40 match self {
41 EitherLua::Owned(guard) => guard,
42 #[cfg(not(feature = "luau"))]
43 EitherLua::Borrowed(lua) => lua,
44 }
45 }
46}
47
48impl<'a> Debug<'a> {
49 #[cfg(not(feature = "luau"))]
51 pub(crate) fn new(lua: &'a RawLua, ar: *mut lua_Debug) -> Self {
52 Debug {
53 lua: EitherLua::Borrowed(lua),
54 ar: ActivationRecord::Borrowed(ar),
55 }
56 }
57
58 pub(crate) fn new_owned(guard: ReentrantMutexGuard<'a, RawLua>, _level: c_int, ar: lua_Debug) -> Self {
59 Debug {
60 lua: EitherLua::Owned(guard),
61 ar: ActivationRecord::Owned(UnsafeCell::new(ar)),
62 #[cfg(feature = "luau")]
63 level: _level,
64 }
65 }
66
67 #[cfg(not(feature = "luau"))]
74 #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
75 pub fn event(&self) -> DebugEvent {
76 unsafe {
77 match (*self.ar.get()).event {
78 ffi::LUA_HOOKCALL => DebugEvent::Call,
79 ffi::LUA_HOOKRET => DebugEvent::Ret,
80 ffi::LUA_HOOKTAILCALL => DebugEvent::TailCall,
81 ffi::LUA_HOOKLINE => DebugEvent::Line,
82 ffi::LUA_HOOKCOUNT => DebugEvent::Count,
83 event => DebugEvent::Unknown(event),
84 }
85 }
86 }
87
88 pub fn names(&self) -> DebugNames {
90 unsafe {
91 #[cfg(not(feature = "luau"))]
92 mlua_assert!(
93 ffi::lua_getinfo(self.lua.state(), cstr!("n"), self.ar.get()) != 0,
94 "lua_getinfo failed with `n`"
95 );
96 #[cfg(feature = "luau")]
97 mlua_assert!(
98 ffi::lua_getinfo(self.lua.state(), self.level, cstr!("n"), self.ar.get()) != 0,
99 "lua_getinfo failed with `n`"
100 );
101
102 DebugNames {
103 name: ptr_to_lossy_str((*self.ar.get()).name),
104 #[cfg(not(feature = "luau"))]
105 name_what: match ptr_to_str((*self.ar.get()).namewhat) {
106 Some("") => None,
107 val => val,
108 },
109 #[cfg(feature = "luau")]
110 name_what: None,
111 }
112 }
113 }
114
115 pub fn source(&self) -> DebugSource {
117 unsafe {
118 #[cfg(not(feature = "luau"))]
119 mlua_assert!(
120 ffi::lua_getinfo(self.lua.state(), cstr!("S"), self.ar.get()) != 0,
121 "lua_getinfo failed with `S`"
122 );
123 #[cfg(feature = "luau")]
124 mlua_assert!(
125 ffi::lua_getinfo(self.lua.state(), self.level, cstr!("s"), self.ar.get()) != 0,
126 "lua_getinfo failed with `s`"
127 );
128
129 DebugSource {
130 source: ptr_to_lossy_str((*self.ar.get()).source),
131 #[cfg(not(feature = "luau"))]
132 short_src: ptr_to_lossy_str((*self.ar.get()).short_src.as_ptr()),
133 #[cfg(feature = "luau")]
134 short_src: ptr_to_lossy_str((*self.ar.get()).short_src),
135 line_defined: linenumber_to_usize((*self.ar.get()).linedefined),
136 #[cfg(not(feature = "luau"))]
137 last_line_defined: linenumber_to_usize((*self.ar.get()).lastlinedefined),
138 #[cfg(feature = "luau")]
139 last_line_defined: None,
140 what: ptr_to_str((*self.ar.get()).what).unwrap_or("main"),
141 }
142 }
143 }
144
145 pub fn curr_line(&self) -> i32 {
147 unsafe {
148 #[cfg(not(feature = "luau"))]
149 mlua_assert!(
150 ffi::lua_getinfo(self.lua.state(), cstr!("l"), self.ar.get()) != 0,
151 "lua_getinfo failed with `l`"
152 );
153 #[cfg(feature = "luau")]
154 mlua_assert!(
155 ffi::lua_getinfo(self.lua.state(), self.level, cstr!("l"), self.ar.get()) != 0,
156 "lua_getinfo failed with `l`"
157 );
158
159 (*self.ar.get()).currentline
160 }
161 }
162
163 #[cfg(not(feature = "luau"))]
166 #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
167 pub fn is_tail_call(&self) -> bool {
168 unsafe {
169 mlua_assert!(
170 ffi::lua_getinfo(self.lua.state(), cstr!("t"), self.ar.get()) != 0,
171 "lua_getinfo failed with `t`"
172 );
173 (*self.ar.get()).currentline != 0
174 }
175 }
176
177 pub fn stack(&self) -> DebugStack {
179 unsafe {
180 #[cfg(not(feature = "luau"))]
181 mlua_assert!(
182 ffi::lua_getinfo(self.lua.state(), cstr!("u"), self.ar.get()) != 0,
183 "lua_getinfo failed with `u`"
184 );
185 #[cfg(feature = "luau")]
186 mlua_assert!(
187 ffi::lua_getinfo(self.lua.state(), self.level, cstr!("a"), self.ar.get()) != 0,
188 "lua_getinfo failed with `a`"
189 );
190
191 #[cfg(not(feature = "luau"))]
192 let stack = DebugStack {
193 num_ups: (*self.ar.get()).nups as _,
194 #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
195 num_params: (*self.ar.get()).nparams as _,
196 #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
197 is_vararg: (*self.ar.get()).isvararg != 0,
198 };
199 #[cfg(feature = "luau")]
200 let stack = DebugStack {
201 num_ups: (*self.ar.get()).nupvals as i32,
202 num_params: (*self.ar.get()).nparams as i32,
203 is_vararg: (*self.ar.get()).isvararg != 0,
204 };
205 stack
206 }
207 }
208}
209
210enum ActivationRecord {
211 #[cfg(not(feature = "luau"))]
212 Borrowed(*mut lua_Debug),
213 Owned(UnsafeCell<lua_Debug>),
214}
215
216impl ActivationRecord {
217 #[inline]
218 fn get(&self) -> *mut lua_Debug {
219 match self {
220 #[cfg(not(feature = "luau"))]
221 ActivationRecord::Borrowed(x) => *x,
222 ActivationRecord::Owned(x) => x.get(),
223 }
224 }
225}
226
227#[derive(Clone, Copy, Debug, PartialEq, Eq)]
229pub enum DebugEvent {
230 Call,
231 Ret,
232 TailCall,
233 Line,
234 Count,
235 Unknown(c_int),
236}
237
238#[derive(Clone, Debug)]
239pub struct DebugNames<'a> {
240 pub name: Option<Cow<'a, str>>,
242 pub name_what: Option<&'static str>,
246}
247
248#[derive(Clone, Debug)]
249pub struct DebugSource<'a> {
250 pub source: Option<Cow<'a, str>>,
252 pub short_src: Option<Cow<'a, str>>,
254 pub line_defined: Option<usize>,
256 pub last_line_defined: Option<usize>,
258 pub what: &'static str,
261}
262
263#[derive(Copy, Clone, Debug)]
264pub struct DebugStack {
265 pub num_ups: i32,
266 #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
268 pub num_params: i32,
269 #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
271 pub is_vararg: bool,
272}
273
274#[cfg(not(feature = "luau"))]
276#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
277#[derive(Clone, Copy, Debug, Default)]
278pub struct HookTriggers {
279 pub on_calls: bool,
281 pub on_returns: bool,
283 pub every_line: bool,
285 pub every_nth_instruction: Option<u32>,
292}
293
294#[cfg(not(feature = "luau"))]
295impl HookTriggers {
296 pub const ON_CALLS: Self = HookTriggers::new().on_calls();
298
299 pub const ON_RETURNS: Self = HookTriggers::new().on_returns();
301
302 pub const EVERY_LINE: Self = HookTriggers::new().every_line();
304
305 pub const fn new() -> Self {
307 HookTriggers {
308 on_calls: false,
309 on_returns: false,
310 every_line: false,
311 every_nth_instruction: None,
312 }
313 }
314
315 pub const fn on_calls(mut self) -> Self {
319 self.on_calls = true;
320 self
321 }
322
323 pub const fn on_returns(mut self) -> Self {
327 self.on_returns = true;
328 self
329 }
330
331 pub const fn every_line(mut self) -> Self {
335 self.every_line = true;
336 self
337 }
338
339 pub const fn every_nth_instruction(mut self, n: u32) -> Self {
343 self.every_nth_instruction = Some(n);
344 self
345 }
346
347 pub(crate) const fn mask(&self) -> c_int {
349 let mut mask: c_int = 0;
350 if self.on_calls {
351 mask |= ffi::LUA_MASKCALL
352 }
353 if self.on_returns {
354 mask |= ffi::LUA_MASKRET
355 }
356 if self.every_line {
357 mask |= ffi::LUA_MASKLINE
358 }
359 if self.every_nth_instruction.is_some() {
360 mask |= ffi::LUA_MASKCOUNT
361 }
362 mask
363 }
364
365 pub(crate) const fn count(&self) -> c_int {
368 match self.every_nth_instruction {
369 Some(n) => n as c_int,
370 None => 0,
371 }
372 }
373}
374
375#[cfg(not(feature = "luau"))]
376impl BitOr for HookTriggers {
377 type Output = Self;
378
379 fn bitor(mut self, rhs: Self) -> Self::Output {
380 self.on_calls |= rhs.on_calls;
381 self.on_returns |= rhs.on_returns;
382 self.every_line |= rhs.every_line;
383 if self.every_nth_instruction.is_none() && rhs.every_nth_instruction.is_some() {
384 self.every_nth_instruction = rhs.every_nth_instruction;
385 }
386 self
387 }
388}
389
390#[cfg(not(feature = "luau"))]
391impl BitOrAssign for HookTriggers {
392 fn bitor_assign(&mut self, rhs: Self) {
393 *self = *self | rhs;
394 }
395}