-
-
Notifications
You must be signed in to change notification settings - Fork 135
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add stringified backtrace to WrappedError passed back to Lua #45
Conversation
This makes the behaviour consistent with how Lua behaves when a normal string is used as error instead of a userdata. When a string is used, the Lua runtime will add a complete stacktrace to the error output; when a userdata is used, it will not do that, but only display the tostring(ud) at the time the error is printed. This makes debugging things much harder (especially when writing modules), because errors emitted by the Rust code (such as type conversion errors) are not attribute to a code line. However, this implementation has two important caveats: - The runtime complexity of walking the stack and transforming it into a nice human-readable string; this complexity is to be expected to also exist in case of Lua doing that internally, modulo extra cost for utf8 checks inside Rust. - When the WrappedError is transported back into and out of Rust, the stacktrace is lost and regenerated when passed back into Lua. This means that the error cause can be "shadowed" by passing through Rust. Handling this would require a more invasive change which allows chaining Error instances in a way which allows carrying the backtrace without changing the Error enum to contain an optional stacktrace in each variant; I’ll leave this to future work. Fixes mlua-rs#44.
// XXX: is this actually sound? we just popped it off the stack, | ||
// didn't we? | ||
err.0.clone() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lua runs GC only during lua_load
/ lua_pcall
calls, so it's safe to pop value in this case.
Thanks for PR! Overall it looks good. As you mentioned in the commit message, attached stacktrace will be lost during WrappedError -> Error conversion and it's quite hard to preserve it. However, I'm sure preserving stacktrace in the I ran a few checks and seems this solution unfortunately is not comprehensive and leaves a few edge cases where stack trace would be incomplete or incorrectly formatted: Code example: fn rust_error(lua: &Lua, _: ()) -> LuaResult<()> {
lua.globals().get::<_, LuaFunction>("rust_error2")?.call(())
}
fn rust_error2(lua: &Lua, _: ()) -> LuaResult<()> {
lua.globals().get::<_, LuaFunction>("rust_error3")?.call(())
}
fn rust_error3(_: &Lua, _: ()) -> LuaResult<()> {
Err(LuaError::RuntimeError("rust error".into()))
}
fn lua_error(lua: &Lua, _: ()) -> LuaResult<()> {
lua.globals().get::<_, LuaFunction>("lua_error2")?.call(())
}
fn lua_error2(lua: &Lua, _: ()) -> LuaResult<()> {
lua.globals().get::<_, LuaFunction>("lua_error3")?.call(())
}
fn lua_error3(lua: &Lua, _: ()) -> LuaResult<()> {
lua.load("error(\"lua error\")").exec()
}
#[mlua::lua_module]
fn my_module(lua: &Lua) -> LuaResult<bool> {
let globals = lua.globals();
for (k, v) in vec![
("rust_error", lua.create_function(rust_error)?),
("rust_error2", lua.create_function(rust_error2)?),
("rust_error3", lua.create_function(rust_error3)?),
("lua_error", lua.create_function(lua_error)?),
("lua_error2", lua.create_function(lua_error2)?),
("lua_error3", lua.create_function(lua_error3)?),
] {
globals.set(k, v)?;
}
Ok(true)
} And test cases: --
No trace that the error was triggered in the --
The error cause --
double stacktrace again Also a few comments about
|
Thank you :)
Yes, I agree.
Did you run the checks with an implementation where CallbackError is used to preserve the stacktrace, or did you manage to produce those traces with the current implementation?
I agree on both points. How would you prefer to proceed?
|
I made some changes and released a new version that addresses a missing tracebacks in a module mode. I already have this function implemented https://github.com/khvzak/mlua/blob/v0.6.0-beta.2/src/ffi/compat53.rs#L676 that I believe would require minimal modifications, to not to trigger 'm' errors. |
@khvzak I rebuilt my project with mlua master, and it looks very nice, thanks! |
This makes the behaviour consistent with how Lua behaves when a
normal string is used as error instead of a userdata.
When a string is used, the Lua runtime will add a complete
stacktrace to the error output; when a userdata is used, it will
not do that, but only display the tostring(ud) at the time the
error is printed.
This makes debugging things much harder (especially when writing
modules), because errors emitted by the Rust code (such as type
conversion errors) are not attribute to a code line.
However, this implementation has two important caveats:
The runtime complexity of walking the stack and transforming it
into a nice human-readable string; this complexity is to be
expected to also exist in case of Lua doing that internally,
modulo extra cost for utf8 checks inside Rust.
When the WrappedError is transported back into and out of Rust,
the stacktrace is lost and regenerated when passed back into
Lua.
This means that the error cause can be "shadowed" by passing
through Rust.
Handling this would require a more invasive change which allows
chaining Error instances in a way which allows carrying the
backtrace without changing the Error enum to contain an optional
stacktrace in each variant; I’ll leave this to future work.
Fixes #44.