-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Mutex and RwLock are unsound in presence of discriminant elision #68206
Comments
Can you show an example where this is happening? I can't come up with an example where the niche inside the Mutex is ever used where there even is an initialized Mutex at all. |
For example following code contains a data race on use std::mem::size_of;
use std::sync::Arc;
use std::sync::Mutex;
#[inline(never)]
#[no_mangle]
pub fn thread1(v: &mut bool) {
*v = true;
}
#[inline(never)]
#[no_mangle]
pub fn thread2(v: &Option<Mutex<bool>>) -> usize {
match *v {
None => 0,
Some(_) => 1,
}
}
fn main() {
// Verify that discriminant elision takes place.
assert_eq!(
size_of::<Mutex<bool>>(),
size_of::<Option<Mutex<bool>>>(),
);
let a = Arc::new(Some(Mutex::new(false)));
let b = a.clone();
let t = std::thread::spawn(move || {
thread1(&mut (*a).as_ref().unwrap().lock().unwrap())
});
println!("{}", thread2(&b));
t.join().unwrap();
} The |
Note that there's another niche in A simple fix would be to change the |
I'd say the problem is rather with UnsafeCell itself. match shouldn't look for the tag through the boundary of an UnsafeCell at all, as accessing its value is inherently unsafe and can only be safe if you know the invariants of the safe abstraction on top, which match in general can not know. So UnsafeCell, like MaybeUninit shouldn't allow outside tags to use niches of its T. |
I tend to agree that the However, I also tend to think that we should have This then raises the immediate question of whether we will need to make a new variant of |
On its own that isn't a reason to leave the optimization opporturnity on the table, it would still be nice to have if only for "peace of mind". But I don't think hacks such as adding a second variant of |
I think it would be useful to have a perma-unstable |
It seems to me that this bug also happens in |
Atomics have no niches, but In terms of naming one might signify |
Ah, good catch! This is very closely related to rust-lang/unsafe-code-guidelines#204, I think. Basically, Rust's notion of "disjointness" says that the discriminant is disjoint from the actual enum data, but with niche optimizations that is not actually the case. |
T-compiler triage: P-high, leaving nominated label in place since this needs discussion by T-lang and T-libs (at least). |
I am curious, is this only a problem for For example with niches use std::cell::Cell;
fn main() {
let cell = Some(Cell::new(false));
let cell_ref = &cell;
if let Some(x) = cell.as_ref() {
x.set(true);
}
// Could this next decision be made with a stale value for cell?
if let Some(x) = cell_ref.as_ref() {
x.set(false);
}
} I would expect the compiler can already do the load of I don't know how to choose the right words, but it seems to me using niches through an |
@pitdicker that's basically what rust-lang/unsafe-code-guidelines#204 that I mentioned is about. |
Interesting. I am of the mind that |
Citing what I said on Zulip (about rust-lang/unsafe-code-guidelines#204), and going a bit beyond that: I agree with @Centril that we should not mix concepts (superficially, it looks like niches and interior mutability are entirely unrelated), but I also cannot say that I know how to define the concept of interior mutability on its own, ignoring niches. A variant of Stacked Borrows that correctly models the "one thing UnsafeCell does" with support for exposing niches might be possible, but I think it will require things I would like to strictly avoid -- like "ghost state" that records the actual enum discriminant in some magical place even when in memory, we just see its encoding via some niche. this is exactly the same kind of "ghost state" as the "active union variant", and I did everything I could to make sure we do not have that. And even if we did do that, we'd end up making niches (or rather, discriminants) very explicit in the language semantics just for the purpose of being able to model their interaction with interior mutability. I see an inherent conflict here between niches, that basically "overlay" multiple pieces of information on the same location, and trying to define interior mutability operationally (in terms of which memory can and cannot be mutated when) in a way that it only applies to "parts of" the information stored in that location. |
Probably my mental model is just very different, but to me they seem quite related. With interior mutability you need to have a set of conventions about when to create references, when you can do reads, and when mutation is allowed. The read that can happen with niches that pass the You can easily do a read of the |
In a sequential setting, "reading a stale value" is not necessarily a problem -- we could define everything such that this is fine. As someone (you?) remarked, the only relevant result -- the discriminant encoded in that value -- will indeed not change. But defining things like this is complicated, and that's indeed why I agree that they are pretty related. |
Finding such a definition for a sequential setting seems pretty close to defining when data races are 'benign'. Sounds interesting, but I fully believe the this is complicated part 😄. |
@rfcbot fcp merge We discussed this in today's @rust-lang/lang meeting. The consensus was that we should inhibit niches from propagating out from I've prepared a write-up of the situation that covers reasoning and implications. Please review it and see if there are things missing from this thread. In particular, I outlined the problems and implications of the decision, but I'd like to add anything I've overlooked. One aspect I'd like some feedback on in particular is my summary of @RalfJung's comment, in the What about cell section. Presuming we decide to 'merge' this proposal, I would think that we could close the bug by:
|
Team member @nikomatsakis has proposed to merge this. The next step is review by the rest of the tagged team members: No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
* Bumped patch release,updated layout for Cell and UnsafeCell due to rustc bug. Link to rust bug rust-lang/rust#68206 * Travis config:remove Cargo.lock after beta builds * Fix previous commit
Further evidence that Rust's aliasing constraints (at least as formalized by Stacked Borrows) don't work well with niches for interior mutable data: #68303 |
Based on @RalfJung's comment, I have greatly expanded the section in my hackmd writeup on the deeper conflict at play here. Ping @joshtriplett @pnkfelix @scottmcm and @withoutboats boats on the FCP merge for the idea that |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
@nikomatsakis that looks good, except for
The stacks and tags introduced by Stacked Borrows are "ghost state". But they are entirely orthogonal to the data stored in memory, and they are handled uniformly across all memory accesses. The "ghost state" needed here would be specific to enums, that feels much more ad-hoc than what we are doing currently. |
@RalfJung yes, good point, thanks. |
Does this interact with RFC 1789? That not only allows projecting references into a Given this ability to project into and materialize |
@rpjohnst I think it's fine because it doesn't let you make any assumptions, the Also, creating a |
…ell, r=oli Hide niches under UnsafeCell Hide any niche of T from type-construction context of `UnsafeCell<T>`. Fix rust-lang#68303 Fix rust-lang#68206
Notably, |
The final comment period, with a disposition to merge, as per the review above, is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. The RFC will be merged soon. |
@rpjohnst a good catch for sure, definitely something to think about. |
(assigning self, since I authored PR #68491) |
…stc bug. Link to rust bug rust-lang/rust#68206
* Bumped patch release,updated layout for Cell and UnsafeCell due to rustc bug. Link to rust bug rust-lang/rust#68206 * Travis config:remove Cargo.lock after beta builds * Fix previous commit
Neither Mutex type nor RwLock type inhibit layout optimization and so either of
them might contain a niche. When a niche is used to perform a discriminant
elision extracting the discriminant of the outer type will inadvertently load
the content of Mutex / RwLock without proper synchronization.
The text was updated successfully, but these errors were encountered: