The design of the exception system for C and Objective-C. Written by Niels Möller 27/1 1996. Contents ~~~~~~~~ 1. Design goals 2. Basic framework 3. The design of the interface 4. C facilities 5. Catch and throw for Objective-C 6. Exceptions 7. NSException 8. Compatibility 9. Todo 1. Design goals ~~~~~~~~~~~~~~~ (i) Functionality similar to catch, throw and unwindprotect in Lisp for the C and Objective-C languages. (ii) An exception mechanism for Objective-C. (iii) NSException (the libobjects implementation written by Adam Fedor) built on top of this. (iv) Compatibility: as far as possible, catches and throws from C-code, d:o from Objective-C code, exceptions and NSExceptions should work together. 2. Basic framework ~~~~~~~~~~~~~~~~~~ I use a stack (more precisely, an obstack) for keeping track of different kind of "frames", associated with catch tags and cleanup actions. For actually jumping to another frame, standard longjmp()/setjmp() is used. So far the following types of frames exist: frstack_ccatch, // Catch tag for the CCATCH and CTHROW // C macros. frstack_catch_object, // Catch tag for Objective-C facilities, // CATCH, TRY, NS_HANDLER frstack_cleanup_fn0, // Various kinds of cleanup actions, frstack_cleanup_fn1, // frstack_cleanup_jmp is perhaps the frstack_cleanup_object, // most useful. frstack_cleanup_jmp, Usually, this stack should be kept in sync with the execution stack; that is, a function that pushes a new frame onto the stack should remove it before it returns. This requirement comes from the fact that longjmp() is used to jump from one frame to another. It may be possible to make use of the frame stack without following this rule, but than you must make sure that you don't longjmp() into the void. 3. The design of the interface ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ I had some difficulties when deciding how the interface to the frame stack should be designed. There are two main alternatives: first the NextStep style, for example NS_DURING body NS_HANDLER handler NS_ENDHANLDER The other style is perhaps influenced from Lisp. I use C-macros taking blocks as their arguments, like CPROTECT(tag, { body }, { cleanup }); I decided in favour of the second style. One of the reasons is that I have several facilities for jumps and exceptions, not just one like in NextStep, and I think that the fewer macro names to invent and remember, the better. 4. C facilities ~~~~~~~~~~~~~~~ For C programs, there are three macros, CCATCH, CTHROW and CPROTECT, analogous to catch, throw and unwind-protect in Lisp. CCATCH(TAG, BODY) is superficially similar to setjmp() . BODY should be a block to be executed. During the execution of BODY, a CTHROW to TAG can pass control directly back to the CCATCH. Values are as for setjmp() and longjmp(): CCATCH returns 0 if no CTHROW happened. A CTHROW can pass a non-zero integer back, which will then be the value of the CCATCH. CPROTECT(BODY, CLEANUP) is the difference between CCATCH() and setjmp(). This statement lets you execute BODY, with the guarantee that when BODY terminates, because of a throw, an exception (which can not however be raised from within a C function), or simply because it falls through to the bottom. Don't use exit(), return, break etc within the CPROTECT. See ccatch.h for exact usage of these macros. 5. Catch and throw for Objective-C ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The Catch class The catch and throw facilities for Objective-C is similar to the C macros described above. The differences are that the tags are objects, and that arbitrary objects, not just non-zero integers, can be returned from a throw. #include id tag = [[Catch alloc] init] // Creation. if (0!=CATCH(tag, { // Prepare for catching. body // Value is 1 of a throw happened, })) // otherwise 0. { // A throw put us here, [tag value]; // this extracts the value } // passed with throw To pass control to the CATCH, use [tag throw: (id) value] The UnwindProtect class This is even more similar to the C version, but it offers more flexibility, as several kinds of cleanup actions are possible. The easy way to use it is almost like with the C version UNWINDPROTECT(BODY,CLEANUP) . 6. Exceptions ~~~~~~~~~~~~~ Exceptions are arranged in a hierarchy, where each kind of exception is represented by a protocol (one reason to use protocols, not classes, is that it is nice to have multiple "inheritance" here). A start of the classification hierarchy is AnyException AnyError StorageError MemoryError ThrowError Exceptions that are not errors (that is, doesn't conform to the AnyError protocol) are considered warnings, and are ignored if they are not caught. In contrast, an uncaught error abort()s with a cor dump. A particular exception is an object of a class that conforms to one or more of the above protocols. Examples of such classes is OutOfMemnory and ThrowWithoutCatch. Useful superclasses when defining new exceptions are Exception, Error and Simple Error, of which the latter two conforms to AnyError. An exception is raised by sending it the -raise message. This method doesn't *usually* return, but it can return if the exception is a warning, not an error, and there is no handler for it. To catch exceptions, use the TRY macro. #include #include TRY({ BODY }, // Only exceptions of type EXCEPTIONTYPE will be caught @protocol(EXCEPTIONTYPE), ERROR, { // Handler, the exception that occured is bound to the // variable ERROR HANDLER }); All exceptions respond to -message, which returns a C string pointer to a description of the exception. When finished with the exception, send it the -finished message. This usually frees the exception object, but some exceptions are kind of read-only, and don't want to be freed. They can then simply override -finished. Exception specific methods should be specified in the corresponding protocols. A few exceptions can be raised from within this package. If a -throw message is sent to a Catch object, for which no corresponding frame can be found in the frame stack (that is, there's no active CATCH involving that object), ThrowWithoutCatch is raised. Also, OutOfMemory can be raised. This is not well tested, but the goal is that this package, and in the future the rest of the libobjc and libobjects libraries should be robust in low memory situations. I have included a version of xmalloc that raises an exception if malloc fails, instead of just abort()ing. And I try to recover gracefully and raise an exception if memory allocation fails when trying to push new frames onto the frame stack. 7. NSException ~~~~~~~~~~~~~~ I have adapted Adam Fedors NSException implementation to use the frame stack. This implementation should be equivalent to the former. 8. Compatibility ~~~~~~~~~~~~~~~~ As far as possible, different kinds of throws and exceptions should work together. Most importantly, cleanup actions should be invoked when the body of for example a UNWINDPROTECT() is terminated, whatever the cause of the termination may be. However, there are a few limitations: (i) Don't use ordinary longjmp() together with this facilities, unless you *really* know what you are doing. Use CATCH or CCATCH instead. (ii) NS_HANDLER won't be invoked for any exception but NSException. This is because the handlers expect a variable to be bound to the NSException that occured, and this is simply not possible. And similarly, TRY won't catch NSExceptions. (iii) [technical] Invoking cleanup actions by sending a UnwindProtect object the -cleanup message won't invoke cleanup actions corresponding to a frstack_cleanup_jmp_frame. The reason is that after longjmp()ing to the cleanup there's no way to return to the correct place. That's all, I hope. And the implementation should be portable, as it is built on standard setjmp(), longjmp() and GNU obstacks. However, the interface macros use extensions of the GNU C Compiler. 9. Todo ~~~~~~~ (*) I have attempted to follow the convention in libobjects to have NSObject as root class. But I'm not too familiar with the NextStep conventions for allocating and freeing objects, and therefore my classes implement the -free method. This should be fixed. (*) Invent another cleanup action, and corresponding frame type, that holds a pointer to a pointer to an id, and a message to be sent on cleanup, provided the id is not nil. Then you could make local variables free themselves automagically: { // Whether this block terminatess normally or by a throw or exception, // foo and tag will be freed properly. id foo = nil; id tag = [[UnwindProtect alloc]] init]; [tag cleanupBySending: @selector(free) toObjectPointedToBy: &foo]; ... Some code that may cause an exception ... [tag cleanup]; }