Mutating `&T` reference with unsafe should be undefined behaviour

I was writing a toy Rc by dereferencing an &T and unsafely mutating the counter, like so:

impl<T> MyRC<T>
where
    T: 'static
{
	pub fn clone_rc(&self) -> Self {
		unsafe {(*self.inner).count += 1;}
        Self {inner: self.inner}
    }
}

// and yes, I probably don't need the T: 'static bound

On reading UnsafeCell's documentation, it seems to state that breaking an &T guarantees without the use of UnsafeCell is undefined behavior.

However, I ran cargo +nightly miri run with this main.rs:

struct Noisy(u32);

impl Drop for Noisy {
	fn drop(&mut self) {
		println!("Dropping!");
	}
}

fn main () {
	let n = MyRC::new(Noisy(3));
	{
		let _ = n.clone_rc();
	}
	drop(n)	
}

And there wasn't any output from Miri.

So is this even undefined behaviour? If this is, how would I find out?

How is MyRc defined? The declaration of whatever’s stored in MyRc.inner would also be helpful.

What @2e71828 said, but assuming you have a *mut InnerRc<T> and a usize in that, yes.

Try running Miri against something like this.

    std::thread::scope(|s| {
        for _ in 0..100 {
            s.spawn(|| { let _d = myrc.clone_rc(); });
        }
    });
1 Like

It seems like self.inner is a raw pointer. The immutability guarantee doesn't apply "through" pointers. The raw pointer itself determines whether you can modify what it points at, and not whether you used a &T or &mut T to get to the raw pointer itself.

7 Likes

Sorry for forgetting to include MyRC's definition

use std::ops::Deref;

struct Shared<T>
where
	T: 'static
{
    val: T,
    count: u32,
}

pub struct MyRC<T>
where
    T: 'static
{
	inner: *mut Shared<T>
}

impl<T> MyRC<T>
where
    T: 'static
{
    pub fn new(val: T) -> Self {
        let ptr: *mut Shared<T> = Box::leak(Box::new(Shared {
			val,
			count: 1,
		}));

        MyRC {
			inner: ptr
        }
    }

	pub fn clone_rc(&self) -> Self {
		unsafe {(*self.inner).count += 1;}
        Self {inner: self.inner}
    }
}

impl<T> Deref for MyRC<T> {
	type Target = T;
	fn deref(&self) -> &Self::Target {
		unsafe {&(*self.inner).val}
	}
}

impl<T> Drop for MyRC<T> {
	fn drop(&mut self) {
		unsafe {
			(*self.inner).count -= 1;
			if (*self.inner).count == 0 {
				drop(Box::from_raw(self.inner))
			}
		}
	}
}

Mutating that data, for example through an alias or by transmuting an &T into an &mut T, is considered undefined behavior. (from the UnsafeCell documentation)

But what you're saying is that &T as *mut T is fine? Particularly in the case that I'm storing and writing to it as a *mut T, not converting an &T to *mut T at time of write?

You can safely convert a &T to a *const T, then convert the *const T to a *mut T. However, it is undefined behavior to actually write to that *mut T, or to convert it to a &mut T.

What @alice is saying is that you can dereference a &*mut T (i.e., an immutable reference to a mutable raw pointer) to obtain a *mut T, then write to that *mut T, assuming that you are otherwise allowed to write to it. This also applies to &MyStruct, where MyStruct contains *mut T.

3 Likes

Your code performs a read from a &(*mut T) to get a *mut T value. This is allowed because the *mut T type is Copy.

That is not the same as performing a pointer cast to go from &T to *const T or *mut T.

3 Likes

I get it now. Thanks!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.