lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


Hi,

another one of my longer postings :-)  This time about error handling
mainly in the C API of Lua.  Let's start with this:

	lua_getglobal(L, "foo");
	if (lua_call(L, 0, 0) != LUA_ERROK)
          printf("error\n");

Looks pretty clean - call global function foo.  The call is protected
so if anything bad happens I catch it and can clean up etc.  But what
about the lua_getglobal()?  It may fail too!  Maybe a stack overflow,
maybe an "out of memory", maybe errors in tag methods active on the
global table.  How to protect against this?

int callfoo(lua_State *L) {
  lua_getglobal(L, "foo");
  lua_rawcall(L, 0, 0);		/* rawcall is enough this time */
  return 0;
}
   ...
   lua_pushcfunction(L, callfoo);
   lua_call(L, 0, 0);

Now the getglobal is protected too.  Hmm... but the pushcfunction may
fail.  So this does not work.  In this case, the only solution is

  lua_dostring(L, "foo()");

Not what you really want :-(

Another example (actually present in Lua 4.0):

  fp = fopen(...);
  ...
  lua_pushstring(...);
  lua_pushstring(...);
  lua_concat(2);
  protectedparser(...);  /* will catch errors */
  fclose(fp);

The parser is protected but any error in lua_pushstring/concat will
leave the file descriptor around.  Another, more nasty one:

  fp = fopen(...)
  if (fp)
    lua_pushusertag(L, (void*)fp, iotag);

If the lua_pushusertag fails the file descriptor will never be
collected.  In fact most apps using userdata will have this
problem - only the successful pushusertag makes sure that the
object is properly collected.  And the dostring() hack from
above will not work here.

Looking through Lua there are only 4 functions that are protected
against errors:

  lua_call
  lua_dofile
  lua_dostring
  lua_dobuffer

That may be enough for some applications but if you want a solid
system you're lost.


So my first suggestion: export something like the luaD_runprotected
function.  I would prefer different calling conventions though:
  
  int lua_catch(lua_State *L, void(*func)(lua_State *, va_list), ...);

This function would call <func> with the additional arguments
passed as a va_list and will catch any errors raised during the
execution of <func> and returns the error status (LUA_ERROK, ...).
So my first example could be rewritten to:

static void callglobal(lua_State *L, va_list args) {
  const char *name = va_arg(args, const char *);
  lua_getglobal(L, name);
  lua_rawcall(L, 0, 0);
}
  ...
  if (lua_catch(L, callglobal, "foo") != LUA_ERROK)
     ...

The lua_catch function mustn't raise an error by itself (like
the call() function in Lua - that may never return!).

Maybe some functions should have a protected variant in the
baselib (especially lua_pushusertag) and others may be better
without protection (i.e. lua_call) because you can't use them
reliable without an outer lua_catch.


To the next point: you may want to catch an error to clean up
and then pass this error on to upper functions.  At the moment
there's only one function to generate an error: lua_error.  If
called with a NULL string pointer it generates a LUA_ERRRUN
without printing an error message.  So a run time error can
be propagated.  But what about the other ones?  LUA_ERRMEM,
LUA_ERRERR, ...

So my second suggestion: export something like the luaD_breakrun
function.

  void /*noreturn*/ lua_throw(lua_State *L, int err);

It raises the error <err>.  Execution returns to the most
recent active lua_catch() which will return with <err>.
If no lua_catch is active, the process terminates via exit().

[Btw, in Sol I've changed the error codes to a typedef'ed enum
sol_Error.  Helps a lot in identifying places that handle
errors.]

In the language itself you have the same problem.  You may
catch errors with call()/dofile()/... but can only propagate
a runtime error.  Here it's even more difficult because these
functions return simple strings for the errors.  Testing for
a specific error is fragile (string compare).


---
Below only some thoughts about error handling and how to
use (better avoid) it.  Maybe someone finds them useful.
---
   

I tried to classify the current error codes:

 LUA_ERRMEM	malloc error.  Nothing printed.
 LUA_ERRERR	Double error.  While trying to print an error
		message another error occurred.  Nothing printed.

One may regard LUA_ERRMEM and LUA_ERRERR as fatal errors that
terminate the process.  At least they require some work to
bring the system back into a working state.

[It's a question whether it makes sense for the call() function
to even catch these errors.  Most probably Lua code is unable
to handle these conditions.  Then call() would only get the
LUA_ERRRUN and that can be passed on...]

 LUA_ERRRUN	A run time error occurred.  Message (maybe with
		backtrace) already printed.

A LUA_ERRRUN should only be generated when a printed message
with backtrace is really wanted and termination of the process
or destroying application data is acceptable.  Normally this
error can be avoided by careful checking your data before
processing it.

 LUA_ERRFILE	Unable to open file in lua_dofile().  Nothing
		printed.
 LUA_ERRSYNTAX	Syntax error parsing some Lua code.  Messages
		like in LUA_ERRRUN.

In fact these are not real errors in the sense that they force
a non-local function exit but extended results of the three
functions lua_dofile/dobuffer/dostring that have been mapped
into the error namespace.  I'm about to change the API for
these function in Sol to clean this up.


If you catch an error you can't be sure that the data the func-
tion was working with is still in a valid state!  If the error
was generated intentionally at a save point all _may_ be ok but
if it was raised due to a stack overflow or invalid argument to
an operator or function or something else you have to expect
inconsistent data.  Even if the error was intentionally but not
caught directly but some function levels above data may have been
corrupted because the intermediate functions were not prepared
to handle the non-local return.

(Note: lua_catch is there to protect the host environment, not
the Lua application!)

The lua_catch/throw functions would allow programmers to invent
new error codes.  But IMHO they shouldn't do that.  The more
error conditions you have the more trouble you get handling the
errors in the catch-code.  Especially when used in extension
libraries or packages.  You may be catching errors never seen
before and unable to tell what to do with them...


Ok.  That's all for them moment.  Comments welcome.

Ciao, ET.