Error Handling Between Lua And Cplusplus

lua-users home
wiki

This page explains some approaches and issues relating to handling errors across the C++/Lua boundary.

[ Note: this page is somewhat of a work in progress. Additional comments are welcome. ]

Linking

If you compile Lua in C, you must surround your #include <lua.h> in an extern "C", or use the #include <lua.hpp> equivalent, else you will get linker errors.

// alternately do #include <lua.hpp>
extern "C" {
#include <lua.h>
}

Compiling Lua with longjmp or exceptions

Lua itself is normally compiled under C but may alternately be compiled under C++. If compiled in C, Lua uses [longjmp]'s to implement error handling (lua_error). If compiled in C++, Lua by default uses C++ exceptions. See the declaration of LUAI_THROW in luaconf.h. See also LuaList:2007-10/msg00473.html .

Combining C++ Code using Exceptions with Lua using longjumps

There is a mismatch between C++ exception handling that properly unwinds the stack and call destructors v.s Lua longjmps that merely toss the stack, so more care is needed if Lua is compiled in C to ensure all necessary C++ destructors are called, preventing memory or resource leaks.

When C++ calls Lua as an extension language, the Lua operations often (but not always) need to be wrapped in a pcall to a lua_CFunction. For example, see [PIL 25.2] or PIL2 25.3. (Details on these conditions are given later below by Rici.) It's often the case that this lua_CFunction is used by only one caller. Therefore, it can be useful to make the lua_CFunction local to the calling function (like a closure). In C++, the lua_CFunction can be defined inside a struct like this:

int operate(lua_State * L, std::string & s, int x, int y) {
    std::string msg = "Calling " + s + "\n";  // can raise exception; must be destroyed
    cout << msg;
    // caution: this code by raise exceptions but not longjump.
    struct C {
        static int call(lua_State * L) {
            // caution: this code may longjump but not raise exceptions.

            C * p = static_cast<C*>(lua_touserdata(L, 1));
            assert(lua_checkstack(L, 4));
            lua_getglobal("add"); // can longjump
            assert(lua_isfunction(L, -1));
            lua_pushstring(L, s); // can longjump
            lua_pushnumber(L, p->x);
            lua_pushnumber(L, p->y);
            lua_call(L, 3, 1);    // can longjump
            p->z = lua_tonumber(L, -1); assert(lua_isnumber(L, -1));
            return 0;
        }
        const char * s; int x; int y; int z;
    } p = {s.c_str(), x, y, 0};
    int res = lua_cpcall(L, C::call, &p); // never longjumps
    if (res != 0) {
        handle_error(L);  // do something with the error; can raise exception
        //note: we let handle_error do lua_pop(L, 1);
    }
    return p.z;
}

Now, error handling is a bit tricky at first glance. The lua_getglobal, lua_pushstring, and lua_call calls can generate a lua_error(), i.e. a longjmp if Lua is compiled in C. The lua_cpcall, which is outside the protected call, is safe because it does not generate a lua_error() (unlike using a lua_pushcfunction followed by a lua_pcall, which could lua_error on memory allocation failure). Unlike C++ exception handling, the longjmp will skip any destructors of objects up the stack (often used for RAII in C++).

Another issue is if lua_cpcall returns a failure result, what do we do with it? There is a possibility we could handle the error in-place, lua_pop it, and continue. More often, the error needs to be dealt with at a more shallow point in the call chain. Possibly a better solution is to keep the error message in the Lua stack, making sure to do a lua_pop if consumed in a catch block:

#include <stdexcept>
#include <boost/shared_ptr.hpp>
/**
 * C++ exception class wrapper for Lua error.
 * This can be used to convert the result of a lua_pcall or

 * similar protected Lua C function into a C++ exception.
 * These Lua C functions place the error on the Lua stack.
 * The LuaError class maintains the error on the Lua stack until
 * all copies of the exception are destroyed (after the exception is
 * caught), at which time the Lua error object is popped from the
 * Lua stack.
 * We assume the Lua stack is identical at destruction as
 * it was at construction.
 */
class LuaError : public std::exception
{
private:
    lua_State * m_L;
    // resource for error object on Lua stack (is to be popped
    // when no longer used)
    boost::shared_ptr<lua_State> m_lua_resource;
    LuaError & operator=(const LuaError & other); // prevent
public:
    // Construct using top-most element on Lua stack as error.
    LuaError(lua_State * L);
    LuaError(const LuaError & other);
    ~LuaError();
    virtual const char * what() const throw();
};

    static void
LuaError_lua_resource_delete(lua_State * L)
{
    lua_pop(L, 1);
}

LuaError::LuaError(lua_State * L)
    : m_L(L), m_lua_resource(L, LuaError_lua_resource_delete)
{

}

LuaError::LuaError(const LuaError & other)
    : m_L(other.m_L), m_lua_resource(other.m_lua_resource)
{

}
    const char *
LuaError::what() const throw()
{
    const char * s = lua_tostring(m_L, -1);
    if (s == NULL) s = "unrecognized Lua error";
    return s;
}
LuaError::~LuaError()
{
}

Example usage:

for(int n=1; n < 100; n++) {
  try {
    string s = "123123123123123123";  // note: may throw bad_alloc
    // ...
    int res = lua_cpcall(L, call, NULL);
    if (res != 0) throw LuaError(L);
  }
  catch(exception & e) {
    cout << e.what() << endl;
  }
}

There is also the case if Lua calls a C function that call C++ code that calls Lua code. In such case, the C++ code might pcall into Lua and convert any error message to a C++ exception, which propogates up to the C function. The C function then needs to convert the C++ exception to a lua_error() which longjmps to Lua. This conversion to a C++ exception is only needed if the C++ code in the call chain allocated memory in the RAII fashion.

--DavidManura

Lua C API Functions that Raise Errors

There are quite a number of API functions which will never throw a Lua error. API functions that throw errors are identified in the reference manual as of 5.1.3. First, none of the stack adjustment functions throw errors; this includes lua_pop, lua_gettop, lua_settop, lua_pushvalue, lua_insert, lua_replace and lua_remove. If you provide incorrect indexes to these functions, or you haven't called lua_checkstack, then you're either going to get garbage or a segfault, but not a Lua error.

None of the functions which push atomic data -- lua_pushnumber, lua_pushnil, lua_pushboolean and lua_pushlightuserdata ever throw an error. API functions which push complex objects (strings, tables, closures, threads, full userdata) may throw a memory error. None of the type enquiry functions -- lua_is*, lua_type and lua_typename -- will ever throw an error, and neither will the functions which set/get metatables and environments. lua_rawget, lua_rawgeti and lua_rawequal will also never throw an error. Aside from lua_tostring, none of the lua_to* functions will throw an error, and you can avoid the possibility of lua_tostring throwing an out of memory error by first checking that the object is a string, using lua_type. lua_rawset and lua_rawseti may throw an out of memory error. The functions which may throw arbitrary errors are the ones which may call metamethods; these include all of the non-raw get and set functions, as well as lua_equal and lua_lt.

If you are creating full userdata out of some object which might need to be freed, you should always create the userdata first, with its various slots cleared, and then attach the correct metatable with a __gc metamethod. Then you should create the object which may need to be freed and put it in the userdata. This will avoid resource leaks because the __gc method will eventually be called if an error is subsequently thrown. A good example of this is the standard liolib.c which uses this strategy to avoid leaking file descriptors. -- RiciLake

See Also


RecentChanges · preferences
edit · history
Last edited October 19, 2008 4:18 am GMT (diff)