-
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
Check for union field accesses in THIR unsafeck #85263
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation itself looks good to me (though, again, I'm not a reviewer).
I think we should add revisions for existing tests. If we do, we can mark the errors that we don't emit yet as
//[mirunsafeck]~ ERROR
// FIXME(thir-unsafeck)
Unsure though. Anyway I'd want to hear from @nikomatsakis
if adt_def.is_union() { | ||
self.requires_unsafe(expr.span, AccessToUnionField); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, the actual check seems quite a bit more complicated! (cc @RalfJung, who authored those changes)
I'm wondering why more tests didn't fail. I think we're going to have to tweak these rules here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, right, you need unsafe
to read from the union. (Tbh I've never used unions in Rust 😄)
I'm wondering why more tests didn't fail.
I think we don't run the existing check-fail tests, see my comment above.
EDIT: nevermind, this should be too conservative instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All reads and some writes should be unsafe
, yes -- and we should have tests covering that... tough probably not very precise ones.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the src/test/ui/union/union-unsafe.rs
should help; you probably want to run that under thirunsafeck as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RalfJung I'm trying to properly check if something is reading from a union, and encountered this weirdness. Is this a bug or feature of the MIR borrow checker?
// this is safe
match foo {
Foo { zst: () } => {},
}
// but this is so unsafe it prints the same error message twice?
match foo {
Foo { pizza: Pizza { topping: Some(PizzaTopping::Cheese) | Some(PizzaTopping::Pineapple) | None } } => {},
// ^~ ERROR access to union field is unsafe and requires unsafe function or block
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that behavior in the first match is a bug in the MIR borrowck, since RFC 1444 says that pattern matching against unions must be unsafe. (and MIR borrowck is okay with it since it doesn't actually involve any reads)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this has to do with how pattern matching is compiled -- matching zst
against the ()
pattern does not actually read anything (we know the pattern matches without even looking). So it makes sense to me that the former would not be unsafe
. But one could easily decide either way and both ways make sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that this pattern is unsafe
:
Foo { zst: x }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've updated the checking, I think it should properly detect if something is a read or write to union now (I am also now running almost all of the src/test/ui/union
test suite).
7f424a7
to
14834a6
Compare
This comment has been minimized.
This comment has been minimized.
14834a6
to
62bbb23
Compare
The THIR checker now rejects code like this while the MIR checker doesn't. I'm pretty sure that this is a bug with the MIR checker though. union Foo { bar: i8 }
let Foo { bar: _ } = Foo { bar: 5 };
union Foo2 { bar: () }
let Foo2 { bar: () } = Foo2 { bar: () }; None of those actually do anything though, so it doesn't seem like this would have any real-world implications. |
I'm on the fence about whether those should require unsafe or not -- as I said, given that matching against Making them unsafe would be a breaking change, so the question is also if that change is worth it. My inclination here is "no" -- either way is a good option, and so in the absence of a preference either way we should err on the side of not doing a breaking change. What is the argument for why you consider this a bug in the MIR checker? |
@RalfJung The MIR rules result in really odd behavior like this in safe code: #[derive(Copy, Clone)]
#[repr(u8)]
enum OneVal {
One = 1,
}
union Foo {
bar: u8,
oneval: OneVal
}
fn main() {
match (Foo { bar: 42 }) {
Foo { oneval: OneVal::One } => {
println!("reached!"); // always prints
},
}
} The value of match (Foo { bar: 42 }) {
Foo { oneval: x @ OneVal::One } => { //~ ERROR access to union field is unsafe
println!("reached!");
},
} And if we add another field to the #[derive(Copy, Clone)]
#[repr(u8)]
pub enum TwoVal {
One = 1,
Two = 2,
}
union Foo {
bar: u8,
oneval: TwoVal
}
fn main() {
match (Foo { bar: 42 }) {
Foo { oneval: TwoVal::One | TwoVal::Two } => { //~ ERROR access to union field is unsafe
println!("reached!");
},
}
} |
(This is a debate that looks somewhat similar to #80059) |
That weird behavior is actually a regression. Rust 1.19 (first stable release with unions) rejects the first snippet while Rust 1.22 accepts it. |
I agree the But that does not imply that the |
I've loosened up the safety rules a little bit to allow binding union fields to wildcards: #[derive(Copy, Clone)]
struct Pie {
slices: u8,
size: u8,
}
union Foo {
bar: i8,
baz: Pie
}
// safe with THIR and MIR checker:
match u {
Foo { baz: _ } => {},
}
// unsafe to THIR, safe to MIR:
match u {
Foo { baz: Pie { .. } } => {},
}
match u {
Foo { baz: Pie { slices: _, size: _ } } => {},
} |
42b10ea
to
129958e
Compare
☔ The latest upstream changes (presumably #85259) made this pull request unmergeable. Please resolve the merge conflicts. |
129958e
to
e6c63c8
Compare
I've updated the way it checks unsafety to match the MIR rules as closely as possible. Specifically, the rule it enforces is "Irrefutable pattern matching against unions without any bindings is safe". This makes the THIR checker almost entirely match the MIR checker. The only difference I know of is union Foo { bar: i8, pizza: Pizza }
#[derive(Copy, Clone)]
struct Pizza {
topping: Option<PizzaTopping>
}
#[derive(Copy, Clone)]
enum PizzaTopping { Cheese, Pineapple }
let foo = Foo { bar: 5 };
match foo { Foo {
pizza: Pizza {
topping: Some(PizzaTopping::Cheese) | Some(PizzaTopping::Pineapple) | None
}
} => {} }; Which the THIR checker accepts but MIR rejects. This seems to be a quirk of the way the MIR desugars nested or expressions. |
Uh... is that really what the old checker did? "Irrefutable" is a very subtle notion, in particular when considering unions. |
@RalfJung As far as I can tell that's what's happening. Here's another example: union Foo { bar: i8 }
let foo = Foo { bar: 5 };
match foo { Foo {
bar: i8::MIN..=i8::MAX, // safe since this always matches
} => {} }; Perhaps it would be better to change to rejecting patterns like this and only make an exception for ZSTs? |
Hm, interesting. |
…rtichaut Add pattern walking support to THIR walker Suggested in rust-lang#85263 (comment), this splits off the support for pattern walking in THIR from rust-lang#85263. This has no observable effect on THIR unsafety checking, since it is not currently possible to trigger unsafety from the THIR checker using the additional patterns or constants that are now walked. THIR patterns are walked in source code order. r? `@LeSeulArtichaut`
…ichaut Add pattern walking support to THIR walker Suggested in rust-lang#85263 (comment), this splits off the support for pattern walking in THIR from rust-lang#85263. This has no observable effect on THIR unsafety checking, since it is not currently possible to trigger unsafety from the THIR checker using the additional patterns or constants that are now walked. THIR patterns are walked in source code order. r? `@LeSeulArtichaut`
☔ The latest upstream changes (presumably #86378) made this pull request unmergeable. Please resolve the merge conflicts. |
r? @oli-obk |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just found a small edge case that is probably an unreachable pattern.
The union "drop field" assignment unsafety can be solved in a follow up PR, they are marked with FIXMEs after all.
7ab11d2
to
df3e003
Compare
@bors r+ |
📌 Commit b86ed4a has been approved by |
☀️ Test successful - checks-actions |
Moved the discussion about pattern matching on unions to #87520. |
see also #85259, #83129, rust-lang/project-thir-unsafeck#7
r? @LeSeulArtichaut