Issues

Issue #729 resolved

Lua and exception handling in the code.

Jason McKesson
created an issue

I was doing some coding, adding a couple of methods to the Lua-facing interface. I was wondering how other code in the system reported Lua errors, when I found this:

try
{
    instance = new Event();
}
catch (love::Exception &e)
{
    return luaL_error(L, "%s", e.what());
}

I know Love2D has its own registered Lua error handler. And that error handler, generally speaking, will not return control to the user's script. It'll terminate the application and display a message.

But this is wrong.

Unless the Lua runtime has been explicitly built as C++ (which does not appear to be an explicit requirement of Love2d), lua_error, and any function that calls it, will execute a long_jmp. This means that execution will never reach the end of the catch block. That is where the love::Exception object would be destroyed.

From this piece of C/C++ documentation:

If replacing of longjmp with throw and setjmp with catch would execute a non-trivial destructor for any automatic object, the behavior of such longjmp is undefined.

The lack of destruction is not the issue (or at least, not the big issue). After all, the registered error handler will not return control to the script. It'll lock the system into an error state and report a message to the user.

The big problem is that the Lua script is quite capable of using pcall and xpcall to intercept the error. Which means that the script can continue processing as "normal".

The memory leak isn't really the issue. The main issue is that, if the script intercepts the error, the C++ system is still in "undefined behavior" land. For all we know, it may still think that every piece of code being executed is still happening in the catch block. The C++ system will be in an unknown and unstable state. Throwing another exception in such a state could cause all kinds of badness.

Handling this properly will be very annoying, I'll grant you that. The correct way to handle this would be to copy the what string to a variable. The actual lua_error call will be made outside of the catch block, if something was written to that variable. Granted, even that variable isn't allowed to be be a stack object, so it'll probably have to be some kind of static object.

Comments (4)

  1. vrld

    I was always under the assumption that when using C++, Lua would throw an exception instead of longjmping, but after re-reading the reference manual I am not so sure anymore:

    3.6 – Error Handling in C

    Internally, Lua uses the C longjmp facility to handle errors. (You can also choose to use exceptions if you use C++; see file luaconf.h.)

    Does that mean the Lua library would have to be compiled as C++?

    In any case, the cleanest solution is probably to push the error message onto the Lua stack, and lua_error() outside the catch block if an error occurred. Something along the lines of this:

    try
    {
        something_stupid()
    }
    catch (love::Exception &e)
    {
        lua_pushstring(L, e.what());
    }
    if (lua_isstring(L, -1)) // you get the idea...
        return lua_error(L);
    
  2. Alex Szpakowski

    Does that mean the Lua library would have to be compiled as C++?

    Yeah, and IIRC even that won't work with LuaJIT on x86 Windows (bottom of the page here.)

    My first thought for a solution was the same as vrld's.

  3. Log in to comment