&mut T, ffi, UB?

#[repr(C)]
struct T { ... }

fn main() {
  let mut t = T::new();
  let ptr = &mut t as *mut T as u64;
  some_ffi_call(ptr); // modifies `t`
  read from t
}

Do we now have potential UB? the reason being:

  1. From rustc's perspective, nothing has modified mut t and thus there may be caches lying around.
  2. mut t is modified via the ffi call
  3. we do a read from t afterwards
1 Like

Not UB.

The lifetime of the borrow &mut t lasts until the borrow's last use, which is inside some_ffi_call. The fact you convert it to an integer first is irrelevant; there is still a way to assign lifetimes to borrows in a way that satisfies the stacked borrows model. So this is fine.

If you switched read from t and some_ffi_call(ptr) that would definitely be UB.

Sorry, either I failed to explain why I think it's UB or I failed to understand your reasoning. My fear is the follows:

let mut t = T::new(); // t.idx = 0
let ptr = &mut t as *mut T as u64;
some_ffi_call(ptr); // sets t.idx = 1`
  read from t // what if there is some cached value of t.idx == 0? (because Rust doesn't see any 'modification')
1 Like

Phrased another way, how does rustc know it needs to clear cache for mut t ?

To state this question yet another way: ptr doesn't mutably borrow t. So how does rust know that t may be modified by the ffi call and make sure to read t only after the ffi call instead of, say, reordering the read before the ffi call during optimization?

I think if you pass something which behaves like a mutable pointer to a function, the optimiser will assume it could have been mutated and won't be able to rely on cached values.

let ptr = &mut t as *mut T as u64;

In this line, ptr has type u64 ...

Yes, but LLVM knows it started life as a pointer.

Not quite. Integers do not have provenance, so LLVM doesn't know that the integer was derived from the pointer. However the moment you turned the pointer into an integer, LLVM has to assume that the pointer escaped and that any function call can create a pointer out of thin air until t ceases to exist (eg the function defining t returns.) or the original pointer is invalidated in another way.

5 Likes

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.