Make Debug interface more user friendly

- use String instead of Vec<u8>
- update docs
- unify fields between lua5.x/luau
- line numbers are `usize`
This commit is contained in:
Alex Orlenko 2023-07-06 00:38:33 +01:00
parent 85f17a269d
commit b3b8d79446
No known key found for this signature in database
GPG Key ID: 4C150C250863B96D
6 changed files with 98 additions and 76 deletions

View File

@ -10,7 +10,8 @@ use crate::memory::MemoryState;
use crate::table::Table; use crate::table::Table;
use crate::types::{Callback, LuaRef, MaybeSend}; use crate::types::{Callback, LuaRef, MaybeSend};
use crate::util::{ use crate::util::{
assert_stack, check_stack, error_traceback, pop_error, ptr_to_cstr_bytes, StackGuard, assert_stack, check_stack, error_traceback, linenumber_to_usize, pop_error, ptr_to_lossy_str,
ptr_to_str, StackGuard,
}; };
use crate::value::{FromLuaMulti, IntoLua, IntoLuaMulti}; use crate::value::{FromLuaMulti, IntoLua, IntoLuaMulti};
@ -52,24 +53,22 @@ impl OwnedFunction {
/// [`Lua Debug Interface`]: https://www.lua.org/manual/5.4/manual.html#4.7 /// [`Lua Debug Interface`]: https://www.lua.org/manual/5.4/manual.html#4.7
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct FunctionInfo { pub struct FunctionInfo {
/// A (reasonable) name of the function. /// A (reasonable) name of the function (`None` if the name cannot be found).
pub name: Option<String>, pub name: Option<String>,
/// Explains the `name` field ("global", "local", "method", "field", "upvalue", or ""). /// Explains the `name` field (can be `global`/`local`/`method`/`field`/`upvalue`/etc).
/// ///
/// Always `None` for Luau. /// Always `None` for Luau.
pub name_what: Option<String>, pub name_what: Option<&'static str>,
/// A string "Lua" if the function is a Lua function, "C" if it is a C function, "main" if it is the main part of a chunk. /// A string `Lua` if the function is a Lua function, `C` if it is a C function, `main` if it is the main part of a chunk.
pub what: Option<String>, pub what: &'static str,
/// The source of the chunk that created the function. /// Source of the chunk that created the function.
pub source: Option<Vec<u8>>, pub source: Option<String>,
/// A "printable" version of source, to be used in error messages. /// A "printable" version of `source`, to be used in error messages.
pub short_src: Option<Vec<u8>>, pub short_src: Option<String>,
/// The line number where the definition of the function starts. /// The line number where the definition of the function starts.
pub line_defined: i32, pub line_defined: Option<usize>,
/// The line number where the definition of the function ends. /// The line number where the definition of the function ends (not set by Luau).
/// pub last_line_defined: Option<usize>,
/// Always `-1` for Luau.
pub last_line_defined: i32,
} }
/// Luau function coverage snapshot. /// Luau function coverage snapshot.
@ -391,23 +390,25 @@ impl<'lua> Function<'lua> {
mlua_assert!(res != 0, "lua_getinfo failed with `>Sn`"); mlua_assert!(res != 0, "lua_getinfo failed with `>Sn`");
FunctionInfo { FunctionInfo {
name: ptr_to_cstr_bytes(ar.name).map(|s| String::from_utf8_lossy(s).into_owned()), name: ptr_to_lossy_str(ar.name).map(|s| s.into_owned()),
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
name_what: ptr_to_cstr_bytes(ar.namewhat) name_what: match ptr_to_str(ar.namewhat) {
.map(|s| String::from_utf8_lossy(s).into_owned()), Some("") => None,
val => val,
},
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
name_what: None, name_what: None,
what: ptr_to_cstr_bytes(ar.what).map(|s| String::from_utf8_lossy(s).into_owned()), what: ptr_to_str(ar.what).unwrap_or("main"),
source: ptr_to_cstr_bytes(ar.source).map(|s| s.to_vec()), source: ptr_to_lossy_str(ar.source).map(|s| s.into_owned()),
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
short_src: ptr_to_cstr_bytes(ar.short_src.as_ptr()).map(|s| s.to_vec()), short_src: ptr_to_lossy_str(ar.short_src.as_ptr()).map(|s| s.into_owned()),
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
short_src: ptr_to_cstr_bytes(ar.short_src).map(|s| s.to_vec()), short_src: ptr_to_lossy_str(ar.short_src).map(|s| s.into_owned()),
line_defined: ar.linedefined, line_defined: linenumber_to_usize(ar.linedefined),
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
last_line_defined: ar.lastlinedefined, last_line_defined: linenumber_to_usize(ar.lastlinedefined),
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
last_line_defined: -1, last_line_defined: None,
} }
} }
} }

View File

