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

Improve possible_borrower #9701

Merged
merged 6 commits into from
Dec 22, 2022
Merged

Conversation

smoelius
Copy link
Contributor

@smoelius smoelius commented Oct 24, 2022

This PR makes several improvements to clippy_uitls::mir::possible_borrower. These changes benefit both needless_borrow and redundant clone.

  1. Use the compiler's MaybeStorageLive analysis

I could spot not functional differences between the one in the compiler and the one in Clippy's repository. So, I removed the latter in favor of the the former.

  1. Make PossibleBorrower a dataflow analysis instead of a visitor

The main benefit of this change is that allows possible_borrower to take advantage of statements' relative locations, which is easier to do in an analysis than in a visitor.

This is easier to illustrate with an example, so consider this one:

    fn foo(cx: &LateContext<'_>, lint: &'static Lint) {
        cx.struct_span_lint(lint, rustc_span::Span::default(), "", |diag| diag.note(&String::new()));
        //                                                                          ^
    }

We would like to flag the & pointed to by the ^ for removal. foo's MIR begins like this:

fn span_lint::foo::{closure#0}(_1: [closure@$DIR/needless_borrow.rs:396:68: 396:74], _2: &mut rustc_errors::diagnostic_builder::DiagnosticBuilder<'_, ()>) -> &mut rustc_errors::diagnostic_builder::DiagnosticBuilder<'_, ()> {
    debug diag => _2;                    // in scope 0 at $DIR/needless_borrow.rs:396:69: 396:73
    let mut _0: &mut rustc_errors::diagnostic_builder::DiagnosticBuilder<'_, ()>; // return place in scope 0 at $DIR/needless_borrow.rs:396:75: 396:75
    let mut _3: &mut rustc_errors::diagnostic_builder::DiagnosticBuilder<'_, ()>; // in scope 0 at $DIR/needless_borrow.rs:396:75: 396:100
    let mut _4: &mut rustc_errors::diagnostic_builder::DiagnosticBuilder<'_, ()>; // in scope 0 at $DIR/needless_borrow.rs:396:75: 396:100
    let mut _5: &std::string::String;    // in scope 0 at $DIR/needless_borrow.rs:396:85: 396:99
    let _6: std::string::String;         // in scope 0 at $DIR/needless_borrow.rs:396:86: 396:99

    bb0: {
        StorageLive(_3);                 // scope 0 at $DIR/needless_borrow.rs:396:75: 396:100
        StorageLive(_4);                 // scope 0 at $DIR/needless_borrow.rs:396:75: 396:100
        _4 = &mut (*_2);                 // scope 0 at $DIR/needless_borrow.rs:396:75: 396:100
        StorageLive(_5);                 // scope 0 at $DIR/needless_borrow.rs:396:85: 396:99
        StorageLive(_6);                 // scope 0 at $DIR/needless_borrow.rs:396:86: 396:99
        _6 = std::string::String::new() -> bb1; // scope 0 at $DIR/needless_borrow.rs:396:86: 396:99
                                         // mir::Constant
                                         // + span: $DIR/needless_borrow.rs:396:86: 396:97
                                         // + literal: Const { ty: fn() -> std::string::String {std::string::String::new}, val: Value(<ZST>) }
    }

    bb1: {
        _5 = &_6;                        // scope 0 at $DIR/needless_borrow.rs:396:85: 396:99
        _3 = rustc_errors::diagnostic_builder::DiagnosticBuilder::<'_, ()>::note::<&std::string::String>(move _4, move _5) -> [return: bb2, unwind: bb4]; // scope 0 at $DIR/needless_borrow.rs:396:75: 396:100
                                         // mir::Constant
                                         // + span: $DIR/needless_borrow.rs:396:80: 396:84
                                         // + literal: Const { ty: for<'a> fn(&'a mut rustc_errors::diagnostic_builder::DiagnosticBuilder<'_, ()>, &std::string::String) -> &'a mut rustc_errors::diagnostic_builder::DiagnosticBuilder<'_, ()> {rustc_errors::diagnostic_builder::DiagnosticBuilder::<'_, ()>::note::<&std::string::String>}, val: Value(<ZST>) }
    }

The call to diag.note appears in bb1 on the line beginning with _3 =. The String is owned by _6. So, in the call to diag.note, we would like to know whether there are any references to _6 besides _5.

The old, visitor approach did not consider the relative locations of statements. So all borrows were treated the same, even if they occurred after the location of interest.

For example, before the _3 = ... call, the possible borrowers of _6 would be just _5. But after the call, the possible borrowers would include _2, _3, and _4.

So, in a sense, the call from which we are try to remove the needless borrow is trying to prevent us from removing the needless borrow(!).

With an analysis, things do not get so muddled. We can determine the set of possible borrowers at any specific location, e.g., using a ResultsCursor.

  1. Change only_borrowers to at_most_borrowers

possible_borrowers exposed a function only_borrowers that determined whether the borrowers of some local were exactly some set S. But, from what I can tell, this was overkill. For the lints that currently use possible_borrower (needless_borrow and redundant_clone), all we really want to know is whether there are borrowers other than those in S. (Put another way, we only care about the subset relation in one direction.) The new function at_most_borrowers takes this more tailored approach.

  1. Compute relations "on the fly" rather than using transitive_relation

The visitor would compute and store the transitive closure of the possible borrower relation for an entire MIR body.

But with an analysis, there is effectively a different possible borrower relation at each location in the body. Computing and storing a transitive closure at each location would not be practical.

So the new approach is to compute the transitive closure on the fly, as needed. But the new approach might actually be more efficient, as I now explain.

In all current uses of at_most_borrowers (previously only_borrowers), the size of the set of borrowers S is at most 2. So you need only check at most three borrowers to determine whether the subset relation holds. That is, once you have found a third borrower, you can stop, since you know the relation cannot hold.

Note that transitive_relation is still used by clippy_uitls::mir::possible_origin (a kind of "subroutine" of possible_borrower).

cc: @Jarcho


changelog: [needless_borrow], [redundant_clone]: Now track references better and detect more cases
#9701

@rust-highfive
Copy link

r? @giraffate

(rust-highfive has picked a reviewer for you, use r? to override)

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Oct 24, 2022
@bors
Copy link
Collaborator

bors commented Oct 27, 2022

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

@xFrednet
Copy link
Member

Hey, thank you for the PR. Could you maybe expand on the changelog entry a bit and explain how you specific improved it? 🙃

@smoelius
Copy link
Contributor Author

@xFrednet Please tell me if what I have now suffices.

@Jarcho
Copy link
Contributor

Jarcho commented Oct 31, 2022

The changelog lines shouldn't mention internal changes. only things visible from the user's perspective.

@smoelius
Copy link
Contributor Author

The changelog lines shouldn't mention internal changes. only things visible from the user's perspective.

👍 I'm going to let @xFrednet reply before revising again, though.

@xFrednet
Copy link
Member

I agree with @Jarcho, these changelog entries seam to focus on the actual change and not the user-facing effect. I guess that you improved the tracking of lifetimes to avoid false positives?

The problem with mentioning rustc related objects like MaybeStorageLive are also not known to everyone. I at least never heard of it 😅. With this, I would probably just read the uitest files and see what has chained.

@smoelius Thank you for taking the time to figure this out and also to document this discussion!

@smoelius
Copy link
Contributor Author

I went with:

changelog: improved the tracking of references to avoid false negatives in `needless_borrow` and `redundant_clone`

But please tell me if this is still not what's desired. Thank you for your feedback, @xFrednet @Jarcho.

@xFrednet
Copy link
Member

xFrednet commented Nov 3, 2022

That should be good enough :), thank you!

