foreign-types icon indicating copy to clipboard operation
foreign-types copied to clipboard

as_ptr()'s use of *mut is misleading

Open kornelski opened this issue 2 years ago • 10 comments

as_ptr() returns a *mut _ pointer. This isn't an error by itself, but under Rust's memory model, this pointer is derived from shared/immutable &self, and therefore doesn't have permission to mutate self, so the mut of the pointer is misleading.

https://github.com/sfackler/foreign-types/blob/393f6ab5a5dc66b8a8e2d6d880b1ff80b6a7edc2/foreign-types-shared/src/lib.rs#L89

I assume this is for convenience, because C APIs aren't diligent about const vs mut distinction. However, this is a gotcha, because C APIs that take *mut may actually mutate the object, and that is UB from Rust's perspective.

Could you add .as_mut_ptr() that takes &mut self to provide a pointer safe for mutation?

kornelski avatar Nov 28 '23 21:11 kornelski

The structure of Opaque is designed to prevent mutation through the *mut from being UB: https://github.com/sfackler/foreign-types/blob/393f6ab5a5dc66b8a8e2d6d880b1ff80b6a7edc2/foreign-types-shared/src/lib.rs#L14.

Even for C APIs that are const-correct, C's notion of constness is distinct enough from Rust's that & <-> *const and &mut <-> *mut doesn't really work.

sfackler avatar Nov 28 '23 21:11 sfackler

I've developed a version of Miri that can execute foreign functions by interpreting LLVM bytecode, and it found a Tree Borrows violation related to mutating through self.as_ptr(). Writing through the mutable pointer created from self.as_ptr() is considered undefined behavior under this model, since this pointer is given a Frozen access tag as a child tag of the implicit &self parameter to as_ptr.

Here's a minimal example that can recreate the error with an unmodified version of Miri, without the FFI. In particular, this error is triggered when mutating through self.as_ptr() within a method that receives &mut self, where self is an instance of ForeignTypeRef.

impl ExampleRef {
    fn invalid_mutation(&mut self) {
        let raw_ptr: *mut ExampleInner = self.as_ptr();
        unsafe {
            (*raw_ptr).inner = 1;
        }
    }
}

Also, I was a bit unsure about what you meant here:

C's notion of constness is distinct enough from Rust's that & <-> *const and &mut <-> *mut doesn't really work.

Are you saying that the compiler's treatment of &T differs enough from * const in C (even under a const-correct API) that this borrowing violation isn't actually problematic in practice?

icmccorm avatar Nov 30 '23 15:11 icmccorm

The &self is a pointer to UnsafeCell (semantically through a PhantomData). Why is it being tagged as Frozen?

sfackler avatar Nov 30 '23 15:11 sfackler

Could it be because UnsafeCell in PhantomData has zero size, and doesn't exist in memory? If it was Opaque(UnsafeCell<ActualForeignSizedType>) then it would map to an address space.

kornelski avatar Nov 30 '23 16:11 kornelski

Yeah, I think there's a difference between PhantomData<UnsafeCell<*mut ()>> and UnsafeCell<PhantomData<*mut ()>>, with only the latter registering as using interior mutability

madsmtm avatar Nov 30 '23 16:11 madsmtm

I'll note again: rustc currently treats UnsafeCell differently whether it's inside PhantomData or not! See this playground, click "Show LLVM IR", and inspect the result; the function taking &PhantomData<UnsafeCell<*mut ()>> gets marked with noalias readonly, while &UnsafeCell<PhantomData<*mut ()>> gets marked with readnone.

If you add extern types to the mix, you'll see the exact same LLVM IR as for &UnsafeCell<PhantomData<*mut ()>> (LLVM will even merge the functions because of that).

madsmtm avatar Jan 16 '24 03:01 madsmtm

See also discussion from https://github.com/rust-lang/unsafe-code-guidelines/issues/236#issuecomment-1637072799, this is still not determined

madsmtm avatar Jan 16 '24 03:01 madsmtm

If you add extern types to the mix, you'll see the exact same LLVM IR

@madsmtm I think you accidentally used the same link twice.

Based on this pull request, allowing PhantomData to apply interior mutability would be a breaking change, so it's not likely to be resolved in this direction soon. It seems like it would be worthwhile to change Opaque for now until the semantics are settled.

icmccorm avatar Jan 16 '24 14:01 icmccorm

I think you accidentally used the same link twice.

Apologies, I've fixed it now.

madsmtm avatar Jan 16 '24 19:01 madsmtm