Search code examples
c++exceptionluaabortluabind

luabind abort when trying to call object method with lua errors


I've used the example from http://www.rasterbar.com/products/luabind/docs.html#deriving-in-lua to define a class in c++ that I can derive from in lua:

class base
{
public:
    base(const char* s)
    { std::cout << s << "\n"; }

    virtual void f(int a)
    { std::cout << "f(" << a << ")\n"; }
};

struct base_wrapper : base, luabind::wrap_base
{
    base_wrapper(const char* s)
        : base(s)
    {}

    virtual void f(int a)
    {
        call<void>("f", a);
    }

    static void default_f(base* ptr, int a)
    {
        return ptr->base::f(a);
    }
};

...

module(L)
[
    class_<base, base_wrapper>("base")
        .def(constructor<const char*>())
        .def("f", &base::f, &base_wrapper::default_f)
];

I've then created a derived class in lua:

class 'base_derived' (base)

function base_derived:__init(str)
    base.__init(self,str)
end

function base_derived:f()
    this_function_doesnt_exist()
end

Any call to 'f' is supposed to throw a lua error, which works fine if I do it in lua:

local x = base_derived("Test")
x:f() -- Throws "attempt to call a nil value..." error

I'd like to do the equivalent of that, but in c++:

auto g = luabind::globals(l);
auto r = g["base_derived"];
if(r)
{
    luabind::object o = r("Test");
    auto gm = luabind::object_cast<base_wrapper*>(o);
    if(gm != nullptr)
    {
        try
        {
            luabind::call_member<void>(o,"f",5);
        }
        catch(luabind::error &e)
        {
            std::cout<<"[LUA] Error: "<<e.what()<<std::endl;
        }
    }
    o.push(l);
}

However the 'luabind::call_member'-call causes an abort in 'luabind/detail/call_member.hpp', line 258:

// Code snippet of luabind/detail/call_member.hpp
~proxy_member_void_caller()
{
    if (m_called) return;

    m_called = true;

    // don't count the function and self-reference
    // since those will be popped by pcall
    int top = lua_gettop(L) - 2;

    // pcall will pop the function and self reference
    // and all the parameters

    push_args_from_tuple<1>::apply(L, m_args);
    if (pcall(L, boost::tuples::length<Tuple>::value + 1, 0))
    {
        assert(lua_gettop(L) == top + 1);
#ifndef LUABIND_NO_EXCEPTIONS
////////////////////////////////////////////
        throw luabind::error(L); // LINE 258
////////////////////////////////////////////
#else
        error_callback_fun e = get_error_callback();
        if (e) e(L);

        assert(0 && "the lua function threw an error and exceptions are disabled."
            "If you want to handle this error use luabind::set_error_callback()");
        std::terminate();
#endif
    }
    // pops the return values from the function
    stack_pop pop(L, lua_gettop(L) - top);
}

The exception in that line isn't actually thrown, but it is what causes the abort.

However, the abort only happens if the lua-functions causes a lua error. If I comment the 'this_function_doesnt_exist()'-call, both the lua- and c++-versions run just fine.

Why is the 'throw luabind::error(L);' causing an abort and what can I do to safely call the function from c++ even with potential lua errors?

// Edit: This is the call stack at the time of the abort (When 'luabind::call_member(o,"f",5);' is called):

>   vcruntime140d.dll!__CxxFrameHandler(EHExceptionRecord * pExcept, unsigned __int64 RN, _CONTEXT * pContext, _xDISPATCHER_CONTEXT * pDC) Line 213 C++
    ntdll.dll!RtlpExecuteHandlerForException()  Unknown
    ntdll.dll!RtlDispatchException()    Unknown
    ntdll.dll!KiUserExceptionDispatch() Unknown
    KernelBase.dll!RaiseException() Unknown
    vcruntime140d.dll!_CxxThrowException(void * pExceptionObject, const _s__ThrowInfo * pThrowInfo) Line 136    C++
    server.dll!luabind::detail::proxy_member_void_caller<boost::tuples::tuple<int const * __ptr64,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type> >::~proxy_member_void_caller<boost::tuples::tuple<int const * __ptr64,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type> >() Line 258    C++

And this is the abort message: enter image description here


Solution

  • See line 254:

    if (pcall(L, boost::tuples::length<Tuple>::value + 1, 0))
    

    Here, pcall is asking lua to execute your x:f(5) equivalent call. Apparently this code returns an error because pcall() returns something different than zero. This is expected because you are indeed creating an error in lua by calling this_function_doesnt_exist(). This also explains why the abort does not happen when you comment the this_function_doesnt_exist() call.

    Then, to the C++ code:

         throw luabind::error(L);
    

    This error is thrown from a destructor: ~proxy_member_void_caller(), and it turns out that throwing an exception from a destructor is bad practice. This problem is known for luabind (see this question) to trigger the call of abort without even throwing.

    The solution is to add noexcept(false)to the signature of ~proxy_member_void_caller(), like this:

    ~proxy_member_void_caller() noexcept(false)