@Jarcho
Copy link
Contributor

Jarcho commented Nov 26, 2022

Going to take over this. r? @Jarcho

Unless @giraffate has any objections.

@rust-highfive rust-highfive assigned Jarcho and unassigned giraffate Nov 26, 2022
@smoelius
Copy link
Contributor Author

Thank you, @Jarcho. Thank you, @giraffate.

let maybe_live = &self.maybe_live;

let mut queued = BitSet::new_empty(self.body.local_decls.len());
let mut deque = VecDeque::with_capacity(self.body.local_decls.len());
Copy link
Contributor

Choose a reason for hiding this comment

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

Can this not just be a Vec? I don't see a reason to process the borrowers in order here.

Copy link
Contributor

Choose a reason for hiding this comment

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

It would also be better to allocate both of these up front when creating the PossibleBorrowerMap and just clear them at the start of the function.

@smoelius
Copy link
Contributor Author

Sorry for the delay, @Jarcho. I rebased and addressed your comments thus far.

@Jarcho
Copy link
Contributor

Jarcho commented Dec 20, 2022

Thank you. @bors r+

@bors
Copy link
Collaborator

bors commented Dec 20, 2022

📌 Commit 9d1cb71 has been approved by Jarcho

It is now in the queue for this repository.

@bors
Copy link
Collaborator

