Implement an error handler that collects the stacktrace
This commit is contained in:
parent
c6f2d303e2
commit
2848d1deee
44
src/main.rs
44
src/main.rs
|
@ -47,7 +47,7 @@ fn main() -> Result<()> {
|
|||
//let globals = lua.globals()?;
|
||||
globals.set("X", tbl)?;
|
||||
|
||||
lua.execute(r#"
|
||||
let res = lua.execute(r#"
|
||||
require "util"
|
||||
|
||||
--[[function dump_table(tbl)
|
||||
|
@ -99,10 +99,33 @@ fn main() -> Result<()> {
|
|||
local v2 = Vec2.new(500, 500)
|
||||
print("v2 is (" .. v2.x .. ", " .. v2.y .. ")")
|
||||
|
||||
local v_add = v1:add(v2)
|
||||
local v_add = v1 + v2
|
||||
v_add.x = 90
|
||||
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 {
|
||||
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)]
|
||||
pub enum Error {
|
||||
#[error("Syntax error: {0}")]
|
||||
Syntax(String),
|
||||
/// An error returned from lua
|
||||
#[error("Lua runtime error: {0}")]
|
||||
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.
|
||||
#[error("Ran out of memory when attempting to use `lua_checkstack`")]
|
||||
Oom,
|
||||
#[error("Failed to allocate memory")]
|
||||
MemoryAlloc,
|
||||
#[error("Ran into a nill value on the stack")]
|
||||
Nil,
|
||||
#[error("Unexpected type, expected {0} but got {1}")]
|
||||
UnexpectedType(String, String),
|
||||
#[error("Bad argument provided to `{}` at position {arg_index}{}, cause: {error}",
|
||||
.func.clone().unwrap_or("Unknown".to_string()),
|
||||
.arg_name.clone().map(|a| format!(" (name: {})", a)).unwrap_or("".to_string()))]
|
||||
#[error("bad argument #{arg_index}{} to `{}` ({error})",
|
||||
.arg_name.clone().map(|a| format!(" (name: {})", a)).unwrap_or("".to_string()),
|
||||
.func.clone().unwrap_or("Unknown".to_string())
|
||||
)]
|
||||
BadArgument {
|
||||
func: Option<String>,
|
||||
arg_index: i32,
|
||||
|
|
137
src/state.rs
137
src/state.rs
|
@ -1,5 +1,5 @@
|
|||
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;
|
||||
|
||||
|
@ -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<()> {
|
||||
let text = format!("{}\0", text);
|
||||
|
||||
unsafe {
|
||||
self.ensure_stack(3)?;
|
||||
let _g = StackGuard::new(self);
|
||||
|
||||
let lua = self.lua.as_ptr();
|
||||
let text_c = text.as_ptr() as *const ffi::c_char;
|
||||
if lua::luaL_dostring(lua, text_c) != 0 {
|
||||
let error_c = CStr::from_ptr(lua::lua_tostring(lua, -1));
|
||||
let error_str = error_c.to_str()
|
||||
.unwrap_or_else(|_| {
|
||||
let b = error_c.to_bytes();
|
||||
std::str::from_utf8_unchecked(b)
|
||||
});
|
||||
//.expect("Error bytes are invalid!");
|
||||
//std::str::from
|
||||
return Err(Error::runtime(error_str));
|
||||
let text_c = text.as_ptr().cast();
|
||||
|
||||
// Subtract one from the length to exclude the null terminator
|
||||
match lua::luaL_loadbuffer(self.state_ptr(), text_c, text.len() - 1, "test.lua\0".as_ptr().cast()) {
|
||||
lua::LUA_ERRSYNTAX => {
|
||||
return Err(Error::Syntax(self.get_error_str().to_string()));
|
||||
},
|
||||
lua::LUA_ERRMEM => {
|
||||
return Err(Error::MemoryAlloc);
|
||||
}
|
||||
_ => {},
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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);
|
||||
assert_eq!(lua::lua_tonumber(lua, -1), 0.0); // ensure that loading base worked
|
||||
lua::lua_pop(lua, 1);
|
||||
|
@ -210,7 +252,7 @@ impl State {
|
|||
/// The result will be Ok if the stack has space for `n` values.
|
||||
pub unsafe fn ensure_stack(&self, n: i32) -> Result<()> {
|
||||
if lua::lua_checkstack(self.state_ptr(), n) == 0 {
|
||||
Err(Error::Oom)
|
||||
Err(Error::MemoryAlloc)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
|
@ -328,14 +370,8 @@ impl State {
|
|||
let ptr = lua::lua_newuserdata(s, mem::size_of::<T>()).cast::<T>();
|
||||
ptr::write(ptr, data);
|
||||
|
||||
let name_cstr = format!("{}\0", name);
|
||||
let name_cstr = name_cstr.as_str();
|
||||
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>())
|
||||
|
||||
// get the current metatable, or create a new one and push it to the stack.
|
||||
let udmts = &mut self.get_extra_space().userdata_metatables;
|
||||
if !udmts.contains_key(&TypeId::of::<T>()) {
|
||||
// create the userdata's metatable and store it in extra space
|
||||
let mt = self.create_userdata_metatable::<T>()?;
|
||||
|
@ -428,4 +464,61 @@ impl State {
|
|||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
22
src/value.rs
22
src/value.rs
|
@ -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> {
|
||||
|
@ -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 {
|
||||
( $count: expr, $first: tt, $( $name: tt ),+ ) => (
|
||||
#[allow(non_snake_case)]
|
||||
|
|
Loading…
Reference in New Issue