std::exception Interoperability

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
8 messages Options
Reply | Threaded
Open this post in threaded view
|

std::exception Interoperability

Erik Cassel

I have patched Lua 5.1 to seamlessly work with std::exception.

This lets me throw an std::exception-derived object from anywhere in C++
code. Lua will catch it and translate it appropriately in
luaD_rawrunprotected().

Also, Lua uses an std::exception-derived class to throw Lua errors. This
means I can catch errors thrown by Lua (as in lua_touserdata).


The benefits of this approach are:

1) I don't have to write exception handlers in every entry point in my
code. If my code is based on std/boost then I can be confident that
exceptions thrown by my code will be handled by Lua. I don't have to
translate them into lua_error() calls.

2) It is perfectly safe for me to intercept an exception thrown by Lua.
Since Lua now throws std::exception-derived objects I can catch and inspect
errors with a catch(std::exception&) clause. Moreover, if I decide *not* to
re-throw the exception, then Lua automatically pops the error message from
the stack.


Has anybody else done something like this?  Would it be useful to anybody?
Have I missed something?

I'd be happy to post my patch (and go over implementation details) and/or
show some sample code of how it can be used.

-Erik


Reply | Threaded
Open this post in threaded view
|

Re: std::exception Interoperability

D Burgess-4
I use a std::exception scheme in WIndows to handle the MS
defiencies with setjmp/longjmp. I would like to see what you have
done

DB

On 3/25/06, Erik Cassel <[hidden email]> wrote:

>
> I have patched Lua 5.1 to seamlessly work with std::exception.
>
> This lets me throw an std::exception-derived object from anywhere in C++
> code. Lua will catch it and translate it appropriately in
> luaD_rawrunprotected().
>
> Also, Lua uses an std::exception-derived class to throw Lua errors. This
> means I can catch errors thrown by Lua (as in lua_touserdata).
>
>
> The benefits of this approach are:
>
> 1)      I don't have to write exception handlers in every entry point in my
> code. If my code is based on std/boost then I can be confident that
> exceptions thrown by my code will be handled by Lua. I don't have to
> translate them into lua_error() calls.
>
> 2)      It is perfectly safe for me to intercept an exception thrown by Lua.
> Since Lua now throws std::exception-derived objects I can catch and inspect
> errors with a catch(std::exception&) clause. Moreover, if I decide *not* to
> re-throw the exception, then Lua automatically pops the error message from
> the stack.
>
>
> Has anybody else done something like this?  Would it be useful to anybody?
> Have I missed something?
>
> I'd be happy to post my patch (and go over implementation details) and/or
> show some sample code of how it can be used.
>
> -Erik
>
>
>
Reply | Threaded
Open this post in threaded view
|

Re: std::exception Interoperability

Greg Falcon
In reply to this post by Erik Cassel
Any time I see a catch(...), I start worrying, so I'd love to get a
look at your improvements to the exception handling.

Greg F

On 3/24/06, Erik Cassel <[hidden email]> wrote:

>
> I have patched Lua 5.1 to seamlessly work with std::exception.
>
> This lets me throw an std::exception-derived object from anywhere in C++
> code. Lua will catch it and translate it appropriately in
> luaD_rawrunprotected().
>
> Also, Lua uses an std::exception-derived class to throw Lua errors. This
> means I can catch errors thrown by Lua (as in lua_touserdata).
>
>
> The benefits of this approach are:
>
> 1)      I don't have to write exception handlers in every entry point in my
> code. If my code is based on std/boost then I can be confident that
> exceptions thrown by my code will be handled by Lua. I don't have to
> translate them into lua_error() calls.
>
> 2)      It is perfectly safe for me to intercept an exception thrown by Lua.
> Since Lua now throws std::exception-derived objects I can catch and inspect
> errors with a catch(std::exception&) clause. Moreover, if I decide *not* to
> re-throw the exception, then Lua automatically pops the error message from
> the stack.
>
>
> Has anybody else done something like this?  Would it be useful to anybody?
> Have I missed something?
>
> I'd be happy to post my patch (and go over implementation details) and/or
> show some sample code of how it can be used.
>
> -Erik
>
>
>
Reply | Threaded
Open this post in threaded view
|

RE: std::exception Interoperability

Erik Cassel
In reply to this post by D Burgess-4

The patch can be implemented in luaconf.h, but for clarity I will show it
expanded in ldo.c. Afterwards I include a sample function illustrating the
uses of this patch.

First we replace Lua's proprietary exception with a class derived from
std::exception.  Its job is to properly maintain Lua's stack during the
lifetime of the exception. Then, in luaD_rawrunprotected we replace "catch
(...)" with type-specific exception handlers.



