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()?;
|
//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,
|
||||||
|
|
137
src/state.rs
137
src/state.rs
|
@ -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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute(&self, text: &str) -> Result<()> {
|
unsafe fn get_error_str(&self) -> &str {
|
||||||
let text = format!("{}\0", text);
|
let error_c = CStr::from_ptr(lua::lua_tostring(self.state_ptr(), -1));
|
||||||
|
|
||||||
unsafe {
|
|
||||||
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()
|
let error_str = error_c.to_str()
|
||||||
.unwrap_or_else(|_| {
|
.unwrap_or_else(|_| {
|
||||||
let b = error_c.to_bytes();
|
let b = error_c.to_bytes();
|
||||||
std::str::from_utf8_unchecked(b)
|
std::str::from_utf8_unchecked(b)
|
||||||
});
|
});
|
||||||
//.expect("Error bytes are invalid!");
|
lua::lua_pop(self.state_ptr(), 1);
|
||||||
//std::str::from
|
error_str
|
||||||
return Err(Error::runtime(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().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();
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
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> {
|
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)]
|
||||||
|
|
Loading…
Reference in New Issue