Lua And Exceptions Hacking Notes |
|
As of version 5.1 built as C++.
struct lua_longjmp { struct lua_longjmp *previous; int dummy; volatile int status; };Its role is to keep track of the protected call stack. We start with an empty global buffer.
// chain a new local buffer, status = 0 // global buffer now points to it try { // call } catch (...) { // force local status to non 0 (e.g. -1) } // restore global buffer // return local status
// if empty global buffer then panic // store status in global buffer // throw its address !
If indeed they weren't caught at Lua level at all, catching them later would unwind violently out of Lua's call stack, leaving us with an inconsistent Lua state. This is what happened when I tweaked Lua 5.0.2 that way.
To ensure a consistent Lua state while still carrying the exception to the other side, we would need to store a copy of it in a safe place, unwind normally to toplevel, and finally throw the copy. However, because catch matches types (Stroustrup 14.3), we lose type information when we `catch (...)`, and with that the ability to copy generic exceptions.
To implement the copy mechanism discussed above, we could narrow the type range of alien exceptions to the standard `std::exception`. If the programmer needs to catch other exceptions, he can wrap them into `std::exception` subclasses. The remaining exceptions would still have to be silently ignored.
This solution has the advantage of simplicity, and works well with hierarchical exception classes (one wrapper per root class). It is however limited to situations where both ends can agree on the `std::exception` wrapping.
NOTE: this means wrapper generators need to be adapted to this scheme.
As a temporary solution, one may also narrow the exception space to a set of custom exceptions, that is, manually at Lua build time.
TODO: check copy of `std::exception`
Looking back, it appears as an appropriate solution in our very case, and it should also be enough for casual applications. However it entails translation between error systems and thus redundancies.
If the Lua source became (or forked) exception-safe, we could enable it to catch C++ exceptions from Lua, either explicitely or through a binding to an eventual Lua exception scheme. A solution is sketched in the next section.
catch (struct lua_longjump * p_lj) { // force local status to non 0 } catch (...) { // do soft unwinding throw }
NOTE: `p_lj` should be pointing to the current `lj`.
TODO: assert that.
This should guarantee that we don't eat alien exceptions, unless someone twisted decides to throw `(struct lua_longjump)*` all over the place from her library.
To devise our algorithm, we need to identify the vital resources that are acquired and released by `pcall`.
Outline of the pcall stack
The top and bottom levels wrap when pcalls are nested.
The D_pcall cleanup
if (status != 0) { /* an error occurred? */ StkId oldtop = restorestack(L, old_top); luaF_close(L, oldtop); /* close eventual pending closures */ luaD_seterrorobj(L, status, oldtop); L->nCcalls = oldnCcalls; L->ci = restoreci(L, old_ci); L->base = L->ci->base; L->savedpc = L->ci->savedpc; L->allowhook = old_allowhooks; restore_stack_limit(L); } L->errfunc = old_errfunc;
It restores the state previously acquired:
// ptrdiff_t old_top, ptrdiff_t ef unsigned short oldnCcalls = L->nCcalls; ptrdiff_t old_ci = saveci(L, L->ci); lu_byte old_allowhooks = L->allowhook; ptrdiff_t old_errfunc = L->errfunc; L->errfunc = ef;
Now, we could use the Resource Acquisition Is Initialization pattern to perform cleanup code automatically at object destruction. It is the natural way to integrate with exceptions (Stroustrup 14.4.1).
Stroustrup 14.9 is a fundamental.