// ************************************************************
// Exception definition

class lua_exception : public std::exception
{
private:
        bool committed; // == luaD_rawrunprotected has handled the exception
        struct lua_longjmp *const errorJmp;

public:
        struct lua_State* const L;
       
        lua_exception(struct lua_State *L, struct lua_longjmp *errorJmp);
        ~lua_exception();
       
        /*override*/ const char *what() const;
       
        // To be called only by luaD_rawrunprotected:
        void commit() { committed = true; }
};

lua_exception::lua_exception(struct lua_State *L, struct lua_longjmp
*errorJmp)
        :L(L)
        ,errorJmp(errorJmp)
        ,committed(false)
{
}

const char *lua_exception::what() const
{
        // Look! You can inspect the error message using a standard call!
        return lua_tostring(L, -1);
}

lua_exception::~lua_exception()
{
        if (!committed)
        {
                // The exception was caught before Lua got it.
                // Revert the error state
                // TODO: Is it safe to set it to 0 or should we
                // restore it to a previous state?
                errorJmp->status = 0;
                // Pop the error message
                lua_pop(L, 1);
        }
}


// ************************************************************
// Throwing exceptions

#define LUAI_THROW(L,c) throw(lua_exception(L,c))
#define luai_jmpbuf int  /* dummy type */


// ***********************************************************
// Exception handling

int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) {
  struct lua_longjmp lj;
  lj.status = 0;
  lj.previous = L->errorJmp;  /* chain new error handler */
  L->errorJmp = &lj;


#if defined(__cplusplus)
        try
        {
                (*f)(L, ud);
        }
        catch (lua_exception& e)
        {
                // lua_error was called somewhere and the
                // error message is on the stack. Commit the
                // lua_exception so that it doesn't take the
                // error message off of the stack
                e.commit();

                // Is this necessary?
                if (lj.status == 0)
                        lj.status = -1;
        }
        catch (std::exception const& e)
        {
                // We caught a conventional exception.
                // Convert it into a lua_exception that can then
                // be passed on to Lua.
               
                // If I were comfortable
                // with the inner workings of lua_error() I could
                // avoid this extra try/catch block and just set
                // all the proper Lua states myself. However, this
                // code is reasonably future-proof.
                try
                {
                        lua_pushstring(L, e.what());
                        lua_error(L);
                }
                catch (lua_exception& e)
                {
                        e.commit();
                        // Is this necessary?
                        if (lj.status == 0)
                                lj.status = -1;
                }

        }

#else
  LUAI_TRY(L, &lj,
    (*f)(L, ud);
  );
#endif
  L->errorJmp = lj.previous;  /* restore old error handler */
  return lj.status;
}


// Done
// ***********************************************************************



Here is some pseudo-code:


int myFunction(lua_State *thread)
{
        int n = lua_gettop(thread);
        if (n!=7)
                // Lua will catch this exception and push the message
                // onto the Lua stack
                throw std::runtime_error("Expected 7 arguments");

        try
        {
                // This could throw a Lua exception
                luaL_argcheck (thread, 3, 6, "foo");                
               
                // This could throw a bad_cast exception
                Bar* bar = boost::polymorphic_cast<Bar*>(getFoo());
        }
        catch (std::exception& e)
        {
                // You can inspect the error message
                fprintf(stderr, e.what());

                // It is safe to throw or not throw e
                if (!myHandleIt(e))
                        throw e;
        }
        return 0;
}



-Erik




-----Original Message-----
From: [hidden email]
[mailto:[hidden email]] On Behalf Of D Burgess
Sent: Friday, March 24, 2006 2:26 PM
To: Lua list
Subject: Re: std::exception Interoperability

I use a std::exception scheme in WIndows to handle the MS
defiencies with setjmp/longjmp. I would like to see what you have
done

DB

On 3/25/06, Erik Cassel <[hidden email]> wrote:

>
> I have patched Lua 5.1 to seamlessly work with std::exception.
>
> This lets me throw an std::exception-derived object from anywhere in C++
> code. Lua will catch it and translate it appropriately in
> luaD_rawrunprotected().
>
> Also, Lua uses an std::exception-derived class to throw Lua errors. This
> means I can catch errors thrown by Lua (as in lua_touserdata).
>
>
> The benefits of this approach are:
>
> 1)      I don't have to write exception handlers in every entry point in
my
> code. If my code is based on std/boost then I can be confident that
> exceptions thrown by my code will be handled by Lua. I don't have to
> translate them into lua_error() calls.
>
> 2)      It is perfectly safe for me to intercept an exception thrown by
Lua.
> Since Lua now throws std::exception-derived objects I can catch and
inspect
> errors with a catch(std::exception&) clause. Moreover, if I decide *not*
to

