Implement an error handler that collects the stacktrace

This commit is contained in:
SeanOMik 2024-01-28 13:32:08 -05:00
parent c6f2d303e2
commit 2848d1deee
Signed by: SeanOMik
GPG Key ID: FEC9E2FC15235964
3 changed files with 173 additions and 30 deletions

View File

@ -47,7 +47,7 @@ fn main() -> Result<()> {
//let globals = lua.globals()?; //let globals = lua.globals()?;
globals.set("X", tbl)?; globals.set("X", tbl)?;
lua.execute(r#" let res = lua.execute(r#"
require "util" require "util"
--[[function dump_table(tbl) --[[function dump_table(tbl)
@ -99,10 +99,33 @@ fn main() -> Result<()> {
local v2 = Vec2.new(500, 500) local v2 = Vec2.new(500, 500)
print("v2 is (" .. v2.x .. ", " .. v2.y .. ")") print("v2 is (" .. v2.x .. ", " .. v2.y .. ")")
local v_add = v1:add(v2) local v_add = v1 + v2
v_add.x = 90 v_add.x = 90
print("v_add is (" .. v_add.x .. ", " .. v_add.y .. ")") print("v_add is (" .. v_add.x .. ", " .. v_add.y .. ")")
"#).unwrap();
function f1(v1, v2)
--print("f1 tb: " .. debug.traceback(1))
local v_add = v1:add(v2)
end
function f2(v1, v2)
--print("f2 tb: " .. debug.traceback(1))
f1(v1, v2)
end
function f3(v1, v2)
--print("f3 tb: " .. debug.traceback(1))
f2(v1, v2)
end
f3(v1, v2)
"#);
// if unwrapped, the new lines in the message would be escaped making
// the traceback in the error difficult to read.
if let Err(e) = res {
panic!("{}", e);
}
unsafe { unsafe {
assert_eq!(lua::lua_gettop(lua.state_ptr()), 0); // ensure that nothing is left on the stack assert_eq!(lua::lua_gettop(lua.state_ptr()), 0); // ensure that nothing is left on the stack
@ -155,19 +178,24 @@ impl<'a> PushToLuaStack<'a> for LuaRef {
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {
#[error("Syntax error: {0}")]
Syntax(String),
/// An error returned from lua /// An error returned from lua
#[error("Lua runtime error: {0}")] #[error("Lua runtime error: {0}")]
Runtime(String), Runtime(String),
#[error("Error when running a __gc metamethod")]
GcFailure(String),
/// Ran into a not enough memory error when trying to grow the lua stack. /// Ran into a not enough memory error when trying to grow the lua stack.
#[error("Ran out of memory when attempting to use `lua_checkstack`")] #[error("Failed to allocate memory")]
Oom, MemoryAlloc,
#[error("Ran into a nill value on the stack")] #[error("Ran into a nill value on the stack")]
Nil, Nil,
#[error("Unexpected type, expected {0} but got {1}")] #[error("Unexpected type, expected {0} but got {1}")]
UnexpectedType(String, String), UnexpectedType(String, String),
#[error("Bad argument provided to `{}` at position {arg_index}{}, cause: {error}", #[error("bad argument #{arg_index}{} to `{}` ({error})",
.func.clone().unwrap_or("Unknown".to_string()), .arg_name.clone().map(|a| format!(" (name: {})", a)).unwrap_or("".to_string()),
.arg_name.clone().map(|a| format!(" (name: {})", a)).unwrap_or("".to_string()))] .func.clone().unwrap_or("Unknown".to_string())
)]
BadArgument { BadArgument {
func: Option<String>, func: Option<String>,
arg_index: i32, arg_index: i32,

View File

@ -1,5 +1,5 @@
use core::ffi; use core::ffi;
use std::{alloc::{self, Layout}, any::TypeId, collections::HashMap, ffi::{CString, CStr}, mem, ptr::{self, NonNull}, str::Utf8Error}; use std::{alloc::{self, Layout}, any::TypeId, collections::{HashMap, VecDeque}, ffi::{CStr, CString}, mem, ptr::{self, NonNull}, str::Utf8Error};
use mlua_sys as lua; use mlua_sys as lua;
@ -150,22 +150,64 @@ impl State {
} }
} }
unsafe fn get_error_str(&self) -> &str {
let error_c = CStr::from_ptr(lua::lua_tostring(self.state_ptr(), -1));
let error_str = error_c.to_str()
.unwrap_or_else(|_| {
let b = error_c.to_bytes();
std::str::from_utf8_unchecked(b)
});
lua::lua_pop(self.state_ptr(), 1);
error_str
}
/// A function that handles errors from calling scripts. It modifies the message to include
/// the stack traceback.
fn state_error_handler(&self, msg: String) -> Result<String> {
self.traceback(Some(&msg))
}
pub fn execute(&self, text: &str) -> Result<()> { pub fn execute(&self, text: &str) -> Result<()> {
let text = format!("{}\0", text); let text = format!("{}\0", text);
unsafe { unsafe {
self.ensure_stack(3)?;
let _g = StackGuard::new(self);
let lua = self.lua.as_ptr(); let lua = self.lua.as_ptr();
let text_c = text.as_ptr() as *const ffi::c_char; let text_c = text.as_ptr().cast();
if lua::luaL_dostring(lua, text_c) != 0 {
let error_c = CStr::from_ptr(lua::lua_tostring(lua, -1)); // Subtract one from the length to exclude the null terminator
let error_str = error_c.to_str() match lua::luaL_loadbuffer(self.state_ptr(), text_c, text.len() - 1, "test.lua\0".as_ptr().cast()) {
.unwrap_or_else(|_| { lua::LUA_ERRSYNTAX => {
let b = error_c.to_bytes(); return Err(Error::Syntax(self.get_error_str().to_string()));
std::str::from_utf8_unchecked(b) },
}); lua::LUA_ERRMEM => {
//.expect("Error bytes are invalid!"); return Err(Error::MemoryAlloc);
//std::str::from }
return Err(Error::runtime(error_str)); _ => {},
}
let handler = self.create_function(|lua, msg: String| {
lua.state_error_handler(msg)
})?;
handler.push_to_lua_stack(self)?;
lua::lua_insert(lua, -2);
match lua::lua_pcall(lua, 0, lua::LUA_MULTRET, -2) {
lua::LUA_ERRRUN => {
let er = self.get_error_str();
return Err(Error::runtime(er));
},
lua::LUA_ERRMEM => {
return Err(Error::MemoryAlloc);
},
lua::LUA_ERRERR => {
let er = self.get_error_str();
// if this panics, its a bug
panic!("Failure when trying to execute error handler function! ({})", er);
},
_ => {}
} }
} }
@ -176,7 +218,7 @@ impl State {
let lua = self.lua.as_ptr(); let lua = self.lua.as_ptr();
unsafe { unsafe {
let base = "_G".as_ptr() as *const i8; let base = "_G\0".as_ptr().cast();
lua::luaL_requiref(lua, base, lua::luaopen_base, 1); lua::luaL_requiref(lua, base, lua::luaopen_base, 1);
assert_eq!(lua::lua_tonumber(lua, -1), 0.0); // ensure that loading base worked assert_eq!(lua::lua_tonumber(lua, -1), 0.0); // ensure that loading base worked
lua::lua_pop(lua, 1); lua::lua_pop(lua, 1);
@ -210,7 +252,7 @@ impl State {
/// The result will be Ok if the stack has space for `n` values. /// The result will be Ok if the stack has space for `n` values.
pub unsafe fn ensure_stack(&self, n: i32) -> Result<()> { pub unsafe fn ensure_stack(&self, n: i32) -> Result<()> {
if lua::lua_checkstack(self.state_ptr(), n) == 0 { if lua::lua_checkstack(self.state_ptr(), n) == 0 {
Err(Error::Oom) Err(Error::MemoryAlloc)
} else { } else {
Ok(()) Ok(())
} }
@ -328,14 +370,8 @@ impl State {
let ptr = lua::lua_newuserdata(s, mem::size_of::<T>()).cast::<T>(); let ptr = lua::lua_newuserdata(s, mem::size_of::<T>()).cast::<T>();
ptr::write(ptr, data); ptr::write(ptr, data);
let name_cstr = format!("{}\0", name); // get the current metatable, or create a new one and push it to the stack.
let name_cstr = name_cstr.as_str(); let udmts = &mut self.get_extra_space().userdata_metatables;
let name_cstr = name_cstr.as_ptr() as *const i8;
// attempt to get the metatable
let udmts = &mut self.get_extra_space().userdata_metatables;//.get(&TypeId::of::<T>())
if !udmts.contains_key(&TypeId::of::<T>()) { if !udmts.contains_key(&TypeId::of::<T>()) {
// create the userdata's metatable and store it in extra space // create the userdata's metatable and store it in extra space
let mt = self.create_userdata_metatable::<T>()?; let mt = self.create_userdata_metatable::<T>()?;
@ -428,4 +464,61 @@ impl State {
Ok(mt) Ok(mt)
} }
pub(crate) unsafe fn debug_info(&self) -> Box<lua::lua_Debug> {
let s = self.state_ptr();
// lua_Debug doesn't implement default
let ar = alloc::alloc(Layout::new::<lua::lua_Debug>())
.cast::<lua::lua_Debug>();
lua::lua_getstack(s, 1, ar);
let what = "nSl".as_ptr() as *const i8;
lua::lua_getinfo(s, what, ar);
Box::from_raw(ar)
}
pub fn current_line(&self) -> usize {
unsafe {
let ar = self.debug_info();
ar.currentline as usize
}
}
pub fn traceback(&self, msg: Option<&str>) -> Result<String> {
unsafe {
let _g = StackGuard::new(self);
let s = self.state_ptr();
let globals = self.globals()?;
let debug = globals.get::<_, Table>("debug")?;
let tb_fn = debug.get::<_, Function>("traceback")?;
tb_fn.push_to_lua_stack(self)?;
if let Some(msg) = msg {
lua::lua_pushstring(s, msg.as_ptr().cast());
} else {
lua::lua_pushnil(s);
}
// skip the call to debug.traceback, and the
// error handler (which hopefully called this)
lua::lua_pushnumber(s, 2.0);
match lua::lua_pcall(s, 2, 1, 0) {
lua::LUA_ERRRUN => {
let er = self.get_error_str();
return Err(Error::runtime(er));
},
lua::LUA_ERRMEM => {
return Err(Error::MemoryAlloc)
},
_ => {}
}
let v = Value::from_lua_stack(self)?;
let s = v.into_string()?;
Ok(s)
}
}
} }

View File

@ -63,6 +63,15 @@ impl<'a> Value<'a> {
} }
} }
} }
pub fn into_string(self) -> crate::Result<String> {
match self {
Value::String(s) => Ok(s),
_ => {
Err(crate::Error::UnexpectedType("String".to_string(), self.type_name().to_string()))
}
}
}
} }
impl<'a> PushToLuaStack<'a> for Value<'a> { impl<'a> PushToLuaStack<'a> for Value<'a> {
@ -241,6 +250,19 @@ impl<'a> FromLuaVec<'a> for ValueVec<'a> {
} }
} }
impl<'a, T: FromLua<'a>> FromLuaVec<'a> for T {
fn from_lua_value_vec(state: &'a State, mut values: ValueVec<'a>) -> crate::Result<Self> {
if let Some(val) = values.pop_front() {
T::from_lua(state, val)
} else {
Err(crate::Error::IncorrectArgCount {
arg_expected: 1,
arg_count: values.len() as i32,
})
}
}
}
macro_rules! impl_from_lua_vec_tuple { macro_rules! impl_from_lua_vec_tuple {
( $count: expr, $first: tt, $( $name: tt ),+ ) => ( ( $count: expr, $first: tt, $( $name: tt ),+ ) => (
#[allow(non_snake_case)] #[allow(non_snake_case)]