Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Safety of casting arbitrary types to usize

I wrote some code. It works... but is it safe?

use std::mem;
use std::ptr;
use std::marker::PhantomData;

struct Atomic<T: Copy>(AtomicUsize, PhantomData<T>);

impl<T: Copy> Atomic<T> {
    unsafe fn encode(src: T) -> usize {
        assert!(mem::size_of::<T>() <= mem::size_of::<usize>());

        let mut dst = 0;
        ptr::write(&mut dst as *mut usize as *mut T, src);
        dst
    }

    unsafe fn decode(src: usize) -> T {
        assert!(mem::size_of::<T>() <= mem::size_of::<usize>());
        ptr::read(&src as *const usize as *const T)
    }

    fn new(val: T) -> Atomic<T> {
        unsafe {
            Atomic(AtomicUsize::new(Self::encode(val)), PhantomData)
        }
    }

    fn load(&self, order: Ordering) -> T {
        unsafe { Self::decode(self.0.load(order)) }
    }

    fn store(&self, val: T, order: Ordering) {
        unsafe { self.0.store(Self::encode(val), order) }
    }
}

impl<T: Copy + Default> Default for Atomic<T> {
    fn default() -> Atomic<T> {
        Self::new(T::default())
    }
}

As you can see, I write an arbitrary Copy value of small enough size into a usize, and ship it around in an Atomic. I then read it out as a new value.

In essence I use the usize as a memory block of size size_of::<usize>().

If this is safe, the next step is to consider fancier operations.

unsafe trait PackedInt {}
unsafe impl PackedInt for u8  {}
unsafe impl PackedInt for i8  {}
unsafe impl PackedInt for u32 {}
unsafe impl PackedInt for i32 {}
unsafe impl PackedInt for u64 {}
unsafe impl PackedInt for i64 {}

impl<T: Copy + PackedInt> Atomic<T> {
    fn compare_and_swap(&self, current: T, new: T, order: Ordering) -> T {
        unsafe {
            Self::decode(self.0.compare_and_swap(
                Self::encode(current),
                Self::encode(new),
                order
            ))
        }
    }

    fn fetch_add(&self, val: T, order: Ordering) -> T {
        unsafe {
            Self::decode(self.0.fetch_add(Self::encode(val), order))
        }
    }

    fn fetch_sub(&self, val: T, order: Ordering) -> T {
        unsafe {
            Self::decode(self.0.fetch_sub(Self::encode(val), order))
        }
    }
}

These are of course not always particularly sensible on overflow (since two "equal" values could compare unequal due to bits outside of the T), but they still seem well-defined... I think.

So, is this safe, and why?

like image 371
Veedrac Avatar asked Oct 30 '22 18:10

Veedrac


1 Answers

It's almost safe... but not quite. You're probably only thinking about people using Atomic with integers and floats, but references are also Copy. A user could easily cause a crash using relaxed loads and stores on an Atomic<&&u32>.

On a side-note, your fetch_add and fetch_sub won't work correctly on big-endian systems.

like image 78
Eli Friedman Avatar answered Nov 09 '22 15:11

Eli Friedman