10

Example code below

The Rust part:

#[no_mangle]
pub extern fn call_c_function(value: i32, fun: fn(i32) -> i32) -> i32 {
    fun(value)
}

And the C part:

int32_t call_c_function(int32_t value, int32_t (*fun)(int32_t));

int32_t triple(int32_t x)
{
    return x*3;
}

int main(int argc, char *argv[])
{
    int32_t value = 3;
    int32_t result = call_c_function(value, triple);

    printf("%d tripled is %d\n", value, result);

    call_c_function(0, NULL);  // Crash here

    return EXIT_SUCCESS;
}

Of course second call of call_c_function will crash. Rust compiler will not complain about unsafe code inside call_c_function, because from rust point of view this code is safe. Also it's not allowed to simply write:

if !fun.is_null() {
    fun(value)
}

because fun type is fn(i32) -> i32 (it's not a pointer).

So my question is, how I can protect call_c_function against NULL pointer dereference? Is there any way to check if callback passed from C is not valid?

Maybe I have to change call_c_function definition?

Dietrich Epp
  • 205,541
  • 37
  • 345
  • 415
ldanko
  • 557
  • 8
  • 20
  • 1
    Yes, just check if fun == NULL. Or zero, if NULL is not a thing in rust. Mind you, I don't know rust very well, but that's how you would do it in C. – rost0031 Jul 17 '15 at 17:08
  • 2
    See here: http://doc.rust-lang.org/book/ffi.html#the-%22nullable-pointer-optimization%22 – Adrian Jul 17 '15 at 17:18
  • @Adrian Thx, that's what I was looking for! – ldanko Jul 17 '15 at 19:59

2 Answers2

16

You can use Option<...> to represent nullable function pointers. It is incorrect to have a NULL value for a value of type fn(...) so the Option wrapper is required for cases like this.

For example,

#[no_mangle]
pub extern "C" fn call_c_function(value: i32, fun: Option<fn(i32) -> i32>) -> i32 {
    if let Some(f) = fun {
        f(value)
    }
}

However, there's one extra point: fun is a C function, but the type fn(...) is a Rust function. They're not directly compatible (e.g. their calling conventions differ). One needs to use the extern "C" fn(...) (a.k.a. extern fn(...)) type when interacting with C function pointers:

#[no_mangle]
pub extern "C" fn call_c_function(value: i32, fun: Option<extern "C" fn(i32) -> i32>) -> i32 {
    if let Some(f) = fun {
        f(value)
    }
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
huon
  • 94,605
  • 21
  • 231
  • 225
  • Wrapping the function pointer argument in `Option<...>` does not seem to work with cbindgen. The generated header file will contain a structure type instead of a function pointer. – blerontin Mar 24 '23 at 13:20
-1

You can compare a pointer that has been generated by unsafe code against std::ptr::null()

e.g.

let pw = libc::getpwnam(username.as_ptr() as *const i8);
if std::ptr::null() != pw ...

null() on Linux is 0 as *const T I'm not sure if that is universal.

teknopaul
  • 6,505
  • 2
  • 30
  • 24
  • 2
    This does not work with *function* pointers, which are non-nullable, as huon's answer explains. – trent Jan 06 '20 at 13:52