bors commented Dec 20, 2022

⌛ Testing commit 9d1cb71 with merge be98a0e...

bors added a commit that referenced this pull request Dec 20, 2022
Improve `possible_borrower`

This PR makes several improvements to `clippy_uitls::mir::possible_borrower`. These changes benefit both `needless_borrow` and `redundant clone`.

1. **Use the compiler's `MaybeStorageLive` analysis**

I could spot not functional differences between the one in the compiler and the one in Clippy's repository. So, I removed the latter in favor of the the former.

2. **Make `PossibleBorrower` a dataflow analysis instead of a visitor**

The main benefit of this change is that allows `possible_borrower` to take advantage of statements' relative locations, which is easier to do in an analysis than in a visitor.

This is easier to illustrate with an example, so consider this one:
```rust
    fn foo(cx: &LateContext<'_>, lint: &'static Lint) {
        cx.struct_span_lint(lint, rustc_span::Span::default(), "", |diag| diag.note(&String::new()));
        //                                                                          ^
    }
```
We would like to flag the `&` pointed to by the `^` for removal. `foo`'s MIR begins like this:
```rust
fn span_lint::foo::{closure#0}(_1: [closure@$DIR/needless_borrow.rs:396:68: 396:74], _2: &mut rustc_errors::diagnostic_builder::DiagnosticBuilder<'_, ()>) -> &mut rustc_errors::diagnostic_builder::DiagnosticBuilder<'_, ()> {
    debug diag => _2;                    // in scope 0 at $DIR/needless_borrow.rs:396:69: 396:73
    let mut _0: &mut rustc_errors::diagnostic_builder::DiagnosticBuilder<'_, ()>; // return place in scope 0 at $DIR/needless_borrow.rs:396:75: 396:75
    let mut _3: &mut rustc_errors::diagnostic_builder::DiagnosticBuilder<'_, ()>; // in scope 0 at $DIR/needless_borrow.rs:396:75: 396:100
    let mut _4: &mut rustc_errors::diagnostic_builder::DiagnosticBuilder<'_, ()>; // in scope 0 at $DIR/needless_borrow.rs:396:75: 396:100
    let mut _5: &std::string::String;    // in scope 0 at $DIR/needless_borrow.rs:396:85: 396:99
    let _6: std::string::String;         // in scope 0 at $DIR/needless_borrow.rs:396:86: 396:99

    bb0: {
        StorageLive(_3);                 // scope 0 at $DIR/needless_borrow.rs:396:75: 396:100
        StorageLive(_4);                 // scope 0 at $DIR/needless_borrow.rs:396:75: 396:100
        _4 = &mut (*_2);                 // scope 0 at $DIR/needless_borrow.rs:396:75: 396:100
        StorageLive(_5);                 // scope 0 at $DIR/needless_borrow.rs:396:85: 396:99
        StorageLive(_6);                 // scope 0 at $DIR/needless_borrow.rs:396:86: 396:99
        _6 = std::string::String::new() -> bb1; // scope 0 at $DIR/needless_borrow.rs:396:86: 396:99
                                         // mir::Constant
                                         // + span: $DIR/needless_borrow.rs:396:86: 396:97
                                         // + literal: Const { ty: fn() -> std::string::String {std::string::String::new}, val: Value(<ZST>) }
    }

    bb1: {
        _5 = &_6;                        // scope 0 at $DIR/needless_borrow.rs:396:85: 396:99
        _3 = rustc_errors::diagnostic_builder::DiagnosticBuilder::<'_, ()>::note::<&std::string::String>(move _4, move _5) -> [return: bb2, unwind: bb4]; // scope 0 at $DIR/needless_borrow.rs:396:75: 396:100
                                         // mir::Constant
                                         // + span: $DIR/needless_borrow.rs:396:80: 396:84
                                         // + literal: Const { ty: for<'a> fn(&'a mut rustc_errors::diagnostic_builder::DiagnosticBuilder<'_, ()>, &std::string::String) -> &'a mut rustc_errors::diagnostic_builder::DiagnosticBuilder<'_, ()> {rustc_errors::diagnostic_builder::DiagnosticBuilder::<'_, ()>::note::<&std::string::String>}, val: Value(<ZST>) }
    }
```
The call to `diag.note` appears in `bb1` on the line beginning with `_3 =`. The `String` is owned by `_6`. So, in the call to `diag.note`, we would like to know whether there are any references to `_6` besides `_5`.

