The current implementation of Lua (Lua 4.0) makes extensive use
of the setjmp() and longjmp() functions
for error handling. This is a very convenient for programmers,
allowing constructs like this:
some_c_function(lua_State *L)
{
const char *str = luaL_check_string(L,1);
...
}
This style of programming means that you do not have to check the
return value of
luaL_check_string() because it will longjmp()
out of the function and back to the Lua interpreter if the requested
argument is not an LUA_TSTRING object.
However, when using C++, setjmp() and longjmp()
do not adhere to the C++ stack unwinding semantics, and can cause objects
to get lost because destructors will not be called. For example:
extern "C" some_cpp_function(lua_State *L)
{
CppObjectWhichNeedsDtor x;
x.name = luaL_check_string(L,1);
}
In the code example above (which is, admittedly, contrived) if
luaL_check_string() discovers that the first Lua argument
to the function is not a LUA_TSTRING object, it will
longjmp() back to the Lua interpreter, and completely miss
the destructor for the CppObjectWhichNeedsDtor object.
My change modifies the setjmp()/longjmp() function
calls into C++ throw ... catch. This is made trivial by
the fact that Lua's code is very modular, and
setjmp()/longjmp() are only referenced by one function (each).
The only cog-in-the-works is that Lua is usually compiled with a C compiler,
not a C++ compiler.
In my source base, I have made the change conditional on a preprocessor
macro THROW_INSTEAD_OF_LONGJMP, and have tried to disturb
the source base as little as possible.
The major portion of my change involves a new source
file lua/src/ldoxx.cpp which moves the functions
luaD_breakrun() and luaD_runprotected()
into a to C++ module and changes the setjmp() in
luaD_runprotected() into a try .. catch block
and the longjmp() in luaD_breakrun() into a
C++ throw. The ldoxx.cpp file can be down-loaded
here.
/*
** $Id:$
** C++ version of luaD_protected_run and luaD_break_run.
** Written by Michael Cuddy (mcuddy@fensende.com), based on functions
** in ldo.c
**
** All rights to derived work ceded to original copyright holder.
**
** See Copyright Notice in lua.h
*/
#ifdef THROW_INSTEAD_OF_LONGJMP
#ifndef __cplusplus
#error "try compiling ldoxx.cpp with a C++ compiler"
#endif /* __cplusplus */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
extern "C" {
#include "lua.h"
#include "lvm.h"
};
class lua_exception {
int status;
public:
lua_exception(int status_) : status(status_) { };
int get_status() { return status; }
};
// XXX -- hack, this function shouldn't be here, it's duplicated from
// ldo.c, where it's declared static.
static void restore_stack_limit (lua_State *L) {
if (L->top - L->stack < L->stacksize - 1)
L->stack_last = L->stack + (L->stacksize-1);
}
extern "C" void luaD_breakrun (lua_State *L, int errcode) {
throw lua_exception(errcode);
}
extern "C" int luaD_runprotected (lua_State *L, void (*f)(lua_State *, void *), void *ud) {
StkId oldCbase = L->Cbase;
StkId oldtop = L->top;
int allowhooks = L->allowhooks;
int status = 0;
try {
(*f)(L, ud);
} catch (lua_exception &e) { /* an error occurred: restore the state */
status = e.get_status();
L->allowhooks = allowhooks;
L->Cbase = oldCbase;
L->top = oldtop;
restore_stack_limit(L);
}
return status;
}
/* }====================================================== */
#endif /* THROW_INSTEAD_OF_LONGJMP */
lstate.c, lstate.h and ldo.cpp:
In lstate.c, the initialization of L->errorJmp in
lua_open() is #ifdef'ed out with
THROW_INSTEAD_OF_LONGJMP:
88 L->allowhooks = 1;
89 #ifndef THROW_INSTEAD_OF_LONGJMP
90 L->errorJmp = NULL;
91 #endif /* !THROW_INSTEAD_OF_LONGJMP */
lstate.h, the forward declaration of the
lua_longjmp structure and the structure member
errorJmp is #ifdef'ed out of the
lua_State structure:
33
34 #ifndef THROW_INSTEAD_OF_LONGJMP
35 struct lua_longjmp; /* defined in ldo.c */
36 #endif /* !THROW_INSTEAD_OF_LONGJMP */
37 struct TM; /* defined in ltm.h */
...
54 StkId Cbase; /* base for current C function */
55 #ifndef THROW_INSTEAD_OF_LONGJMP
56 struct lua_longjmp *errorJmp; /* current error recover point */
57 #endif /* !THROW_INSTEAD_OF_LONGJMP */
58 char *Mbuffer; /* global buffer */
In ldo.c, the functions moved to ldoxx.cpp
are also #ifdef'ed out:
57 #ifndef THROW_INSTEAD_OF_LONGJMP
58 /* mcuddy - if this function is changed, change it in ldoxx.cpp, too. */
59 static void restore_stack_limit (lua_State *L) {
60 if (L->top - L->stack < L->stacksize - 1 )
61 L->stack_last = L->stack + (L->stacksize-1);
62 }
63 #endif /* !THROW_INSTEAD_OF_LONGJMP */
...
320 /*
321 ** {======================================================
322 ** Error-recover functions (based on long jumps)
323 ** =======================================================
324 */
325
326 #ifdef THROW_INSTEAD_OF_LONGJMP
327 /*
328 ** in the case where we're using throw() for C++ instead of longjmp
329 ** (which keeps C++ objects from being destroyed!) the functions
330 ** luaD_breakrun() and luaD_runprotected(), are defined in ldoxx.cpp
331 ** Also, the lua_longjmp structure is not needed.
332 */
333 #else
334 /* chain list of long jump buffers */
335 struct lua_longjmp {
336 jmp_buf b;
337 struct lua_longjmp *previous;
338 volatile int status; /* error code */
339 };
340 #endif /* THROW_INSTEAD_OF_LONGJMP */
...
359 }
360
361 #ifndef THROW_INSTEAD_OF_LONGJMP
362 void luaD_breakrun (lua_State *L, int errcode) {
363 if (L->errorJmp) {
...
391 L->errorJmp = lj.previous; /* restore old error handler */
392 return lj.status;
393 }
394 #endif /* THROW_INSTEAD_OF_LONGJMP */
THROW_INSTEAD_OF_LONGJMP,
Lua will now maintain the C++ guarantee of stack unwinding.
A future direction to take this would be to make the lua_exception
public (and derive it from std::exception which would allow
user code to catch the exception for extended error handling. This trivial,
and is left as an excercise to the reader.