Skip to content
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

Incorrect "unreachable match arm" warning #117119

Closed
RalfJung opened this issue Oct 24, 2023 · 10 comments · Fixed by #118308
Closed

Incorrect "unreachable match arm" warning #117119

RalfJung opened this issue Oct 24, 2023 · 10 comments · Fixed by #118308
Assignees
Labels
A-exhaustiveness-checking Relating to exhaustiveness / usefulness checking of patterns T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@RalfJung
Copy link
Member

RalfJung commented Oct 24, 2023

The following code generates a warning:

fn main() {
    #[derive(Copy, Clone)]
    enum Void {}
    union Uninit<T: Copy> {
        value: T,
        uninit: (),
    }
    unsafe {
        let x: Uninit<Void> = Uninit { uninit: () };
        match x.value {
            _ => println!("hi from the void!"),
        }
    }
}
warning: unreachable pattern
  --> src/main.rs:12:13
   |
12 |             _ => println!("hi from the void!"),
   |             ^
   |
   = note: `#[warn(unreachable_patterns)]` on by default

This warning is wrong: running the code in Miri shows that this arm is reachable and there is no UB.

@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Oct 24, 2023
@RalfJung
Copy link
Member Author

RalfJung commented Oct 24, 2023

Here's another variant of the false warning:

fn main() {
    #[derive(Copy, Clone)]
    enum Void {}
    
    let ptr = 1 as *const Void;
    unsafe {
        match *ptr {
            _ => println!("hello from the Void"),
        }
    }
}

@RalfJung
Copy link
Member Author

In fact the warning is actively harmful -- by removing the arm, UB is introduced into this well-defined code!

@RalfJung
Copy link
Member Author

There was a now-deleted comment saying that the warnings appear even without the feature gate -- that seems right, the lint is actually already misfiring. But at least currently when people then try to remove the "unreachable" arm they get a build error.

Cc @Nadrieril @oli-obk (pattern match lint issue)

@RalfJung RalfJung changed the title Incorrect "unreachable match arm" warning with exhaustive_patterns Incorrect "unreachable match arm" warning Oct 24, 2023
@Nadrieril Nadrieril added the A-exhaustiveness-checking Relating to exhaustiveness / usefulness checking of patterns label Oct 24, 2023
@Nadrieril Nadrieril self-assigned this Oct 24, 2023
@Nadrieril
Copy link
Member

But at least currently when people then try to remove the "unreachable" arm they get a build error.

They don't (playground)

@Nadrieril
Copy link
Member

There's a special case in exhaustiveness checking to allow match x {} with x: Void. Specifically for ! and enums without variants. It's the only place we allow omitting arms for empty types outside of the exhaustive_patterns feature.

match x {} with x: Void is allowed on stable today, and is relied on; we can't remove that.

What could we do? Could we detect the unsafe block? Or detect pointer dereference/union field access?

@RalfJung
Copy link
Member Author

RalfJung commented Oct 24, 2023 via email

@RalfJung
Copy link
Member Author

RalfJung commented Oct 24, 2023 via email

@Nadrieril
Copy link
Member

Nadrieril commented Oct 24, 2023

Let me check with you to be sure: in the following, the let y = .. is already UB, right?

unsafe {
    let x: Uninit<Void> = Uninit { uninit: () };
    let y = x.value;
    match y {
    }
}

and in here, the braces also would cause UB?

unsafe {
    let x: Uninit<Void> = Uninit { uninit: () };
    match {x.value} {
    }
}

So for the purposes of exhaustivenesss/unreachability I only need to check if the match scrutinee is literally a union field access expression or a pointer dereference expression? Anything else?

@saethlin saethlin added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. and removed needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. labels Oct 25, 2023
@RalfJung
Copy link
Member Author

Let me check with you to be sure: in the following, the let y = .. is already UB, right?

Yes.

and in here, the braces also would cause UB?

I think braces mean we do a load from the place, create a new temporary place, and then match on that. If that's accurate then yes that would also be UB already at the {x.value}.

So for the purposes of exhaustivenesss/unreachability I only need to check if the match scrutinee is literally a union field access expression or a pointer dereference expression?

Yes.

Anything else?

I have some concerns around dereferencing references (not just raw pointers) as well. The reference says that references have to be recursively valid, but that's not actually the semantics I think we should end up with. But I haven't found a clear example yet so it's possible that this is fine. We should IMO start with the places where we clearly allow invalid values: raw pointers and unions.

@Nadrieril
Copy link
Member

I have some concerns around dereferencing references (not just raw pointers) as well

Even exhaustive_patterns treats &! as inhabited, so at least there's no problem to fix here. For the purposes of this issue, raw pointers and unions seem to be the thing we care about

bors added a commit to rust-lang-ci/rust that referenced this issue Nov 26, 2023
…take-3, r=<try>

Don't warn an empty pattern unreachable if we're not sure the data is valid

Exhaustiveness checking used to be naive about the possibility of a place containing invalid data. This could cause it to emit an "unreachable pattern" lint on an arm that was in fact reachable, as in rust-lang#117119.

This PR fixes that. We now track whether a place that is matched on may hold invalid data. This also forced me to be extra precise about how exhaustiveness manages empty types.

Note that this now errs in the opposite direction: the following arm is truly unreachable but not linted as such. I'd rather not recommend writing a `match ... {}` that has the implicit side-effect of loading the value. [Never patterns](rust-lang#118155) will solve this cleanly.
```rust
match union.value {
    _x => unreachable!(),
}
```

I recommend reviewing commit by commit. I went all-in on the test suite because this went through a lot of iterations and I kept everything. The bit I'm least confident in is `is_known_valid_scrutinee` in `check_match.rs`.

Fixes rust-lang#117119.
@bors bors closed this as completed in 06e02d5 Dec 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-exhaustiveness-checking Relating to exhaustiveness / usefulness checking of patterns T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants