Why does a rustdoc test with unsafe code fail but the same code in an integration test passes?

478 Views Asked by At

I am trying to write a rustdoc test of a method called insert. The testing function is called in the last line of the test, and when I comment it out the test passes just fine.

Error message:

$ cargo test
   Compiling reproduce v0.1.0 
(file:///home/user/reproduce)                                                           
    Finished dev [unoptimized + debuginfo] target(s) in 2.03s
     Running target/debug/deps/reproduce-17ad4bb50aa9c47e

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

   Doc-tests reproduce

running 1 test
test src/lib.rs - MyStruct::method (line 19) ... FAILED

failures:

failures:
    src/lib.rs - MyStruct::method (line 19)

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out

error: test failed, to rerun pass '--doc'

Code:

impl MyStruct {
    pub fn new(lsb: u8, step: usize) -> MyStruct {
        let mask: u8 = match lsb {
            1 => 0b0000_0001,
            2 => 0b0000_0011,
            _ => 0b0000_1111
        };

        MyStruct { lsb, mask, step }
    }

    /// # Examples
    /// ```
    /// extern crate reproduce;
    /// extern crate rgb;
    ///
    /// use std::slice;
    /// use rgb::RGB;
    ///
    /// let msg = "This is a secret message".as_bytes();
    /// let img = vec![RGB {r: 0, g: 0, b: 0}; 800*500];
    ///
    /// // Create a reference to the bytes of img
    /// let p: *const Vec<RGB<u8>> = &img;
    /// let p: *const u8 = p as *const u8;
    /// let p: &[u8] = unsafe { 
    ///     slice::from_raw_parts(p, 3*800*500)
    /// };
    ///
    /// let settings = reproduce::MyStruct::new(2, 12);
    /// let new_data = settings.method(p, &msg);
    /// ```
    pub fn method(&self, img: &[u8], msg: &[u8]) -> Vec<u8> {
        let mut ret = img.to_vec();
        let mut n = 0;

        for ch in msg.iter() {
            for i in 1..=8/self.lsb {
                let shift = (self.lsb*i) as u32;
                ret[n] = (ret[n] & !self.mask) |
                    (ch & self.mask.rotate_right(shift)).rotate_left(shift);
                n += self.step;
            }
        }

        ret
    }
}

Could it be that there are special rules for a rustdoc test to pass (for example the use of assert! macro to prove that it returns a proper value)?

I am asking because my method passes integration test with very similar code, so I am pretty confident it's correct

1

There are 1 best solutions below

1
On BEST ANSWER

This has nothing to do with the fact that it's running in a rustdoc test. Your code is exhibiting Undefined Behaviour because it is unsound. Running the same code outside of a test also panics for me.

You are making assumptions about how memory is laid out - assumptions that are in no way backed up by any compiler guarantees. A "correct" way to get the pointer would be this:

let p = img.as_ptr() as *const u8;

However, while this removes the assumption on how the Vec is laid out, the rest of the code is still assuming the layout of RGB. This change stops it panicking for me, but I can't be sure that it is working correctly and I can't know if it will break on some other computer, or on mine after the next Rust update.

I am asking because my method passes integration test with very similar code, so I am pretty confident it's correct

This is exactly the danger of Undefined Behaviour, and it's why unsafe should be used only in code that is not only well tested but also where the assumptions and guarantees are well understood.