@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::cell::UnsafeCell; use std::cell::UnsafeCell;
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
use std::ops::{BitOr, BitOrAssign}; use std::ops::{BitOr, BitOrAssign};
@ -6,7 +7,7 @@ use std::os::raw::c_int;
use ffi::lua_Debug; use ffi::lua_Debug;
use crate::lua::Lua; use crate::lua::Lua;
use crate::util::ptr_to_cstr_bytes; use crate::util::{linenumber_to_usize, ptr_to_lossy_str, ptr_to_str};
/// Contains information about currently executing Lua code. /// Contains information about currently executing Lua code.
/// ///
@ -78,9 +79,12 @@ impl<'lua> Debug<'lua> {
); );
DebugNames { DebugNames {
name: ptr_to_cstr_bytes((*self.ar.get()).name), name: ptr_to_lossy_str((*self.ar.get()).name),
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
name_what: ptr_to_cstr_bytes((*self.ar.get()).namewhat), name_what: match ptr_to_str((*self.ar.get()).namewhat) {
Some("") => None,
val => val,
},
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
name_what: None, name_what: None,
} }
@ -102,15 +106,17 @@ impl<'lua> Debug<'lua> {
); );
DebugSource { DebugSource {
source: ptr_to_cstr_bytes((*self.ar.get()).source), source: ptr_to_lossy_str((*self.ar.get()).source),
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
short_src: ptr_to_cstr_bytes((*self.ar.get()).short_src.as_ptr()), short_src: ptr_to_lossy_str((*self.ar.get()).short_src.as_ptr()),
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
short_src: ptr_to_cstr_bytes((*self.ar.get()).short_src), short_src: ptr_to_lossy_str((*self.ar.get()).short_src),
line_defined: (*self.ar.get()).linedefined, line_defined: linenumber_to_usize((*self.ar.get()).linedefined),
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
last_line_defined: (*self.ar.get()).lastlinedefined, last_line_defined: linenumber_to_usize((*self.ar.get()).lastlinedefined),
what: ptr_to_cstr_bytes((*self.ar.get()).what), #[cfg(feature = "luau")]
last_line_defined: None,
what: ptr_to_str((*self.ar.get()).what).unwrap_or("main"),
} }
} }
} }
@ -210,18 +216,26 @@ pub enum DebugEvent {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct DebugNames<'a> { pub struct DebugNames<'a> {
pub name: Option<&'a [u8]>, /// A (reasonable) name of the function (`None` if the name cannot be found).
pub name_what: Option<&'a [u8]>, pub name: Option<Cow<'a, str>>,
/// Explains the `name` field (can be `global`/`local`/`method`/`field`/`upvalue`/etc).
///
/// Always `None` for Luau.
pub name_what: Option<&'static str>,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct DebugSource<'a> { pub struct DebugSource<'a> {
pub source: Option<&'a [u8]>, /// Source of the chunk that created the function.
pub short_src: Option<&'a [u8]>, pub source: Option<Cow<'a, str>>,
pub line_defined: i32, /// A "printable" version of `source`, to be used in error messages.
#[cfg(not(feature = "luau"))] pub short_src: Option<Cow<'a, str>>,
pub last_line_defined: i32, /// The line number where the definition of the function starts.
pub what: Option<&'a [u8]>, pub line_defined: Option<usize>,
/// The line number where the definition of the function ends (not set by Luau).
pub last_line_defined: Option<usize>,
/// A string `Lua` if the function is a Lua function, `C` if it is a C function, `main` if it is the main part of a chunk.
pub what: &'static str,
} }
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]

View File

@ -1,11 +1,12 @@
use std::any::{Any, TypeId}; use std::any::{Any, TypeId};
use std::borrow::Cow;
use std::ffi::CStr; use std::ffi::CStr;
use std::fmt::Write; use std::fmt::Write;
use std::mem::MaybeUninit; use std::mem::MaybeUninit;
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_int, c_void};
use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe}; use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
use std::sync::Arc; use std::sync::Arc;
use std::{mem, ptr, slice}; use std::{mem, ptr, slice, str};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
@ -1066,11 +1067,25 @@ pub(crate) unsafe fn get_destructed_userdata_metatable(state: *mut ffi::lua_Stat
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, key); ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, key);
} }
pub(crate) unsafe fn ptr_to_cstr_bytes<'a>(input: *const c_char) -> Option<&'a [u8]> { pub(crate) unsafe fn ptr_to_str<'a>(input: *const c_char) -> Option<&'a str> {
if input.is_null() { if input.is_null() {
return None; return None;
} }
Some(CStr::from_ptr(input).to_bytes()) str::from_utf8(CStr::from_ptr(input).to_bytes()).ok()
}
pub(crate) unsafe fn ptr_to_lossy_str<'a>(input: *const c_char) -> Option<Cow<'a, str>> {
if input.is_null() {
return None;
}
Some(String::from_utf8_lossy(CStr::from_ptr(input).to_bytes()))
}
pub(crate) fn linenumber_to_usize(n: c_int) -> Option<usize> {
match n {
n if n < 0 => None,
n => Some(n as usize),
}
} }
static DESTRUCTED_USERDATA_METATABLE: u8 = 0; static DESTRUCTED_USERDATA_METATABLE: u8 = 0;

View File

