Search code examples
rustunsafe

Dereferencing a *const Mutex panics


I am currently working on a safe binding of a FFI library, and this is a minimal implementation of the problem I have encountered.

fn test_weird_thing() {
    use std::sync::Mutex;
                
    struct WeirdThing<'a> {
        vec: Mutex<Vec<i32>>,
        callback_runner: &'a CallbackRunner,
    }

    // Hypothetical FFI Struct///
    struct CallbackRunner {
        callback: extern "C" fn(*const Mutex<Vec<i32>>),
        data: *const Mutex<Vec<i32>>,
    }

    impl CallbackRunner {
        extern "C" fn default_callback(_vec: *const Mutex<Vec<i32>>) {}

        fn new() -> Self {
            CallbackRunner {
                callback: Self::default_callback,
                data: std::ptr::null(),
            }
        }

        fn set_callback(
            &mut self, callback: extern "C" fn(*const Mutex<Vec<i32>>), 
            data: *const Mutex<Vec<i32>>
        ) { 
            self.data = data;
            self.callback = callback;
        }

        fn run(&self) {
            (self.callback)(self.data);
        }
    }
    /////////////////////////////

    impl<'a> WeirdThing<'a> {
        fn new(runner: &'a mut CallbackRunner) -> Self {
            let vec = Mutex::new(Vec::new());
            runner.set_callback(Self::callback, &vec as *const Mutex<Vec<i32>>);
            WeirdThing { vec, callback_runner: runner }
        }

        extern "C" fn callback(vec: *const Mutex<Vec<i32>>) {
            let vec = unsafe { &*vec };
            let mut vec = vec.lock().unwrap();
            vec.push(1);
        }

        fn do_thing(&self) -> Vec<i32> {
            self.callback_runner.run();
            self.vec.lock().unwrap().clone()
        }
    }

    let mut runner = CallbackRunner::new();
    let thing = WeirdThing::new(&mut runner);
    debug!("Thing: {:?}", thing.do_thing());
}

I have thought that thing.vec should be valid when runner.run() is called, since it is called when thing is still alive, but the code panics.

How should I explain this behavior?


Solution

  • thing is valid when you call the callback, the problem is that both adding vec to the struct as well as returning it from the function does move it, in other words it can be at another memory location than the one callback_runner.data points to.

    Since you're dereferencing a dangling pointer your code contains UB, lucky for you you got an error instead of a silent failure somewhen down the line.

    To get around that you have to make sure vec isn't moved around in memory after you create the pointer to it, for example by putting it on the heap with a (pinned) Box.

    I've lined out in this answer how to create a pointer to something you create within the function you should be able to apply it to your problem.