Skip to content
This repository has been archived by the owner on Jun 6, 2021. It is now read-only.

Note: Failure and Unwinding

Alex Rønne Petersen edited this page Jun 9, 2013 · 15 revisions

'Normal' failure can occur in five places in Flect code:

  • When an assert fails.
  • When an array bounds check fails.
  • When a null pointer check fails.
  • When a division by zero is attempted.
  • When an allocation fails due to lack of memory.
  • When an explicit call to core::main::error is made.

In these cases, we want to print a stack trace and gracefully exit.

First a few C bindings:

priv type jmp_buf = [uint .. /* N */];

priv fn ext "cdecl" setjmp(env : *jmp_buf) -> i32;
priv fn ext "cdecl" longjmp(env : *jmp_buf, value : i32) -> unit;
priv fn ext "cdecl" exit(int exit_code) -> !;

The compiler generates a C main like this:

extern void flect_entry_point(int argc, char **argv) __attribute__((noreturn));

void main(int argc, char **argv) __attribute__((noreturn)) {
    flect_entry_point(argc, argv);
}

And an actual entry point (the one the user wrote):

void flect_main() {
    // ...
}

A function is defined in the core library that is called when an error occurs:

priv glob mut sjlj_env : jmp_buf;

priv struct ErrorLocation {
    file : str;
    line : u32;
    column : u32;
    module : str;
    function : str;
}

priv enum ErrorKind : u8 {
    FailedAssert = 0;
    InvalidIndex = 1;
    NullPointer = 2;
    DivideByZero = 3;
    OutOfMemory = 4;
    UserError = 5;
}

priv fn ext "cdecl" flect_error(loc : ErrorLocation, kind : ErrorKind, msg : str) -> ! {
    // Assume some printing of the error and a stack trace...

    // Transfer control to flect_entry_point (see below).
    longjmp(&sjlj_env, 1);
}

The flect_entry_point function would look like:

priv glob mut exit_code : i32 = 0;
priv glob mut args : &[str] = @[];

// Assume some public functions to get/set exit_code and args...

priv fn ext "cdecl" flect_main() -> unit;

priv fn ext "cdecl" flect_entry_point(argc : uint, argv : **u8) -> unit {
    // Assume some setting of args...

    let jmp = setjmp(&sjlj_env);

    if jmp == 0 {
        // We're starting up.
        flect_main();

        // If nothing went wrong, we'll get here and exit normally.
        exit(exit_code);
    } else {
        // An error was raised.
        exit(core::u8::max);
    };
}

This is a fairly simple approach that only relies on setjmp/longjmp being present in the C library we're using.

Of course, in the freestanding target, none of the above would be applicable at all. The compiler would assume that the programmer implements flect_error somewhere and somehow, but that's about it.

It's worth noting that when failure occurs, destructors are not called. The reason for this is that failure is meant to be absolutely fatal - think C's abort function. It would also be impractical to run destructors if we're out of memory.

Lastly, the definition of the core::main::error macro:

pub macro error(msg) {
    let loc = new ErrorLocation {
        file = macro "file",
        line = macro "line",
        column = macro "column",
        module = macro "module",
        function = macro "function"
    };

    flect_error(loc, ErrorKind.UserError, msg);
}

This is just a convenient way to raise a fatal error.

Clone this wiki locally