@ -196,37 +196,37 @@ fn test_function_info() -> Result<()> {
let function1_info = function1.info(); let function1_info = function1.info();
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
assert_eq!(function1_info.name.as_deref(), Some("function1")); assert_eq!(function1_info.name.as_deref(), Some("function1"));
assert_eq!(function1_info.source.as_deref(), Some(b"source1".as_ref())); assert_eq!(function1_info.source.as_deref(), Some("source1"));
assert_eq!(function1_info.line_defined, 2); assert_eq!(function1_info.line_defined, Some(2));
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
assert_eq!(function1_info.last_line_defined, 4); assert_eq!(function1_info.last_line_defined, Some(4));
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
assert_eq!(function1_info.last_line_defined, -1); assert_eq!(function1_info.last_line_defined, None);
assert_eq!(function1_info.what.as_deref(), Some("Lua")); assert_eq!(function1_info.what, "Lua");
let function2_info = function2.info(); let function2_info = function2.info();
assert_eq!(function2_info.name, None); assert_eq!(function2_info.name, None);
assert_eq!(function2_info.source.as_deref(), Some(b"source1".as_ref())); assert_eq!(function2_info.source.as_deref(), Some("source1"));
assert_eq!(function2_info.line_defined, 3); assert_eq!(function2_info.line_defined, Some(3));
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
assert_eq!(function2_info.last_line_defined, 3); assert_eq!(function2_info.last_line_defined, Some(3));
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
assert_eq!(function2_info.last_line_defined, -1); assert_eq!(function2_info.last_line_defined, None);
assert_eq!(function2_info.what.as_deref(), Some("Lua")); assert_eq!(function2_info.what, "Lua");
let function3_info = function3.info(); let function3_info = function3.info();
assert_eq!(function3_info.name, None); assert_eq!(function3_info.name, None);
assert_eq!(function3_info.source.as_deref(), Some(b"=[C]".as_ref())); assert_eq!(function3_info.source.as_deref(), Some("=[C]"));
assert_eq!(function3_info.line_defined, -1); assert_eq!(function3_info.line_defined, None);
assert_eq!(function3_info.last_line_defined, -1); assert_eq!(function3_info.last_line_defined, None);
assert_eq!(function3_info.what.as_deref(), Some("C")); assert_eq!(function3_info.what, "C");
let print_info = globals.get::<_, Function>("print")?.info(); let print_info = globals.get::<_, Function>("print")?.info();
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
assert_eq!(print_info.name.as_deref(), Some("print")); assert_eq!(print_info.name.as_deref(), Some("print"));
assert_eq!(print_info.source.as_deref(), Some(b"=[C]".as_ref())); assert_eq!(print_info.source.as_deref(), Some("=[C]"));
assert_eq!(print_info.what.as_deref(), Some("C")); assert_eq!(print_info.what, "C");
assert_eq!(print_info.line_defined, -1); assert_eq!(print_info.line_defined, None);
Ok(()) Ok(())
} }

View File

@ -2,7 +2,6 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::ops::Deref; use std::ops::Deref;
use std::str;
use std::sync::atomic::{AtomicI64, Ordering}; use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -61,9 +60,8 @@ fn test_function_calls() -> Result<()> {
assert_eq!(debug.event(), DebugEvent::Call); assert_eq!(debug.event(), DebugEvent::Call);
let names = debug.names(); let names = debug.names();
let source = debug.source(); let source = debug.source();
let name = names.name.map(|s| str::from_utf8(s).unwrap().to_owned()); let name = names.name.map(|s| s.into_owned());
let what = source.what.map(|s| str::from_utf8(s).unwrap().to_owned()); hook_output.lock().unwrap().push((name, source.what));
hook_output.lock().unwrap().push((name, what));
Ok(()) Ok(())
})?; })?;
@ -80,18 +78,12 @@ fn test_function_calls() -> Result<()> {
if cfg!(feature = "luajit") && lua.load("jit.version_num").eval::<i64>()? >= 20100 { if cfg!(feature = "luajit") && lua.load("jit.version_num").eval::<i64>()? >= 20100 {
assert_eq!( assert_eq!(
*output, *output,
vec![ vec![(None, "main"), (Some("len".to_string()), "Lua")]
(None, Some("main".to_string())),
(Some("len".to_string()), Some("Lua".to_string()))
]
); );
} else { } else {
assert_eq!( assert_eq!(
*output, *output,
vec![ vec![(None, "main"), (Some("len".to_string()), "C")]
(None, Some("main".to_string())),
(Some("len".to_string()), Some("C".to_string()))
]
); );
} }

View File

@ -1226,8 +1226,8 @@ fn test_inspect_stack() -> Result<()> {
let logline = lua.create_function(|lua, msg: StdString| { let logline = lua.create_function(|lua, msg: StdString| {
let debug = lua.inspect_stack(1).unwrap(); // caller let debug = lua.inspect_stack(1).unwrap(); // caller
let source = debug.source().short_src.map(core::str::from_utf8); let source = debug.source().short_src;
let source = source.transpose().unwrap().unwrap_or("?"); let source = source.as_deref().unwrap_or("?");
let line = debug.curr_line(); let line = debug.curr_line();
Ok(format!("{}:{} {}", source, line, msg)) Ok(format!("{}:{} {}", source, line, msg))
})?; })?;