-
Notifications
You must be signed in to change notification settings - Fork 13.6k
Open
Labels
A-MIRArea: Mid-level IR (MIR) - https://blog.rust-lang.org/2016/04/19/MIR.htmlArea: Mid-level IR (MIR) - https://blog.rust-lang.org/2016/04/19/MIR.htmlA-miriArea: The miri toolArea: The miri toolA-patternsRelating to patterns and pattern matchingRelating to patterns and pattern matchingC-bugCategory: This is a bug.Category: This is a bug.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.Relevant to the compiler team, which will review and decide on the PR/issue.
Description
I tried this code:
#![allow(deref_nullptr)]
enum Never {}
fn main() {
unsafe {
match *std::ptr::null::<Result<Never, Never>>() {
Ok(_) => {
lol();
}
Err(_) => {
wut();
}
}
}
}
fn lol() {
println!("lol");
}
fn wut() {
println!("wut");
}
I expected to see this happen: Running this with Miri detects undefined behavior.
Instead, this happened: Running this with Miri outputs "lol", and program exits successfully.
It seems like rustc is discarding the second match arm entirely in the MIR?
MIR
// WARNING: This output format is intended for human consumers only
// and is subject to change without notice. Knock yourself out.
// HINT: See also -Z dump-mir for MIR at specific points during compilation.
fn main() -> () {
let mut _0: ();
let mut _1: *const std::result::Result<Never, Never>;
let _2: ();
bb0: {
_1 = null::<Result<Never, Never>>() -> [return: bb1, unwind continue];
}
bb1: {
_2 = lol() -> [return: bb2, unwind continue];
}
bb2: {
return;
}
}
// MIR for lol() and wut() omitted for brevity
Meta
Reproducible on the playground with 1.89.0-nightly (2025-06-10 1677d46cb128cc8f285d)
@rustbot labels +A-patterns
Metadata
Metadata
Assignees
Labels
A-MIRArea: Mid-level IR (MIR) - https://blog.rust-lang.org/2016/04/19/MIR.htmlArea: Mid-level IR (MIR) - https://blog.rust-lang.org/2016/04/19/MIR.htmlA-miriArea: The miri toolArea: The miri toolA-patternsRelating to patterns and pattern matchingRelating to patterns and pattern matchingC-bugCategory: This is a bug.Category: This is a bug.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.Relevant to the compiler team, which will review and decide on the PR/issue.
Type
Projects
Milestone
Relationships
Development
Select code repository
Activity
[-]`match` on a null place with uninhabited type has strange behavior[/-][+]`match` on uninhabited type has strange behavior[/+]xizheyin commentedon Jun 12, 2025
This may because, if either type parameter of
Result
isNever
, the Rust compiler treats the type as uninhabitable and optimizes away the code, so dereferencing a null pointer doesn't cause a crash; but withResult<i32, i32>
, the type is inhabited, so dereferencing null will lead to a segmentation fault.theemathas commentedon Jun 12, 2025
Miri is supposed to run on unoptimized code.
saethlin commentedon Jun 12, 2025
No. The fact that the pointer here is null is an unnecessary distraction that I didn't realize last night.
Result<Never, Never>
is a ZST, and our documentation clearly states:Moreover, if you look at the MIR you can see that we never even emit a read. The dereference is fine.
This code has the same strange lowering:
kpreid commentedon Jun 12, 2025
This seems closely related to rust-lang/unsafe-code-guidelines#540, and arguably corresponds to a gap in the definition of what is UB — what are the steps executing a pattern match actually performs? Which one of those steps is is UB when an uninhabited value is matched — not ever produced, yet matched?
Here is a simpler program that also passes Miri:
This one lacks the puzzle of “which of the match arms should be run? neither, of course”, though, and one could perhaps argue that it shouldn’t be UB since there is no branch and Rust prefers not to have entirely gratuitous UB. Yet, what semantics justifies that?
kpreid commentedon Jun 12, 2025
Here are a couple of possible high-level statements of where the UB should be (these do not reflect the current documented semantics of Rust, but could in the future):
Result<Never, Never>
is, itself, uninhabited. Therefore, reading it to match it is “producing an invalid value” UB forstd::mem::Discriminant<Result<Never, Never>>
or an equivalent internal construct.match
es match, and control flow falls off the end of thematch
, which should be defined as UB.RalfJung commentedon Jun 13, 2025
I agree this should be UB -- I'd have expected a discriminant read that causes UB here. But somehow we don't get one?
Cc @Nadrieril
[-]`match` on uninhabited type has strange behavior[/-][+]`match` on uninhabited type does not trigger UB in Miri[/+]RalfJung commentedon Jun 13, 2025
The MIR is already odd immediately after being built:
Note the entire lack of a discriminant check.
Nadrieril commentedon Jun 13, 2025
Yep, this is something we're about to fix. Today we skip discriminant reads for enums based on the presence of empty variants; this was considered incorrect and t-lang FCP-approved a fix here (which is apparently waiting on me?? oops).
RalfJung commentedon Jun 13, 2025
I recall having discussions around this, but... that PR mentions closures and there are no closures involved here?
Nadrieril commentedon Jun 20, 2025
Ah yeah you're right. That PR is only the first step towards fixing that, but we intend to fix that eventually.