As talked about at the 1/28/14 rust meetup, there is a serious safety hole. Lua error handling by default uses setjmp
/longjmp
, which won't fire destructors as it pops the stack. Lua can be compiled with C++, which will make it use throw
/catch
instead, which will fire Rust destructors, but this still isn't safe. Rust code typically makes the assumption that any premature stack unwinding is due to task failure, and therefore anything on the stack will be thrown away.
Compiling Lua as C++ is significantly better than letting it use setjmp
/longjmp
. However, Lua is typically only compiled this way when embedded (as source) into a C++ application. Freestanding Lua library compilation, which rust-lua takes advantage of by default, will generally be compiled with the default C behavior. Unfortunately, embedding the Lua source into rust-lua is problematic. Not only does it preclude building a Rust library that is then used in a larger application that itself links against Lua, but also the act of compiling Lua needs some platform-specific knowledge (to turn on a few defines that control behavior).
If we bite the bullet and embed Lua after all, we still have the problem where throwing C++ exceptions through Rust code is not particularly safe. I'm inclined to require Rust functions exposed to Lua to be marked unsafe
as a hint that there is an issue here. Beyond that I don't know what to do. Marking every method on State
as unsafe
is possible, but not something I'd like to do. We also cannot call every Lua C function in protected mode as the act of setting up protected mode may potentially throw an exception.
It's also been pointed out to me that C++ considers it undefined behavior to throw a C++ exception past an extern "C"
boundary. This does suggest another alternative, which is to wrap every lua function in a C++ function that catches all exceptions and returns an error code. rust-lua could then call those C++ wrappers (which themselves would be extern "C"
) and return a Result
. This has its own problems, of course, beyond the awkwardness of writing all the C++ wrappers. Not only are there performance implications to consider, but I have no idea how Lua will behave if any Lua functions are called after catching this exception (as this interrupted Lua's expected error handling). Furthermore, there's no good way to re-throw the exception again in order to resume Lua's error handling.
Another option to consider is that we could redefine the macros used to control error handling to invoke whatever code we want. The question is how to get Lua to unwind past its own code, stop when it hits a Rust function, and then resume when the Rust function finishes (and further, how to enforce in the Rust function that it needs to return immediately on any errors).
bug serious