> re-throw the exception, then Lua automatically pops the error message from
> the stack.
>
>
> Has anybody else done something like this?  Would it be useful to anybody?
> Have I missed something?
>
> I'd be happy to post my patch (and go over implementation details) and/or
> show some sample code of how it can be used.
>
> -Erik
>
>
>

Reply | Threaded
Open this post in threaded view
|

RE: std::exception Interoperability

Erik Cassel
In reply to this post by Greg Falcon

> Any time I see a catch(...), I start worrying

Agreed. If you look at the 5.1 code you'll see that Lua throws a
lua_longjmp*.  Therefore, I think that Lua should only catch a lua_longjmp*
and not a "...".  Any other exception thrown within luaD_rawrunprotected
should be considered a programming error.

As an alternative to my more generic patch (posted earlier), here is a very
small patch (untested) that I think addresses the (...) issue only:

#define LUAI_TRY(L,c,a) \
        try
        {
                a
        }
        catch(lua_longjmp*)
        {
                if ((c)->status == 0)
                        (c)->status = -1;
        }
        catch(...)
        {
                // Serious design error. Implementers are required to
                // throw *only* lua_longjmp*.

                // At this point we have no idea if the Lua stack has
                // an error message on it, and we don't know if (c)->status
                // is valid. All bets are off.

                // Pick your action  :-)
                assert(false);
                exit(-1);
                throw;
        }
       


-Erik




-----Original Message-----
From: [hidden email]
[mailto:[hidden email]] On Behalf Of Greg Falcon
Sent: Friday, March 24, 2006 2:56 PM
To: Lua list
Subject: Re: std::exception Interoperability

Any time I see a catch(...), I start worrying, so I'd love to get a
look at your improvements to the exception handling.

Greg F

On 3/24/06, Erik Cassel <[hidden email]> wrote:

>
> I have patched Lua 5.1 to seamlessly work with std::exception.
>
> This lets me throw an std::exception-derived object from anywhere in C++
> code. Lua will catch it and translate it appropriately in
> luaD_rawrunprotected().
>
> Also, Lua uses an std::exception-derived class to throw Lua errors. This
> means I can catch errors thrown by Lua (as in lua_touserdata).
>
>
> The benefits of this approach are:
>
> 1)      I don't have to write exception handlers in every entry point in
my
> code. If my code is based on std/boost then I can be confident that
> exceptions thrown by my code will be handled by Lua. I don't have to
> translate them into lua_error() calls.
>
> 2)      It is perfectly safe for me to intercept an exception thrown by
Lua.
> Since Lua now throws std::exception-derived objects I can catch and
inspect
> errors with a catch(std::exception&) clause. Moreover, if I decide *not*
to

> re-throw the exception, then Lua automatically pops the error message from
> the stack.
>
>
> Has anybody else done something like this?  Would it be useful to anybody?
> Have I missed something?
>
> I'd be happy to post my patch (and go over implementation details) and/or
> show some sample code of how it can be used.
>
> -Erik
>
>
>

Reply | Threaded
Open this post in threaded view
|

Re: std::exception Interoperability

Greg Falcon
In reply to this post by Erik Cassel
On 3/25/06, Erik Cassel <[hidden email]> wrote:
>#define LUAI_THROW(L,c) throw(lua_exception(L,c))
...snip...

> lua_exception::~lua_exception()
> {
>         if (!committed)
>         {
>                 // The exception was caught before Lua got it.
>                 // Revert the error state
>                 // TODO: Is it safe to set it to 0 or should we
>                 // restore it to a previous state?
>                 errorJmp->status = 0;
>                 // Pop the error message
>                 lua_pop(L, 1);
>         }
> }

There's unfortunately a problem with the C++ here.  Paragraphs 3 and 4
of section 15.1 of the C++ spec cover it.

The problem has to do with your LUAI_THROW's explicitly generated
temporary object, and the rules of the lifetime of thrown objects.
Execution of the line

throw(lua_exception(L,c))

can cause the following sequence of events:
1. A temporary lua_exception is explicitly constructed
2. The exception mechanism generates its own temporary lua_exception.
It is constructed by copying step #1's temporary object (using
lua_exception's implicit copy c'tor)
3. Code jumps to the exception handler.  The temporary lua_exception
from step 1 goes out of scope, its d'tor is called, and since that
temporary was never commit()ed, an object is incorrectly popped from
the stack.

The reason why you didn't run into this problem is because the
compiler is allowed to optimize this away, combining the two temporary
objects into one.  From the spec, 15.1 paragraph 5:

