🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Luabind - 'abort' when trying to call object method which contains lua errors

Started by
1 comment, last by Silverlan 8 years, 8 months ago

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?

Advertisement
Are you absolutely certain LUABIND_NO_EXCEPTIONS is not defined? If the line is definitely reached, is it possible the error happens because something bad happens when luabind::error tries to read extra information from the Lua state? I believe the default behavior of Lua is to call exit when it needs to raise an error (see also Lua's lua_atpanic.

Additionally, if you try to throw an exception out of any function which is marked as noexcept you will get terminate called. Note that none of the things I listed actually call abort(), but it might depend on your exact compiler.

The only thing I can think of which explicitly calls abort() would be a failed assert. With a decent debugger you should be able to find the exact line where things go wrong.

Additionally, if you try to throw an exception out of any function which is marked as noexcept you will get terminate called. Note that none of the things I listed actually call abort(), but it might depend on your exact compiler.

Actually, the solution was to add noexcept to the destructor. Someone on stack overflow posted this idea, I'll just paste his response here for better visibility, in case anyone else has the same problem:

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][1]. This problem is known for luabind ([see this question][2]) 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)


[1]: http://stackoverflow.com/questions/130117/throwing-exceptions-out-of-a-destructor
[2]: http://stackoverflow.com/questions/23574323/why-cant-i-catch-a-luabinderror-exception-when-my-lua-code-throws-an-error

This topic is closed to new replies.

Advertisement