How to properly create an empty *mut [u8] object input to FFI to modify?

I'm trying to use FFI to generate payload in a dynamic length buffer, and pass the ownership to the caller.

struct CustomAlloc(Global);

unsafe impl Allocator for CustomAlloc {
    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
        println!("allocating");
        self.0.allocate(layout)
    }

    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
        print!("deallocating");
        self.0.deallocate(ptr, layout)
    }
}

// generate a boxed slice with payload, pass ownership to caller and released by called
unsafe fn ffi(ptr: &mut *mut [u8]) {
    println!("in ffi: input ptr is {:p}", *ptr);

    let size = 100usize;
    let mut boxed_slice = Box::new_zeroed_slice_in(size, CustomAlloc(Global));

    boxed_slice.iter_mut().for_each(|i| {
        i.write(15u8);
    });

    let boxed_slice = boxed_slice.assume_init();

    *ptr = Box::into_raw(boxed_slice);
    println!("in ffi: input ptr is {:p}", *ptr);
}

fn unsafe_codes() {
    let mut p_slice: *mut [u8] = unsafe { slice::from_raw_parts_mut(std::ptr::null_mut(), 0) };
    let pp_slice = &mut p_slice;

    unsafe {
        ffi(pp_slice);
    }

    let boxed_slice = unsafe { Box::from_raw_in(p_slice, CustomAlloc(Global)) };

    println!("{:?}", &*boxed_slice);
}

I use Box to transfer the ownership, so that the boxed_slice doesn't need to call another ffi to release the buffer. But the p_slice seems not created in a nice way [ MIRI tells it is UB?].
Is there any better way for this situation?

Update 1:

Maybe passing &mut *mut u8 and &mut usize is a better chioce?

unsafe fn ffi_x(ptr: &mut *mut u8, len: &mut usize) {
    println!("in ffi_x: input ptr is {:p}", *ptr);

    *len = 100usize;

    let mut boxed_slice = Box::new_zeroed_slice_in(*len, CustomAlloc(Global));

    boxed_slice.iter_mut().for_each(|i| {
        i.write(10u8);
    });

    *ptr = Box::into_raw(boxed_slice.assume_init()).as_mut_ptr();

    println!("in ffi: input ptr is {:p}", *ptr);
}

unsafe fn unsafe_codes_x() {
    let mut ptr = ptr::null_mut();
    let pptr = &mut ptr;
    let mut len = 0usize;

    unsafe { ffi_x(pptr, &mut len) }

    let slice = slice::from_raw_parts_mut(ptr, len);

    let boxed_slice = Box::from_raw_in(slice, CustomAlloc(Global));

    println!("{:?}", &*boxed_slice);
}

Miri already told you exactly what the problem is: a reference must never be null. Why aren't you using &mut []?

1 Like

Because the FFI signature is something like StatusCode ffi(uint8_t **pp, size_t *size), the return has been taken.
Then I'm trying to modify to StatusCode ffi(void **pp), to pass a single parameter as a pointer of slice, rather than passing another size_t parameter to write.

That's exactly what my code does. Did you check it?

Do not pass null pointers to from_raw_parts_mut. If you need a dummy pointer for it, then use NonNull::dangling().as_ptr().

2 Likes

In FFI interfaces where you're exposing &T or &mut T arguments you can use Option<&T> or Option<&mut T> instead. This takes up the same amount of space and lets you panic cleanly when you get a null pointer.

2 Likes

Sorry, didn't find out your answer contains a link. It seems perfect for me.
I'd like to know what &mut [] exactly means? Does it create a on stack empty slice object? or create a static slice object and assigns to a mut reference?

Hmm, the expression &mut [] is actually a bit interesting. It gets a static lifetime, so its not just a reference to a stack local. Normally, this kind of thing is called "constant promotion", where references to compile-time constants become 'static since they reference a global. However, constant promotion only happens for immutable references, since modifying the global constant would be a problem.

I wonder if &mut [] is just a special case where constant promotion can happen for mutable references? I tried some other zero-sized types, but I could only get it to work with the empty slice.

1 Like

It is special-cased.

2 Likes