"Memory Safe Context Switching: longjmp, setjmp and ucontext in Fil-C"
C's setjmp and longjmp are famously dangerous — they save a register snapshot and let you teleport back up the call stack, bypassing normal function returns. They "return twice," they dodge destructors, and if you misuse them, they silently corrupt memory. Fil-C, a project by Filip Pizlo (of JavaScriptCore fame), aims to make C and C++ memory-safe while preserving full compatibility with existing code — including the gnarly bits like setjmp/longjmp and the ucontext coroutine APIs.
What Makes setjmp/longjmp So Dangerous
setjmp saves only the callee-save registers and the stack pointer — not the stack contents themselves. If you call setjmp in a function and then return from that function before calling longjmp, the saved context is pointing at a stack frame that no longer exists. The result: execution on a dangling stack, producing crashes that don't even print a proper stack trace.
Even when used correctly, setjmp creates subtle problems for compilers. The compiler must know that setjmp "returns twice" to avoid reusing spill slots unsoundly. If you obfuscate the call (e.g., through a function pointer), GCC might still get it right, Clang might print garbage, and Fil-C's compiler (filcc) will refuse to compile the code at all — a deliberate design choice.
How Fil-C Solves It
Fil-C takes a three-pronged approach:
- Opaque jmpbuf. The
jmpbufin Fil-C doesn't contain raw register values — it holds a pointer to an opaquezjmpbufobject that only the runtime can manipulate. Code that inspectsjmpbufinternals will cause a panic rather than a silent corruption.
- Compiler enforcement. Fil-C's compiler (
filcc) ensures that any call tosetjmpis recognized as the real function, not a pointer-based alias. This guarantees the spill-slot optimizations are correctly inhibited, preventing the compiler from silently breakinglongjmpcorrectness.
- Stack lifetime tracking. Fil-C tracks which stack frames are still valid. If you try to
longjmpto a context whose stack has been freed or reused, the runtime panics with a clear error instead of executing on corrupted memory.
The ucontext APIs
The ucontext family — getcontext, setcontext, makecontext, swapcontext — is even more treacherous. Used for coroutines and fibers (e.g., Boost's fiber implementation), these APIs let you create contexts on arbitrary stacks and switch between them at will. In Yolo-C (Filip's term for conventional C), freeing a stack that a context points to produces a dangling-stack exploit. Fil-C prevents this entirely: either the context switch succeeds safely or the program panics before corruption occurs.
Support for the full ucontext API landed in Fil-C release 0.680, which means Fil-C now supports the coroutine patterns that many systems-level libraries rely on, all while maintaining its memory safety guarantees.
Why This Matters
As AI and systems programming push C and C++ back into the spotlight, the industry is desperate for memory safety without a complete rewrite. Fil-C's approach — full compatibility, source-level safety, no garbage collector — shows that even the darkest corners of C (and there are few darker than setjmp/longjmp) can be tamed. If Fil-C can make longjmp safe, it can make anything safe.