The old, visitor approach did not consider the relative locations of statements. So all borrows were treated the same, *even if they occurred after the location of interest*.

For example, before the `_3 = ...` call, the possible borrowers of `_6` would be just `_5`. But after the call, the possible borrowers would include `_2`, `_3`, and `_4`.

So, in a sense, the call from which we are try to remove the needless borrow is trying to prevent us from removing the needless borrow(!).

With an analysis, things do not get so muddled. We can determine the set of possible borrowers at any specific location, e.g., using a `ResultsCursor`.

3. **Change `only_borrowers` to `at_most_borrowers`**

`possible_borrowers` exposed a function `only_borrowers` that determined whether the borrowers of some local were *exactly* some set `S`. But, from what I can tell, this was overkill. For the lints that currently use `possible_borrower` (`needless_borrow` and `redundant_clone`), all we really want to know is whether there are borrowers *other than* those in `S`. (Put another way, we only care about the subset relation in one direction.) The new function `at_most_borrowers` takes this more tailored approach.

4. **Compute relations "on the fly" rather than using `transitive_relation`**

The visitor would compute and store the transitive closure of the possible borrower relation for an entire MIR body.

But with an analysis, there is effectively a different possible borrower relation at each location in the body. Computing and storing a transitive closure at each location would not be practical.

So the new approach is to compute the transitive closure on the fly, as needed. But the new approach might actually be more efficient, as I now explain.

In all current uses of `at_most_borrowers` (previously `only_borrowers`), the size of the set of borrowers `S` is at most 2. So you need only check at most three borrowers to determine whether the subset relation holds. That is, once you have found a third borrower, you can stop, since you know the relation cannot hold.

Note that `transitive_relation` is still used by `clippy_uitls::mir::possible_origin` (a kind of "subroutine" of `possible_borrower`).

cc: `@Jarcho`

---

changelog: [`needless_borrow`], [`redundant_clone`]: Now track references better and detect more cases
[#9701](#9701)
<!-- changelog_checked -->
@bors
Copy link
Collaborator

bors commented Dec 20, 2022

💔 Test failed - checks-action_test

@smoelius
Copy link
Contributor Author

Thank you, @Jarcho, and sorry for the trouble. I rebased and pushed what I think is a fix for the build failure.

@Jarcho
Copy link
Contributor

Jarcho commented Dec 22, 2022

@bors retry

@xFrednet
Copy link
Member

@bors r=Jarcho

Bors removes the approval, after any changes to the last commit. This should start the run :)

@bors
Copy link
Collaborator

bors commented Dec 22, 2022

📌 Commit 4dbd8ad has been approved by Jarcho

It is now in the queue for this repository.

@bors
Copy link
Collaborator

bors commented Dec 22, 2022

⌛ Testing commit 4dbd8ad with merge 4fe3727...

@bors
Copy link
Collaborator

bors commented Dec 22, 2022

☀️ Test successful - checks-action_dev_test, checks-action_remark_test, checks-action_test
Approved by: Jarcho
Pushing 4fe3727 to master...

@bors bors merged commit 4fe3727 into rust-lang:master Dec 22, 2022
@smoelius smoelius deleted the improve-possible-borrower branch December 22, 2022 15:40
@smoelius
Copy link
Contributor Author

Thanks again, @Jarcho.

This was referenced Jan 12, 2023
bors added a commit that referenced this pull request Jan 12, 2023
Partially revert #9701

This partially reverts #9701 due to #10134

r? `@flip1995`

changelog: None
ephemeralriggs pushed a commit to google/omaha-client that referenced this pull request Feb 19, 2024
Only applies to those targets which opt-in to this lint. The lint was
recently expanded to catch new instances in
rust-lang/rust-clippy#9701 .

Bug: 118659
Change-Id: Ic72b16783d0e5f6a804615fa7f792206fd2a534e
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/785723
Reviewed-by: Sen Jiang <senj@google.com>
Commit-Queue: Auto-Submit <auto-submit@fuchsia-infra.iam.gserviceaccount.com>
Reviewed-by: Joseph Ryan <josephry@google.com>
Reviewed-by: Marc Khouri <mnck@google.com>
Reviewed-by: Steven Grady <slgrady@google.com>
Fuchsia-Auto-Submit: Dan Johnson <computerdruid@google.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: Awaiting review from the assignee but also interested parties
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants