-
Notifications
You must be signed in to change notification settings - Fork 58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Stacked Borrows' interior mutability is per-location: enum discriminants can surprisingly change #204
Comments
@RalfJung I've been wondering about this general thing ever since you told me that that unsafe-cell was probably going to become "infectious" up to the enclosing enum. Clearly there is some kind of "gap" between the patterns that the borrow checker enforces and the actions that stacked borrows allows here. I mean, this isn't surprising, it will always be the case, but this feels a bit different than the sorts of "overapproximations" I expected. I'm not too worried about it, but I could see it leading to some surprising results in the compiler. As an example, imagine that we have Anyway, your example here feels like it's in the same vein. Our optimizations are relying on the I think perhaps we could live with this, but I also sense that it will be the cause of compiler bugs, because we'll want to be reasoning about paths and overlap (much as, e.g., the borrow checker does) and we'll have to be careful. |
I don't understand what this has to do with use std::num::NonZeroUsize;
fn test(v: &mut NonZeroUsize) {
let p = (v as *mut NonZeroUsize) as *mut usize;
unsafe { *p = 0; }
}
fn main() {
let mut opt = NonZeroUsize::new(1);
println!("{:?}", opt);
test(opt.as_mut().unwrap());
println!("{:?}", opt);
} I would not expect unsafe code to be allowed to set Maybe I'm going off on a tangent here, but is the intent for unsafe code to be allowed to "temporarily" violate the requirements of the type if it has exclusive access and "fixes it" before returning? I could see why that would be specific to |
This is a distinct but related issue: #84. |
Could you clarify what makes them distinct? Does this example capture the distinction? fn should_be_fine(v: &mut NonZeroUsize) {
let p = (v as *mut NonZeroUsize) as *mut usize;
unsafe { *p = 0; }
unsafe { *p = 1; }
}
fn should_be_ub(v: &UnsafeCell<NonZeroUsize>) {
let p = v.get() as *mut usize;
unsafe { *p = 0; }
unsafe { *p = 1; }
} ie. the first is valid because the violation is not visible outside the function, whereas the second is invalid because another thread could be observing an |
@Diggsey the difference is shown here (playground): // this enum has layout: (discriminant: i32, MaybeUninit<UnsafeCell<i32>>)
// the discriminant field is Freeze, but the UnsafeCell is !Freeze
enum E {
A(UnsafeCell<i32>),
B,
}
// E::B does not contain any !Freeze fields, and
// can be put in read-only memory
// => it is illegal to try to modify the memory of this static
static B: E = E::B;
// miri accepts this code:
unsafe { *(&B as *const _ as *mut i32) = 0; } Note that the layout of the enum is This is subtly different from #84, where the enum discriminant is put inside the data field via an enum layout optimization. In #84, depending on whether the data-field can be modified or not, the discriminant can be modified through it or not. Here, the discriminant can only be modified if it is put in In particular, if you actually were to replace the enum with the
which says that you don't have write access to modify the discriminant field. I'd expect the same error to have triggered for the |
Thanks, that explains it more clearly. The stacked borrows blog describes a "location" as a memory location (a byte address essentially) so I found the title and description confusing. It seems like the issue is that interior mutability is tracked per-value rather than per-location, and that's what leads to the problem? Otherwise (without enum layout optimisation) the discriminant would be at a different location than the unsafe cell, and there would be no issue. |
No idea what you mean by "per-value" here, but Stacked Borrows is definitely working "per-location" as in memory location.
There is a separate issue where when seeing an instance of type
Yes. And TBH, I'd really like to also do this for simpler types like
I am confused, this is basically the same example, how is it just "in the same vein"? |
Maybe it's the same example. I think the difference I was referring to is just that, in your example, modifying the value also changed the (implicit) discriminant, and hence switched from
I somewhat lean this way as well. The loss of precision feels worth it in exchange for a relatively simple rule regarding "how infectious" an |
Some recent discussion on Zulip led to the idea that we can view #84 and this issue as one and the same if we make it about "pointers where writing some values to them is okay but writing other values is immediate UB". That is not the only way either of these issues could be resolved, but if we had such pointers than for both #84 and this issue clearly we'd have 'maximal UB'. That said, for mutable references we actually might want to allow "temporarily" violating the invariant, as long as everything is fine when the reference expires. For mutation of shared data via interior mutability, that is not an option. |
The original question was from a time when UnsafeCell had niches. They don't any more, so the question is is moot and the issue can be closed. |
Stacked Borrows does per-location tracking of whether something has interior mutability. This means if the discriminant is stored on the same location as an
UnsafeCell
, it can surprisingly change:This passes in Miri and prints "None".
Note that right now, this would pass in Miri even with no enum layout optimizations as I simplified the handling of interior mutability (see rust-lang/miri#931). The old, more precise handling was a total mess and basically impossible to reason about. But even the most precise handling I can imagine would still allow the code above.
Cc @nikomatsakis @eddyb
The text was updated successfully, but these errors were encountered: