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

Fix false positives in unreachable_code lint #90636

Closed
wants to merge 2 commits into from

Conversation

FabianWolff
Copy link
Contributor

@FabianWolff FabianWolff commented Nov 5, 2021

Fixes #89779 (both the false positives as well as the empty match) and fixes #88393.

r? @Mark-Simulacrum

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Nov 5, 2021
Copy link
Member

@Mark-Simulacrum Mark-Simulacrum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you give a high level description of the changes in the second commit? I can probably figure it out based on the diff, but it would be good to document that in the commit message and PR description to make it easier to identify what's going on.

compiler/rustc_passes/src/liveness.rs Outdated Show resolved Hide resolved
@Mark-Simulacrum Mark-Simulacrum added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Nov 9, 2021
@apiraino apiraino added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Nov 11, 2021
@FabianWolff
Copy link
Contributor Author

Can you give a high level description of the changes in the second commit? I can probably figure it out based on the diff, but it would be good to document that in the commit message and PR description to make it easier to identify what's going on.

I've added a comment in the code explaining what is happening.

Honestly, at this point, I think it would be better to have a separate pass that does unreachable code warnings. The warnings in the type checker are incomplete (#85071), and adding them to the liveness analysis was a bad idea in hindsight because it just isn't a good fit (and avoiding duplicate warnings if the warning can be issued in two different places is also cumbersome).

This PR should solve #89779 for now, but we should look elsewhere for a good long-term solution.

@tabokie
Copy link
Contributor

tabokie commented Dec 2, 2021

This will also fix #88393.

@bors
Copy link
Contributor

bors commented Dec 18, 2021

☔ The latest upstream changes (presumably #89841) made this pull request unmergeable. Please resolve the merge conflicts.

@FabianWolff
Copy link
Contributor Author

This will also fix #88393.

Right, thanks for pointing this out! I have added a test case for it.

@rustbot ready

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Jan 15, 2022
@rust-log-analyzer
Copy link
Collaborator

The job mingw-check failed! Check out the build log: (web) (plain)

Click to see the possible cause of the failure (guessed by this bot)
configure: rust.debug-assertions := True
configure: rust.overflow-checks := True
configure: llvm.assertions      := True
configure: dist.missing-tools   := True
configure: build.configure-args := ['--enable-sccache', '--disable-manage-submodu ...
configure: writing `config.toml` in current directory
configure: 
configure: run `python /checkout/x.py --help`
configure: 

// As we go through the code, we warn about unreachable code after expressions
// with uninhabited types. We do not want to warn if the last expression of an
// if or match arm has an uninhabited type, though, because the code after the
// if/match could still be reachable via another arm -- unless all arms
Copy link
Member

@Mark-Simulacrum Mark-Simulacrum Jan 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, can you say more about the specific case being addressed here? It's not quite clear to me why diverges would be yes and warn unreachable would be no, yet we shouldn't lint on that expression? Maybe some code examples for each case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enum Void {}
fn void() -> Void {todo!()}

fn bar(b: bool) {
    if b {
        void(); // A
    } else {
        // B
    }
    // C
}

In this example, A diverges because it has an uninhabited type. We can't issue a warning that "any code [in particular, C] following this expression is unreachable", though, because C might be reachable via the else branch. Hence A diverges and we don't want to warn about unreachable successors (C). If B also turns out to be diverging, we will warn about the entire if expression diverging and making C unreachable.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, so it seems like when we're exiting the if in the CFG, we should only consider C diverging if A and B have diverged -- so at point C we should have diverges = no, and as such not emit any warnings?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My comment was meant to suggest that I (still) don't understand the purpose/necessity for WarnUnreachable -- it seems to me that we should already not be warning when seeing C just based on Diverges = no at that point, so WarnUnreachable isn't actually necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, sorry. Consider another example then:

if ... {
    diverges(); // X
    alsoDiverges(); // Y
}
// Z

Y has the successor Z and diverges, so Diverges=Yes, but WarnUnreachable=No because Z is also reachable via the (implicit) else branch. By contrast, X has the successor Y and diverges, so Diverges=Yes, and WarnUnreachable=Yes because X is Y's only predecessor. If there was a non-implicit else branch, the Diverges information would be used to check whether the entire if is diverging (in which case we would want to warn about Z being unreachable).

So you are right in that WarnUnreachable doesn't make a difference if Diverges=No, but it does for Diverges=Yes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect the implicit else branch to have Diverges::No as its "result", making point Z have the same diverges as point A (presumably No, in your example):

// A
if ... {
   diverges(); // X
   diverges(); // Y
}
// Z

Y's successor is Z, but Z has two predecessors in the CFG: (roughly) A or Y, depending on if the if was taken. (Really, the predecessor of Z is the if condition and Y, I guess).

Getting this right is likely to be relatively hard, which is why I'm not sure we should be trying to roll this on our own -- a MIR rewrite would make it much easier.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting this right is likely to be relatively hard, which is why I'm not sure we should be trying to roll this on our own -- a MIR rewrite would make it much easier.

I totally agree, but I don't have the MIR knowledge and time to do this myself at the moment.

@Mark-Simulacrum
Copy link
Member

I left one question -- the code at a brief glance seems OK, but would like to review it more thoroughly with a better understanding of the intent behind the changes here (per the comment I left).

I tend to agree with you (#90636 (comment)) that we should be exploring other options. In particular, "dead code paths" seem like a pass that is well-suited for MIR, particularly since it may let us run const-prop and similar to detect more cases (though it's not clear whether that's actually desirable -- sometimes worse quality lints are actually better). On the other hand, MIR may be too low level to effectively map back. I think all the linting we do in the liveness pass should be possible to do on MIR, though -- we already have similar passes there, I think. If we feel that such a rewrite is feasible, I'd probably suggest pursuing that over patchwork fixes.

@Mark-Simulacrum Mark-Simulacrum added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jan 18, 2022
@JohnCSimon JohnCSimon added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Feb 6, 2022
@JohnCSimon
Copy link
Member

Ping from triage: Can you address the comment from the reviewer and fix the build?
@FabianWolff

FYI: when a PR is ready for review, post a message containing
@rustbot ready to switch to S-waiting-on-review so the PR appears in the reviewer's backlog.

@JohnCSimon
Copy link
Member

@FabianWolff
Ping from triage: I'm closing this due to inactivity, Please reopen when you are ready to continue with this.
Note: if you do please open the PR BEFORE you force push to it, else you won't be able to reopen.
Thanks for your contribution.

@rustbot label: +S-inactive

@JohnCSimon JohnCSimon closed this Apr 23, 2022
@rustbot rustbot added the S-inactive Status: Inactive and waiting on the author. This is often applied to closed PRs. label Apr 23, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-inactive Status: Inactive and waiting on the author. This is often applied to closed PRs. S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

False-positive with the unreachable code lint Unreachable code warning even when no code follows
9 participants