A work-around exception implementation for VisIt ================================================ Motivation: =========== VisIt must work properly on systems with buggy compilers/operating systems that prevent standard C++ exceptions from being caught properly when thrown from a shared library. After failing to get VisIt to build and run using other compilers on the afflicted platforms, the only option was to explore an alternative to exception handling. Support for exceptions has been questioned in the past and lead to the development of the EXCEPTION0, EXCEPTION1, ... macros that are used to throw exceptions. Those macros make it easy to remove the throwing of exceptions from VisIt. Doing so, however, would render useless much exception handling code that is used to alter the program control flow during an exception. Now we have gone a step further and replaced all C++ exception keywords with macros that allow an alternative implementation of exceptions that is based on setjmp/jongjmp that will work on platforms with poor exception support. Information: ============ Code developed now and in the future should use the new macros so we can continue to support buggy platforms without having to remove exceptions from the code. It is worth noting that using these macros, in the normal case, produces exactly the same code that we had before the switch to macros. They are only necessary to convert the exception control structures to other control structures when we're building the code using the setjmp/longjmp scheme. Care has been taken to make the macros as close to real C++ exception handling keywords as possible to make this solution more palatable to everyone working on VisIt. It is important that C++ exceptions not be used instead of the macros because the macros remove ALL C++ style exception handling in favor of a setjmp/longjmp mechanism. This means that C++ exceptions are no longer caught and throwing a C++ style exception when the code is built with simulated exceptions will cause an unhandled C++ exception. Fortunately, this has not been an issue since all exceptions that we handle are based on the VisItException base class and others types of exceptions to not seem to crop up. All aspects of standard C++ exceptions are supported so we can pretty much do exceptions as usual. This includes multiple catch blocks, exception handling based on type, nested try blocks, propagation of unhandled exceptions, rethrowing of exceptions, etc. All of the former properties of exception handling are provided for free by the macro implementation except for the handling based on type which requires the exception name to be placed in a table of known exception names. Keep that in mind when adding new exception types. A limitation of this approach is that it does not "unwind the stack". Unwinding the stack means that as an exception is being passed up the call stack, it calls the destructors of objects that are created in each function along the way. This is not done as we use longjmp to jump directly to the last TRY statement before the exception. This will, of course, lead to possible memory leaks but we may be able to abandon this work-around exception implementation in the future when the bad platforms are fixed. Macros: ======= Keyword Macro(s) Macro Explanation try TRY The macro makes a call to setjmp and puts the jump buffer returned by setjmp on the exception stack so there is a place we can jump back to when an exception is thrown. ENDTRY This macro must be placed immediately after the last CATCH block. It is responsible for rethrowing uncaught exceptions and popping the exception stack if the exception was handled. catch CATCH(e) Used in place of catch when there is no named exception argument but we have an exception type. Example: CATCH(VisItException) catch CATCH2(e,n) Used in place of catch when there is an exception type and a named exception argument. This is used when you want to use the exception object. Example: CATCH(VisItException, e) catch CATCHALL(...) Used in place of catch where all exceptions are to be caught. It is handled differently from CATCH because it needs a special macro expansion. throw EXCEPTION0(e) Used in place of throw where we are throwing throw EXCEPTION1(e,a) an object. This this macro jumps back to the throw EXCEPTION2(e,a,b) last TRY that put on the exception stack. throw EXCEPTION3(e,a,b,c) throw RETHROW Used in place of throw with no object. This re-throws the last exception object. If there is no exception object, the code aborts. return CATCH_RETURN(n) Use this when you want to use a return statement from inside a catch block. The n value indicates the number of nested try statements that precede call to return. It is IMPERATIVE that this macro is used if you plan on returning from a catch block. return CATCH_RETURN2(n,v) Used when you want to use a return statement that returns a value from inside a catch block. The n value indicates the number of nested try statements that precede the call to return. It is IMPERATIVE that this macro is used if you plan on returning from a catch block. Examples: ========= // Multiple catch blocks are supported. TRY { // do work } CATCH(PipelineException) { // handle } CATCH2(VisItException, e) { cerr << "message: " << e.GetMessage() << endl; } CATCHALL(...) { // handle } ENDTRY // Nested try blocks and exception "inheritance" supported TRY { TRY { // do work } CATCH(PipelineException) { // handle then rethrow // rethrow supported RETHROW; } ENDTRY // more work } CATCH2(VisItException, foo) { // catch all VisItExceptions and derived types. } ENDTRY // Returning from a catch block void foo() { TRY { // do work } CATCHALL(...) { // handle it // return from the catch block. The macro argument 1 indicates // the nesting level of TRY statements where the return is located. CATCH_RETURN(1); } }