#control-flow #jump #setjmp #interface #access #llvm #sigsetjmp

cee-scape

The cee-scape crate provides Rust access to setjmp and sigsetjmp functionality

8 releases

0.2.0 Sep 28, 2023
0.1.6 Aug 10, 2023

#353 in Configuration

Download history 669/week @ 2024-08-03 857/week @ 2024-08-10 683/week @ 2024-08-17 1449/week @ 2024-08-24 1000/week @ 2024-08-31 1600/week @ 2024-09-07 1665/week @ 2024-09-14 1838/week @ 2024-09-21 1947/week @ 2024-09-28 2532/week @ 2024-10-05 3349/week @ 2024-10-12 3306/week @ 2024-10-19 3838/week @ 2024-10-26 4915/week @ 2024-11-02 8345/week @ 2024-11-09 6115/week @ 2024-11-16

23,963 downloads per month
Used in 6 crates (via pgrx-pg-sys)

MIT/Apache

41KB
469 lines

cee-scape

The cee-scape crate provides Rust access to setjmp and sigsetjmp functionality, via an interface that ensures LLVM won't miscompile things.

Example

You might imagine you have some C code that looks like this (vastly over simplified):

#include <setjmp.h>
#include <stdint.h>

uint32_t subtract_but_longjmp_if_underflow(jmp_buf env, uint32_t a, uint32_t b) {
    if (b > a) {
        longjmp(env, b - a);
    }
    return a - b;
}

If you want to call out to that C code from Rust, you need to establish a jump environment (the jmp_buf env parameter); but Rust does not provide setjmp.

Here's how you might use this crate to solve your problem:

use cee_scape::call_with_setjmp;

// This invocation passes parameters that follow normal control flow.
assert_eq!(call_with_setjmp(|env| {
    (unsafe {
        subtract_but_longjmp_if_underflow(env, 10, 3)
    }) as c_int
}), 7);

// This invocation passes parameters that cause a non-local jump.
assert_eq!(call_with_setjmp(|env| {
    unsafe {
        subtract_but_longjmp_if_underflow(env, 3, 10);
        panic!("should never get here.");
    }
}), 7);

Why not just add setjmp itself as a function?

There has been significant discussion of that question amongst the Rust project.

See in particular rust-lang/libc PR 1216 and rust-lang/rfcs Issue 2625.

The answer is multifaceted.

First, adding support for calling setjmp would require adding extra support in the Rust compiler. Namely, support for marking functions as potentially returning multiple times, e.g. via a returns_twice attribute as discussed in rust-lang/rfcs Issue 2625.

Second, calls to the setjmp function would interact in strange ways with the Rust borrow checker; unless the borrow checker were extended to understand the meaning of a returns_twice attribute, it would invite immediate unsound behavior, such as moving an owned object multiple times from the same stack slot. This could cause the same value to be dropped multiple times (which would be unsound).

Third, the setjmp function itself only has well-defined behavior in very specific contexts according to the C standard (again discussed in rust-lang/rfcs Issue 2625); even just let x = setjmp(...) would be undefined behavior in Rust.

The methods offered by cee-scape, such as call_with_setjmp, side-step the above issues by limiting the use of setjmp to a specific coding pattern:

call_with_setjmp(|env| { ... })

Within the dynamic extent of an invocation to call_with_setjmp, one can either return normally, or via a longjmp to the given jump environment env (which causes a returns from the call_with_setjmp invocation). Either way, the outer call returns at most once. The given jump environment is only usable within that dynamic extent (and Rust's lifetime rules help enforce that constraint).

Why is it called cee-scape

Its a pun: C's jump environments are also known as "escape continuations". This crate enables C escapes.

Dependencies

~0.4–325KB