"If the use of the [throw-expression-generated] temporary object can
be eliminated without changing the meaning of the program except for
the execution of constructors and destructors associated with the use
of the temporary object, then the exception in the handler can be
initialized directly with the argument of the throw expression."

This optimization is allowed but not required, so a
standards-compliant C++ compiler would be justified in running your
d'tor twice, once for each temporary, resulting in an extra item being
popped off the stack.

There's no completely pleasing solution to this problem.  You can't
hide the implicit copy constructor, because thrown objects need to be
copyable.  The best I can think of is to give class lua_exception
transfer-of-ownership semantics, sort of like auto_ptr:

lua_exception::lua_exception(const lua_exception& that)
       :L(that.L)
       ,errorJmp(that.errorJmp)
       ,committed(that.false)
{
  that.commit();
}

and mark member variable committed as mutable.

It's a little scary, in the way that auto_ptr is scary: making a copy
of a lua_exception object changes the behavior of that object.  If
users of this patch (as altered) treat lua_exception as an off-limits
implementation class, and not as a class to use for their own
purposes, then this problem can be avoided.

Greg F
Reply | Threaded
Open this post in threaded view
|

Re: std::exception Interoperability

Greg Falcon
On 3/27/06, Greg Falcon <[hidden email]> wrote:
> lua_exception::lua_exception(const lua_exception& that)
>        :L(that.L)
>        ,errorJmp(that.errorJmp)
>        ,committed(that.false)
> {
>   that.commit();
> }

I meant to say
        ,committed(that.committed)
of course.

Greg F
Reply | Threaded
Open this post in threaded view
|

RE: std::exception Interoperability

Erik Cassel
In reply to this post by Greg Falcon

Greg,

Thanks for finding this bug!  I made a classic C++ mistake.

I agree that a properly crafted transfer-of-ownership constructor should do
the trick.  But yes, you have to accept that a lot of "auto" stuff goes on
under the hood.

I agree that the implementation should be hidden, or some constructors and
fields should be declared private, etc.  

-Erik


-----Original Message-----
From: [hidden email]
[mailto:[hidden email]] On Behalf Of Greg Falcon
Sent: Monday, March 27, 2006 12:42 PM
To: Lua list
Subject: Re: std::exception Interoperability

On 3/25/06, Erik Cassel <[hidden email]> wrote:
>#define LUAI_THROW(L,c) throw(lua_exception(L,c))
...snip...

> lua_exception::~lua_exception()
> {
>         if (!committed)
>         {
>                 // The exception was caught before Lua got it.
>                 // Revert the error state
>                 // TODO: Is it safe to set it to 0 or should we
>                 // restore it to a previous state?
>                 errorJmp->status = 0;
>                 // Pop the error message
>                 lua_pop(L, 1);
>         }
> }

There's unfortunately a problem with the C++ here.  Paragraphs 3 and 4
of section 15.1 of the C++ spec cover it.

The problem has to do with your LUAI_THROW's explicitly generated
temporary object, and the rules of the lifetime of thrown objects.
Execution of the line

throw(lua_exception(L,c))

can cause the following sequence of events:
1. A temporary lua_exception is explicitly constructed
2. The exception mechanism generates its own temporary lua_exception.
It is constructed by copying step #1's temporary object (using
lua_exception's implicit copy c'tor)
3. Code jumps to the exception handler.  The temporary lua_exception
from step 1 goes out of scope, its d'tor is called, and since that
temporary was never commit()ed, an object is incorrectly popped from
the stack.

The reason why you didn't run into this problem is because the
compiler is allowed to optimize this away, combining the two temporary
objects into one.  From the spec, 15.1 paragraph 5:

"If the use of the [throw-expression-generated] temporary object can
be eliminated without changing the meaning of the program except for
the execution of constructors and destructors associated with the use
of the temporary object, then the exception in the handler can be
initialized directly with the argument of the throw expression."

This optimization is allowed but not required, so a
standards-compliant C++ compiler would be justified in running your
d'tor twice, once for each temporary, resulting in an extra item being
popped off the stack.

There's no completely pleasing solution to this problem.  You can't
hide the implicit copy constructor, because thrown objects need to be
copyable.  The best I can think of is to give class lua_exception
transfer-of-ownership semantics, sort of like auto_ptr:

lua_exception::lua_exception(const lua_exception& that)
       :L(that.L)
       ,errorJmp(that.errorJmp)
       ,committed(that.false)
{
  that.commit();
}

and mark member variable committed as mutable.

It's a little scary, in the way that auto_ptr is scary: making a copy
of a lua_exception object changes the behavior of that object.  If
users of this patch (as altered) treat lua_exception as an off-limits
implementation class, and not as a class to use for their own
purposes, then this problem can be avoided.

Greg F