rustlanguage-lawyer

Access to local variable via a raw pointer


Consider the API for accessing some memory mapped registers:

struct Register {
    address: usize,
}

impl Register {

    const fn new(address: usize) -> Self {
        Self { address }
    }

    unsafe fn read(&self) -> u32 {
        unsafe { std::ptr::read_volatile(self.address as *const u32) }
    }

    unsafe fn write(&mut self, value: u32) {
        unsafe { std::ptr::write_volatile(self.address as *mut u32, value) };
    }
}

As far as I understand this interface is fine as long as Register is constructed from addresses that are disjoint from the memory space controlled by the Rust program and are considered to have "exposed provenance".

However, if we want to write a unit test without specific underlying hardware, we could do something like this:


#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    pub fn my_test() {
        let mut local_var: u32 = 45;
        let addr = (&mut local_var) as *mut u32;
        let mut reg: Register = Register::new(addr as usize);

        unsafe {
            assert_eq!(reg.read(), 45);
            reg.write(124);
            assert_eq!(reg.read(), 124);
        }
    }
}

But the problem is obviously that local_var is not a disjoint memory, and I have a strong feeling that this test is UB, but I need to pinpoint exactly what is being violated here. Also, is there a proper and well defined way to write a similar test?


Solution

  • As written, this test is fine; there are few conditions it should meet, and it obeys all of them.

    1. There should not be overlapping references to the memory.

      I.e. if you read/write concurrently with Register, you must make sure both Register and the test use only raw pointers.

      Note that creating references to MMIO is problematic anyway; see e.g. here and here.

    2. The pointer's provenance has been exposed.

      Using an as cast is fine, but it's better to use expose_provenance().

    3. The address-to-pointer cast should use the exposed provenance.

      Again, using as is fine, but it's better to use with_exposed_provenance().