Catching Lua Exceptions

lua-users home
wiki

By: KevinBaca

Lua Version: 5.0 (VersionNotice)

This article presents a set of patches that facilitate lua exception handling in C and C++.

Introduction

An exception generated in a Lua script has two possible outcomes:

1. It propagates to the top of the call chain. The Lua runtime panics and typically exits the program.

2. It stops at a point in the call chain where a protected call has been made. The function making the protected call can then handle the exception.

Lua's C API provides the function lua_pcall() for making protected calls [1].

Lua's Implementation

When a program "throws" an exception it is making a "non-local jump". It is jumping to a point in the code that may not be in the same block, the same function, or even the same module. The Lua manual calls the destination of these non-local jumps "Recover Points".

Because the Lua runtime is written in C it cannot use C++ exceptions, so Lua implements this behavior using the C functions setjmp() and longjmp() [2].

Note: As of Lua 5.1, when compiled as C++, Lua uses native C++ exceptions instead of setjmp() and longjmp().

Problems with setjmp()/longjmp()

The setjmp()/longjmp() idiom is very efficient and easy to use. It is used in many C libraries to provide exception handling support. It is not, however, without its problems.

setjmp() stores the current state of the CPU's execution environment (registers, stack pointers, etc) to a buffer. Calling longjmp() with that buffer restores the saved execution environment. Essentially, longjmp() "time travels" back to the setjmp() call. The program continues execution from that point with its execution environment appearing as if it hasn't changed.

jmp_buf jb;

char* mystr = "Original Value";

/* start */
int i_except = setjmp( jb );

if( 0 == i_except ) /* try */
{
    mystr = "New Value";

    /* Throw an exception.
       Jumps back to "start", and setjmp() returns 33
     */
    longjmp( jb, 33 );
}
else /* catch */
{
    /* Handle the exception */
}

print( mystr );

Many compilers will optimize references to local variables by caching their values in registers. This is fine when code execution is sequential. If the compiler runs out of registers it can store the value to memory and load it later. A call to longjmp(), however, restores the state of registers saved by setjmp(). If the compiler has cached a variable's value in a register and its value changes after setjmp() but before longjmp(), its new value will be lost when longjmp() restores its old value.

In the example above the variable mystr is susceptible to this problem. Depending on how the compiler has optimized the code, when print() is called mayvar may contain "New Value", or it may contain "Original Value".

-- Actually, the context of the setjmp invocation in the example above is non-conforming and the resulting behaviour is undefined. The example below is OK in this respect. -- Wim Couwenberg

This problem can be mitigated with careful programming. The solutions are:

1. Don't change local variables between calls to setjmp()/longjmp().

2. Variables declared outside the setjmp()/longjmp() block should be declared volatile.

In the example above, declaring mystr as volatile will prevent the compiler from caching its value to a register:

char* volatile mystr;

Declare a volatile int:

volatile int myint;

Note that function arguments must be included in the set of volatile variables:

int func( int* volatile pInt ) /* pointer declared volatile */
{
    jmp_buf jb;

    if( 0 == setjmp( jb ) )
    {
        pInt++;
        longjmp( jb, 97 );
    }
    else
    {
    }
}


RecentChanges · preferences
edit · history
Last edited July 3, 2009 8:00 am GMT (diff)