# How Exception Handling Works in mulle-objc Runtime: Try-Catch-Finally Implementation

> Explore mulle-objc runtime's try catch finally implementation. Discover how vector tables and setjmp longjmp enable robust exception handling in Objective-C.

- Repository: [mulle-objc/mulle-objc-runtime](https://github.com/mulle-objc/mulle-objc-runtime)
- Tags: internals
- Published: 2026-03-07

---

**The mulle-objc runtime implements Objective-C exception handling through a universe-level vector table that dispatches try-catch-finally operations via setjmp/longjmp and function pointers stored in `struct _mulle_objc_universe`.**

The mulle-objc runtime provides a low-level implementation of Objective-C exception handling that diverges from traditional unwinding-based approaches. Instead of using stack unwinding libraries like libunwind, the runtime manages control flow through explicit jump buffers and a configurable vector table stored in each universe. This design allows embedded and specialized environments to customize exception behavior while maintaining full compatibility with Objective-C syntax.

## The Universe-Level Exception Vector Table

At the core of **exception handling in mulle-objc runtime** lies the `exceptionvectors` structure within `struct _mulle_objc_universe`. Defined in [`src/mulle-objc-universe-struct.h`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-universe-struct.h), this structure contains function pointers that dispatch the five essential operations required by compiler-generated exception code:

- **`try_enter`**: Pushes a new catch buffer onto the thread-local exception stack
- **`throw`**: Delivers an exception to the topmost catch buffer and performs a `longjmp`
- **`try_exit`**: Pops the current catch buffer when leaving the `@try` scope
- **`extract`**: Retrieves the exception object stored in the current catch buffer
- **`match`**: Tests whether a caught exception matches a given class for `@catch` filtering

These vectors are initialized by `_mulle_objc_universe_init_exception` in [`src/mulle-objc-universe-exception.c`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-universe-exception.c), allowing the runtime to bootstrap the exception handling mechanism before any Objective-C code executes.

## Entering a @try Block

When the compiler encounters a `@try` statement, it allocates a **catch buffer** (`struct _mulle_objc_exceptionstackentry`) on the stack. This structure contains a `jmp_buf` for saving execution context plus metadata fields. The runtime then calls `mulle_objc_exception_tryenter` in [`src/mulle-objc-try-catch-finally.c`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-try-catch-finally.c), which forwards to the universe's `try_enter` vector:

```c
void mulle_objc_exception_tryenter(void *localExceptionData,
                                   mulle_objc_universeid_t universe)
{
    struct _mulle_objc_universe *universePtr =
        mulle_objc_global_get_universe_inline(universe);
    universePtr->exceptionvectors.try_enter(universePtr, localExceptionData);
}

```

The concrete implementation `_mulle_objc_universe_tryenter` in [`src/mulle-objc-universe-exception.c`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-universe-exception.c) stores the previous stack top and clears the exception field in the buffer. The compiler pairs this call with `setjmp(catchbuf.buf)` to save the execution context before entering the protected code region.

## Throwing Exceptions via the throw Vector

When `@throw` executes, `mulle_objc_exception_throw` resolves the target universe and invokes the `throw` vector:

```c
void mulle_objc_exception_throw(void *exception,
                                mulle_objc_universeid_t universe)
{
    struct _mulle_objc_universe *universePtr =
        mulle_objc_global_get_universe_inline(universe);
    universePtr->exceptionvectors.throw(universePtr, exception);
}

```

The implementation `_mulle_objc_universe_throw` in [`src/mulle-objc-universe-exception.c`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-universe-exception.c) retrieves the current catch buffer from thread-local storage, stores the exception object within it, pops the buffer from the stack, and performs a `longjmp` to transfer control back to the corresponding `setjmp` site established at the beginning of the `@try` block.

## Catching and Class Matching

After the `longjmp` returns execution to the `setjmp` location, the runtime calls `mulle_objc_exception_extract` to retrieve the thrown object from the catch buffer. The compiler then uses `mulle_objc_exception_match` to determine if the exception matches any `@catch` clause:

```c
void *mulle_objc_exception_extract(void *localExceptionData,
                                   mulle_objc_universeid_t universe);

```

The match operation forwards to the universe's `match` vector (`_mulle_objc_universe_match_exception`), which traverses the class hierarchy of the caught object to verify if it is an instance of the requested exception class. This enables polymorphic catch blocks where a `@catch (NSException *e)` will catch any subclass of `NSException`.

## Exiting Scope and @finally Semantics

When control exits a `@try` block—whether normally, through a `@catch` handler, via a control flow statement, or during stack unwinding—the compiler emits a call to `mulle_objc_exception_tryexit`. This function invokes the `try_exit` vector to pop the current catch buffer from the thread-local exception stack, ensuring that nested exception handlers maintain correct nesting semantics.

The `@finally` block is handled entirely by the compiler front-end (clang/LLVM), not by the runtime vectors. The compiler generates code that executes the `@finally` block both after successful `@try` completion and after `@catch` handling, placing this logic between the catch handler and the `mulle_objc_exception_tryexit` call. The runtime guarantees that `try_exit` only executes after any finally-style cleanup completes.

## Practical Example: Manual Try-Catch-Finally

The following example demonstrates the low-level API that the compiler uses to implement `@try/@catch/@finally` syntax:

```c
#include "mulle-objc-try-catch-finally.h"
#include "mulle-objc-universe.h"
#include <setjmp.h>
#include <stdio.h>

int main(void)
{
    mulle_objc_universeid_t uni = mulle_objc_global_universeid();
    struct _mulle_objc_exceptionstackentry catchbuf;

    /* Enter try region */
    mulle_objc_exception_tryenter(&catchbuf, uni);
    if (setjmp(catchbuf.buf) == 0)
    {
        printf("Inside try block – about to throw\n");
        mulle_objc_exception_throw((void *)0xDEADBEEF, uni);
        printf("Never reached\n");
    }
    else
    {
        void *ex = mulle_objc_exception_extract(&catchbuf, uni);
        printf("Caught exception %p\n", ex);
    }

    /* Exit try region (where @finally would run) */
    mulle_objc_exception_tryexit(&catchbuf, uni);
    printf("After try/catch – cleanup done\n");
    return 0;
}

```

For class-specific catching, the runtime provides `mulle_objc_exception_match` to filter exception types against the class hierarchy:

```c
#define MY_ERROR_CLASSID   (mulle_objc_global_classid("MyError"))
#define NS_EXCEPTION_CLASSID (MULLE_OBJC_CLASSID(0xa41284db))

void handle_exception(void *exception, mulle_objc_universeid_t uni)
{
    if (mulle_objc_exception_match(exception, uni, MY_ERROR_CLASSID))
        printf("Handled MyError\n");
    else if (mulle_objc_exception_match(exception, uni, NS_EXCEPTION_CLASSID))
        printf("Handled NSException\n");
    else
        printf("Unhandled exception %p\n", exception);
}

```

## Summary

- **Exception handling in mulle-objc runtime** operates through a configurable vector table (`exceptionvectors`) stored in each universe structure in [`src/mulle-objc-universe-struct.h`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-universe-struct.h)
- The `try_enter` and `try_exit` vectors manage a thread-local stack of `jmp_buf` catch buffers, with `throw` using `longjmp` for control transfer
- Exception objects are stored in `struct _mulle_objc_exceptionstackentry` buffers and retrieved via `mulle_objc_exception_extract`
- Class matching for `@catch` clauses uses the `match` vector in [`src/mulle-objc-universe-exception.c`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-universe-exception.c) to walk the inheritance hierarchy
- `@finally` semantics are implemented by the compiler, not the runtime, ensuring cleanup code executes before `mulle_objc_exception_tryexit` pops the exception frame

## Frequently Asked Questions

### How does mulle-objc runtime store exception state during a try block?

The runtime stores exception state in a `struct _mulle_objc_exceptionstackentry` allocated on the stack by the compiler. This structure contains a `jmp_buf` for saving the execution context and a field for the thrown exception object. When `mulle_objc_exception_tryenter` is called, the runtime links this buffer to the thread-local exception stack via the `try_enter` vector implemented in [`src/mulle-objc-universe-exception.c`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-universe-exception.c).

### What mechanism does mulle-objc use instead of stack unwinding?

Rather than using libunwind or similar stack unwinding libraries, mulle-objc uses **setjmp/longjmp** for exception handling. When an exception is thrown, the `throw` vector (implemented in [`src/mulle-objc-universe-exception.c`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-universe-exception.c)) performs a `longjmp` back to the `setjmp` site established at the beginning of the `@try` block. This approach provides deterministic control flow suitable for embedded systems and specialized environments.

### How does the runtime determine if a caught exception matches a catch clause?

The runtime uses `mulle_objc_exception_match` to test whether the caught exception is an instance of the class specified in a `@catch` clause. This function forwards to the universe's `match` vector (`_mulle_objc_universe_match_exception`), which traverses the class hierarchy of the thrown object to check for inheritance relationships, enabling polymorphic exception catching.

### Is @finally handled by the runtime exception vectors?

No, the `@finally` block is **not** handled by the runtime exception vectors. According to the mulle-objc implementation, the compiler (clang/LLVM) generates code to execute the `@finally` block both after normal `@try` completion and after `@catch` handling. The runtime only manages the `try_exit` vector to pop the exception frame after all finally-style cleanup has occurred.