-
Notifications
You must be signed in to change notification settings - Fork 59
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
Promotion introduces UB into otherwise well-defined code #493
Comments
This even has issues with the weaker vertical model I proposed, since you could do: let offset = {
let x = Some(Cell::new(0i32));
let p = match &x{
Some(y) => y as *const _,
None => unreachable!()
};
p.byte_offset_from(addr_of!(x))
};
let x: &Option<Cell<i32>> = &None;
ptr::from_ref(x).cast_mut().byte_offset(offset).cast::<i32>().write(0); I'm assuming we can't not promote there b/c it's valid on stable with I think there's two options here, both bad:
I don't think that it would be reasonable to lift read-only status, because that poses issues with (de-)duplication. |
Yeah, this needs full variant-awareness to be justified.
Just because it's stable doesn't mean we can't try to slowly change it, maybe not promote it over an edition, whatever. But I worry this might be extremely widely used. And until inline const finally stabilizes, we often don't have any alternative we can offer. And any kind of migration for promotion-related things is very hard to do since it is basically impossible to have a lint for "you are relying on X being promoted, but in the future this will not be promoted any more, use inline const instead".
What does the |
I'm wondering if even that is sufficient. |
Well, even with inline const it is somewhat questionable to make |
…g, r=<try> const checking: do not do value-based reasoning for interior mutability This basically means a nicer error for rust-lang#121610, see the new test in `tests/ui/consts/refs-to-cell-in-final.rs`. Currently we have a mismatch between the dynamic semantics of `&` (things might be mutable when the pointee type is `!Freeze`, no matter the pointee value) and const-qualif (doing value-based reasoning). This removes that mismatch. I don't think this can break anything that builds on current nightly; any such code would already later run into the error that the interner complains about the mutable pointer (that's exactly rust-lang#121610). So a crater run makes little sense. Overall this peanuts compared to the *actual* problem with value-based reasoning for interior mutability in promotion (rust-lang/unsafe-code-guidelines#493). I have no idea how to resolve that. But at least now the use of the problematic qualif is down to 1... The first commit is independent, I just got worried about promotion of unions and was happy to notice that at least there we don't do value-based reasoning. r? `@oli-obk`
IIUC, there are something like one-and-a-half issues here:
For (2), instead of considering the value behind the reference, could this be addressed by distinguishing between read-only and mutable provenances, where the former is used for static read-only memory, and is "stronger than" interior mutability? Of course this might be a heavy hammer for dealing with a single edge case like this. (I've been casually following the provenance and aliasing model discussions, but amn't fully in the loop, so feel free to dismiss this as a silly idea if it is one.) |
Not sure what you mean, Miri detects this UB just fine. |
The case for item or inline IIUC, it's not plausible to only do I see three options, roughly matching Connor's:
If If we're going to in any way constrain const promotion from what's currently eligible in the future (i.e. over an edition) for any reason, it's probably a good idea to also constrain it to Personally, I don't think it's too bad — code relying on "adjacent" mutability is already a bad idea and only ever tenuously sound — but it is unfortunate that promotion — conceptually a "more things just work" feature — would restrict what code is WF. But not annoying enough to justify discriminant dependent retagging. At a bare minimum, a clippy restriction lint against const promotion of Footnotes
|
AFAIK unions get promoted but only if they are |
Fixing this the way I'd like to (by not doing value-based reasoning for interior mutability) would leak to 4k crater regressions (see rust-lang/rust#122789). Not sure how to best proceed here. I think the best possible outcome at this point is to do this promotion change over an edition that tells people to use
Agreed. |
I think promotion was clearly intended to be "transparent" to users - ie. the only visible side effect should be the extended lifetime. If it was meant to have other side effects then it would have been introduced with an explicit syntax for promotion, since Rust generally places a lot of importance on explicitness. Given that, I think the proposed solution of removing implicit promotion for !Freeze types is the best option. I think that even having promotion for !Freeze types inside let x: &'static Option<Cell<i32>> = {
static cell: Option<Cell<i32>> = None;
&cell
}; AIUI, |
Yes, it is. OTOH, if users don't write unsafe, it will actually do exactly what they want. So there's a question here whether it's worth requiring the extra syntactic overhead of something even more explicit (and making that even possible with generics), given that most code won't care and given that we have 10 years worth of code relying on this happening fully implicitly. I am not aware of cases where this caused issues -- we won't see all those cases, obviously, but writing to such a promoted would fairly reliably segfault since they are stored in read-only memory, so if people were surprised by that I suspect they would tell us.
This doesn't work with generics so we'll probably not be able to offer good migration for some amount of the existing code -- leaving aside the ergonomic questions. |
We've established that this would require an edition change anyway, so if a more explicit syntax is better, then we should change to that syntax, rather than compromising for no reason. While the amount of breakage is enough to require an edition change, in the grand scheme of things a tiny amount of code would be affected (and thus require the longer syntax).
Ah... Still, there are other more explicit options. |
Such as? I don't think there are. |
I mean you could add any syntax you want to explicitly call it out, but the obvious one is to use a |
I have no idea what you mean. If you want to reject const MY_CONST: &'static Cell<i32> = &None; so we can't use Put differently, we already have a syntax for {
const cell: Option<Cell<i32>> = &None;
cell
} and it's Now I realize your desugaring put the IOW, this is still implicit promotion causing UB in what looks like well-defined code and should be rejected, so we can't have it desugar to this: const C: Option<Cell<i32>> = None;
let x = &C;
ptr::from_ref(x).cast_mut().write(Some(Cell::new(0))); So, if you think |
I think fixing this can only really happen as part of rust-lang/rust#124328. |
…dead const-eval interning: accept interior mutable pointers in final value …but keep rejecting mutable references This fixes rust-lang#121610 by no longer firing the lint when there is a pointer with interior mutability in the final value of the constant. On stable, such pointers can be created with code like: ```rust pub enum JsValue { Undefined, Object(Cell<bool>), } impl Drop for JsValue { fn drop(&mut self) {} } // This does *not* get promoted since `JsValue` has a destructor. // However, the outer scope rule applies, still giving this 'static lifetime. const UNDEFINED: &JsValue = &JsValue::Undefined; ``` It's not great to accept such values since people *might* think that it is legal to mutate them with unsafe code. (This is related to how "infectious" `UnsafeCell` is, which is a [wide open question](rust-lang/unsafe-code-guidelines#236).) However, we [explicitly document](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) that things created by `const` are immutable. Furthermore, we also accept the following even more questionable code without any lint today: ```rust let x: &'static Option<Cell<i32>> = &None; ``` This is even more questionable since it does *not* involve a `const`, and yet still puts the data into immutable memory. We could view this as promotion [potentially introducing UB](rust-lang/unsafe-code-guidelines#493). However, we've accepted this since ~forever and it's [too late to reject this now](rust-lang#122789); the pattern is just too useful. So basically, if you think that `UnsafeCell` should be tracked fully precisely, then you should want the lint we currently emit to be removed, which this PR does. If you think `UnsafeCell` should "infect" surrounding `enum`s, the big problem is really rust-lang/unsafe-code-guidelines#493 which does not trigger the lint -- the cases the lint triggers on are actually the "harmless" ones as there is an explicit surrounding `const` explaining why things end up being immutable. What all this goes to show is that the hard error added in rust-lang#118324 (later turned into the future-compat lint that I am now suggesting we remove) was based on some wrong assumptions, at least insofar as it concerns shared references. Furthermore, that lint does not help at all for the most problematic case here where the potential UB is completely implicit. (In fact, the lint is actively in the way of [my preferred long-term strategy](rust-lang/unsafe-code-guidelines#493 (comment)) for dealing with this UB.) So I think we should go back to square one and remove that error/lint for shared references. For mutable references, it does seem to work as intended, so we can keep it. Here it serves as a safety net in case the static checks that try to contain mutable references to the inside of a const initializer are not working as intended; I therefore made the check ICE to encourage users to tell us if that safety net is triggered. Closes rust-lang#122153 by removing the lint. Cc `@rust-lang/opsem` `@rust-lang/lang`
const-eval interning: accept interior mutable pointers in final value …but keep rejecting mutable references This fixes rust-lang/rust#121610 by no longer firing the lint when there is a pointer with interior mutability in the final value of the constant. On stable, such pointers can be created with code like: ```rust pub enum JsValue { Undefined, Object(Cell<bool>), } impl Drop for JsValue { fn drop(&mut self) {} } // This does *not* get promoted since `JsValue` has a destructor. // However, the outer scope rule applies, still giving this 'static lifetime. const UNDEFINED: &JsValue = &JsValue::Undefined; ``` It's not great to accept such values since people *might* think that it is legal to mutate them with unsafe code. (This is related to how "infectious" `UnsafeCell` is, which is a [wide open question](rust-lang/unsafe-code-guidelines#236).) However, we [explicitly document](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) that things created by `const` are immutable. Furthermore, we also accept the following even more questionable code without any lint today: ```rust let x: &'static Option<Cell<i32>> = &None; ``` This is even more questionable since it does *not* involve a `const`, and yet still puts the data into immutable memory. We could view this as promotion [potentially introducing UB](rust-lang/unsafe-code-guidelines#493). However, we've accepted this since ~forever and it's [too late to reject this now](rust-lang/rust#122789); the pattern is just too useful. So basically, if you think that `UnsafeCell` should be tracked fully precisely, then you should want the lint we currently emit to be removed, which this PR does. If you think `UnsafeCell` should "infect" surrounding `enum`s, the big problem is really rust-lang/unsafe-code-guidelines#493 which does not trigger the lint -- the cases the lint triggers on are actually the "harmless" ones as there is an explicit surrounding `const` explaining why things end up being immutable. What all this goes to show is that the hard error added in rust-lang/rust#118324 (later turned into the future-compat lint that I am now suggesting we remove) was based on some wrong assumptions, at least insofar as it concerns shared references. Furthermore, that lint does not help at all for the most problematic case here where the potential UB is completely implicit. (In fact, the lint is actively in the way of [my preferred long-term strategy](rust-lang/unsafe-code-guidelines#493 (comment)) for dealing with this UB.) So I think we should go back to square one and remove that error/lint for shared references. For mutable references, it does seem to work as intended, so we can keep it. Here it serves as a safety net in case the static checks that try to contain mutable references to the inside of a const initializer are not working as intended; I therefore made the check ICE to encourage users to tell us if that safety net is triggered. Closes rust-lang/rust#122153 by removing the lint. Cc `@rust-lang/opsem` `@rust-lang/lang`
While investigating rust-lang/rust#121610, I noticed something problematic... we currently accept code like this:
Promotion kicks in despite interior mutability, because it sees the
None
variant and considers that one variant to not have interior mutability.But also, both Stacked Borrows and Tree Borrows consider this to be legal:
They allow this because we avoid making "do we allow mutation" value-dependent. This is for two reasons: (a) we don't even want to require the reference to point to a valid value, but if the memory might store any sequence of bytes then the concept of value-dependency doesn't even make sense, and (b) checking the value stored behind the reference would introduce something very close to a cyclic argument into the memory model. So when creating a reference to
o
, we can't know whethero
isNone
orSome
, and therefore we just conservatively assume that interior mutability is allowed.It should follow that this is also legal:
But this is UB, due to promotion putting the
None
into read-only memory. That's Not Good, promotion is introducing UB!This is related to #236 but seems worth a separate sub-issue.
See rust-lang/rust#122789 for a crater run of at attempt to fix this.
Cc @oli-obk
The text was updated successfully, but these errors were encountered: