lua-users home
lua-l archive

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


Bruno Silva wrote:
> 
> > > I have a newbie question then: how should I call this function in an
> > > *error-proof* way?
> > >
> > >         -- takes 2 strings, returns a boolean
> > >         handled = Function (arg1, arg2)
> > >
> > >         // calls Function
> > >         lua_getglobal(s_lua, "Function");
> > >         lua_pushstring (s_lua, arg1);
> > >         lua_pushstring (s_lua, arg2);
> > >         if (lua_call (s_lua, 2, 1) == 0)
> > >         {
> > >             handled = (bool) lua_tonumber (s_lua, -1);
> > >             lua_pop (s_lua, 1);
> > >         }
> >
> > That is difficult.  lua_getglobal, lua_pushstring and lua_tonumber
> > may raise an error.  Even lua_pushcfunction/cclosure may raise one
> > so it's pretty difficult to call something in a secure way.  A
> > lua_catch function (similar to luaD_runprotected) would help a lot.
> >
> This is unfortunate. As Luiz pointed out though, this is less of a
> problem when the functions being called are written in Lua.

Hmm... it doesn't matter whether the function called by lua_call is a
C or Lua function.  Results are 100% the same.

> So is the following any safer?
> 
>         lua_dostring (L, "return Function (arg1, arg2)");

This will avoid possible errors from lua_getglobal and lua_pushstring.
But somehow you have to pass arg1/arg2 to Lua and you have to fetch
the results.  So practically it will not help you very much.
(Btw, lua_dostring _compiles_ the string so it's pretty slow.)

> This leaves whatever values "Function" returns on the stack and would
> allow leaking if Function is not properly written.

I don't understand what problems you have with "stack leaks".  Where's
your problem of properly using the API?  Your first example above
using lua_call is safe in this regard.  It will always leave the stack
as it was before the lua_getglobal.  The lua_dostring example alone
will leave things on the stack, but what would you expect?
Either omit the return:

   lua_dostring(L, "Function(arg1, arg2)");  // ignore failure, no results

or force it to return 2 results (2nd is nil):

   if (lua_dostring(L, "return Function(arg1, arg2), nil") == 0) {
      ... // process result at index -2 (at -1 is the nil)
      lua_pop(L, 2);
   } else error...

or clean up afterwards:

   int x = lua_gettop(L);
   if (lua_dostring(L, "return Function(arg1, arg2)" == 0) {
      ... // process var num of results between x and lua_gettop()
      lua_settop(L, x);  // remove all results
   } else error...

All these three variants will leave a clean stack and will not call
exit() (unless Function calls Lua's exit of course ;-).

The possibly raised errors in your first example are:

 - lua_getglobal may invoke tag methods and by that executes arbitrary
   code and can generate all kind of errors.

 - lua_pushstring may get an out of memory error.

 - lua_getglobal and lua_pushstring may get a stack overflow.

 - If you'd used lua_tostring instead of lua_tonumber you could get
   an out of memory there too.

That're the only possible errors.  You don't have to protect against
errors occurring during the lua_call execution (lua_call takes care
of them and returns an appropriate status) but errors while setting
up the arguments for and processing the results of lua_call.

Unfortunately it's pretty difficult to protect against these errors.

> I am planning to write a convenience
> class that provides this behavior, and change the Lua sources to make
> sure no Lua API calls can happen outside this context (that is, one that
> doesn't have a setjmp to jump back to).

_I_ would simply use the (undocumented) function luaD_runprotected (it
creates a setjmp context).  It's already extern; only the prototype is
missing.  I.e. to make your first example safe:

  // -- Prototype for luaD_runprotected(L, f, ud) --
  // It calls f with the ud ptr as second arg and catches any Lua errors
  // that may happen while executing f.
  // If an error occurs, the stack pointer is reset to the value it had
  // before calling f and the error status is returned (LUA_ERRxxx).
  // If everything goes well, the stack is left as f had set it and 0 is
  // returned.
  int luaD_runprotected (lua_State *L, void (*f)(lua_State *, void *), void *ud);

  struct callenv { char *func, *arg1, *arg2; bool result; };

  // this func pushes the arguments, calls the function and converts
  // the results.  It'll be called via luaD_runprotected.
  static void callit(lua_State *L, void *x)
  {
      struct callenv *env = (struct callenv *)x;
      lua_getglobal(L, env->func);
      lua_pushstring(L, env->arg1);
      lua_pushstring(L, env->arg2);
      lua_rawcall(L, 2, 1);  // no need for lua_call now
      env->result = (bool)lua_tonumber(L, -1);
      // pop results.  luaD_runprotected only cleans up in case of errors.
      lua_pop(L, 1);
   }

   ...
   // call func with arg1, arg2 - return bool (or garbage if func fails)
   bool call(char *func, char *arg1, char *arg2)
   {
      struct callenv env;
      env.func = func;
      env.arg1 = arg1;
      env.arg2 = arg2;
      if (luaD_runprotected(L, callit, &env) == 0)
         printf("results is %d\n", env.result);
      else
         printf("error calling %s\n", env.func);
      return env.result;
   }

If you are paranoid about functions like callit leaving elements on the
stack you could check it around the luaD_runprotected call.  But another
note here: if the stack after calling luaD_runprotected is lower then
before you have a stack underflow and you can't recover that case.  Some
elements on the stack (or even memory outside the stack) will be damaged.
Only faulty C code can produce a stack underflow and an abort/exit is the
only sane thing to do.

I hope that a luaD_runprotected-like function will be available in Lua 4.1.
In Sol I called it sol_catch:

   sol_Error sol_catch(sol_State *, void (f)(sol_State *, va_list), ...)

and f gets the va_list of the varargs of sol_catch.

Ciao, ET.